diff --git a/app/src/au/au_svr.pgc b/app/src/au/au_svr.pgc index 2384ecd..84a18cd 100644 --- a/app/src/au/au_svr.pgc +++ b/app/src/au/au_svr.pgc @@ -1,53 +1,56 @@ /* - * au_svr.pgc - au (승인/한도) MODULE SERVER. + * au_svr.pgc - au (승인/한도) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of au's ~30 online services now lives in its own file + * (app/src/au/svc/.pgc) with its own copybook header + * (app/src/au/svc/.h); those objects are archived into libausvc.a and + * linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all services from a {name, fn} table, + * - tpclose() at shutdown. * - * 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. + * 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 -#include -#include #include #include #include -#include -#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); } +/* every au service's copybook header (declares its record struct + prototype) */ +#include "AUTH.h" +#include "AUTH_CANCEL.h" +#include "AUTH_PARTCXL.h" +#include "LIMIT_CHK.h" +#include "LIMIT_DEC.h" +#include "LIMIT_RST.h" +#include "LIMIT_INQ.h" +#include "STANDIN.h" +#include "FRAUD_DETECT.h" +#include "AUTH_RETRY.h" +#include "INSTALL_AUTH.h" +#include "AUTH_STATUS.h" +#include "AUTH_REVERSE.h" +#include "AUTH_APPROVE.h" +#include "AUTH_DECLINE.h" +#include "CARD_VALIDATE.h" +#include "VELOCITY_CHK.h" +#include "BLACKLIST_CHK.h" +#include "AUTH_HOLD.h" +#include "AUTH_RELEASE.h" +#include "AUTH_HISTORY.h" +#include "LIMIT_SET.h" +#include "LIMIT_TEMP.h" +#include "AUTH_SUMMARY.h" +#include "AUTH_ROUTE.h" +#include "DCC_QUOTE.h" +#include "AUTH_CAPTURE.h" +#include "PREAUTH.h" +#include "PREAUTH_COMPLETE.h" +#include "AUTH_VOID.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"AUTH", AUTH}, {"AUTH_CANCEL", AUTH_CANCEL}, {"AUTH_PARTCXL", AUTH_PARTCXL}, {"LIMIT_CHK", LIMIT_CHK}, @@ -90,571 +93,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/au/batch/au_blacklist_promote_batch.pgc b/app/src/au/batch/au_blacklist_promote_batch.pgc new file mode 100644 index 0000000..f1d0681 --- /dev/null +++ b/app/src/au/batch/au_blacklist_promote_batch.pgc @@ -0,0 +1,41 @@ +/* + * au_blacklist_promote_batch.pgc - au 고위험 카드 블랙리스트 승격 (INSERT..SELECT, XA). + * + * Promotes cards with high fraud scores into au_blacklist via a single set-based + * INSERT..SELECT (ON CONFLICT skip), under ONE global XA transaction. + * Usage: au_blacklist_promote_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + 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 INSERT INTO au_blacklist (card_no, reason, biz_date) + SELECT DISTINCT card_no, 'HIGH_FRAUD_SCORE', :h_bizdate + FROM au_fraud_log WHERE biz_date = :h_bizdate AND score >= 80 + ON CONFLICT (card_no) DO NOTHING; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_blacklist_promote_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_blacklist_promote_batch COMMIT: bizdate=%s promoted=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_cancel_daily_batch.pgc b/app/src/au/batch/au_cancel_daily_batch.pgc new file mode 100644 index 0000000..be05491 --- /dev/null +++ b/app/src/au/batch/au_cancel_daily_batch.pgc @@ -0,0 +1,40 @@ +/* + * au_cancel_daily_batch.pgc - au 일일 취소 집계 (다중 집계 배치, XA). + * + * Runs two sequential EXEC SQL aggregates (FULL then PARTIAL cancel sums) on the + * biz_date under ONE global XA transaction. Usage: au_cancel_daily_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_full, h_part; + 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 SELECT coalesce(sum(amount),0) INTO :h_full FROM au_cancel + WHERE biz_date = :h_bizdate AND cancel_type = 'FULL'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FULL agg FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_part FROM au_cancel + WHERE biz_date = :h_bizdate AND cancel_type = 'PARTIAL'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "PARTIAL agg FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_cancel_daily_batch COMMIT: bizdate=%s full=%ld partial=%ld\n", bizdate, h_full, h_part); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_capture_sweep_batch.pgc b/app/src/au/batch/au_capture_sweep_batch.pgc new file mode 100644 index 0000000..73e6bb0 --- /dev/null +++ b/app/src/au/batch/au_capture_sweep_batch.pgc @@ -0,0 +1,52 @@ +/* + * au_capture_sweep_batch.pgc - au 승인 매입확정 스윕 (커서 + 인라인 UPDATE, XA). + * + * Cursor-scans approved(A) authorizations for a biz_date and flips each to + * captured(M) with an inline EXEC SQL UPDATE inside the loop, all under ONE + * global XA transaction. Usage: au_capture_sweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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; + 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 csc CURSOR FOR + SELECT auth_id FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'A' ORDER BY auth_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_auth; + 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; } + EXEC SQL UPDATE au_authorization SET status = 'M', captured_at = now(), updated_at = now() + WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 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(">>> au_capture_sweep_batch COMMIT: bizdate=%s captured(A->M)=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_daily_auth_batch.pgc b/app/src/au/batch/au_daily_auth_batch.pgc new file mode 100644 index 0000000..b76de5d --- /dev/null +++ b/app/src/au/batch/au_daily_auth_batch.pgc @@ -0,0 +1,38 @@ +/* + * au_daily_auth_batch.pgc - au 일일 승인 총계 (단건 집계 배치, XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, an EXEC SQL aggregate runs on the biz_date, then tpcommit drives + * XA 2PC. Usage: au_daily_auth_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_appr, h_gross; + 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 SELECT count(*), count(*) FILTER (WHERE status = 'A'), coalesce(sum(amount),0) + INTO :h_cnt, :h_appr, :h_gross FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_daily_auth_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_daily_auth_batch COMMIT: bizdate=%s auths=%ld approved=%ld gross=%ld\n", bizdate, h_cnt, h_appr, h_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_dcc_settle_batch.pgc b/app/src/au/batch/au_dcc_settle_batch.pgc new file mode 100644 index 0000000..c74aaf2 --- /dev/null +++ b/app/src/au/batch/au_dcc_settle_batch.pgc @@ -0,0 +1,37 @@ +/* + * au_dcc_settle_batch.pgc - au 대리승인(STANDIN) 정산 집계 (2열 집계, XA). + * + * Single EXEC SQL two-column aggregate over STANDIN-type authorizations for a + * biz_date under ONE global XA transaction. Usage: au_dcc_settle_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, 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 SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum + FROM au_authorization WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_dcc_settle_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_dcc_settle_batch COMMIT: bizdate=%s standin_txns=%ld standin_sum=%ld\n", bizdate, h_cnt, h_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_decline_bucket_batch.pgc b/app/src/au/batch/au_decline_bucket_batch.pgc new file mode 100644 index 0000000..00fdc5c --- /dev/null +++ b/app/src/au/batch/au_decline_bucket_batch.pgc @@ -0,0 +1,50 @@ +/* + * au_decline_bucket_batch.pgc - au 거절사유 버킷 집계 (커서 누적, 무기록, XA). + * + * Cursor-scans the day's declines grouped by reason and accumulates counts in C + * (read-only work, no DB write) under ONE global XA transaction, printing the + * total and distinct-reason bucket count. Usage: au_decline_bucket_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long buckets = 0, total = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[128]; + long 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; } + + EXEC SQL DECLARE dbc CURSOR FOR + SELECT decline_reason, count(*) FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'D' GROUP BY decline_reason; + EXEC SQL OPEN dbc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH dbc INTO :h_reason, :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 dbc; tpabort(0); return 1; } + total = total + h_cnt; + buckets++; + } + EXEC SQL CLOSE dbc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_decline_bucket_batch COMMIT: bizdate=%s reasons=%ld total_declines=%ld\n", bizdate, buckets, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_eod_reflect_batch.pgc b/app/src/au/batch/au_eod_reflect_batch.pgc new file mode 100644 index 0000000..2131b43 --- /dev/null +++ b/app/src/au/batch/au_eod_reflect_batch.pgc @@ -0,0 +1,52 @@ +/* + * au_eod_reflect_batch.pgc - au 일마감 승인 반영 (커서 + 다중 dbio 호출, XA). + * + * Cursor-scans captured(M) authorizations for a biz_date and, per row, calls TWO + * dbio helpers (settle the status, then re-stamp the amount) under ONE global XA + * transaction. Usage: au_eod_reflect_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rows = 0, total = 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 erc CURSOR FOR + SELECT auth_id, amount FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'M' ORDER BY auth_id; + EXEC SQL OPEN erc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH erc 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 erc; tpabort(0); return 1; } + if (audb_update_auth_status(h_auth, "S") < 0) { EXEC SQL CLOSE erc; tpabort(0); return 1; } + if (audb_update_auth_amount(h_auth, h_amt) < 0) { EXEC SQL CLOSE erc; tpabort(0); return 1; } + total = total + h_amt; + rows++; + } + EXEC SQL CLOSE erc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_eod_reflect_batch COMMIT: bizdate=%s reflected(M->S)=%ld amount=%ld\n", bizdate, rows, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_fraud_summary_batch.pgc b/app/src/au/batch/au_fraud_summary_batch.pgc new file mode 100644 index 0000000..c0aba5e --- /dev/null +++ b/app/src/au/batch/au_fraud_summary_batch.pgc @@ -0,0 +1,35 @@ +/* + * au_fraud_summary_batch.pgc - au 부정거래 요약 (dbio 헬퍼 호출 배치, XA). + * + * Instead of inline SQL, this batch drives the libaudbio.a query helpers + * (audb_fraud_avg_score + audb_auth_count_by_date) under ONE global XA + * transaction and prints a summary. Usage: au_fraud_summary_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" +#include "au_fraud_agg_dbio.h" +#include "au_auth_agg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long avg_score, auth_cnt; + + 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; } + + avg_score = audb_fraud_avg_score(bizdate); + if (avg_score < 0) { fprintf(stderr, "avg_score FAIL\n"); tpabort(0); return 1; } + auth_cnt = audb_auth_count_by_date(bizdate); + if (auth_cnt < 0) { fprintf(stderr, "auth_cnt FAIL\n"); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_fraud_summary_batch COMMIT: bizdate=%s auths=%ld avg_fraud_score=%ld\n", bizdate, auth_cnt, avg_score); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_limit_reset_batch.pgc b/app/src/au/batch/au_limit_reset_batch.pgc new file mode 100644 index 0000000..5ff1924 --- /dev/null +++ b/app/src/au/batch/au_limit_reset_batch.pgc @@ -0,0 +1,43 @@ +/* + * au_limit_reset_batch.pgc - au 일마감 한도 사용액 초기화 (커서 + dbio, XA). + * + * Cursor-scans every card_limit row and calls audb_limit_reset() on each under + * ONE global XA transaction, zeroing daily used_amount. Usage: au_limit_reset_batch + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + 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; } + + EXEC SQL DECLARE lrc CURSOR FOR SELECT card_no FROM card_limit WHERE used_amount > 0; + EXEC SQL OPEN lrc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH lrc INTO :h_card; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE lrc; tpabort(0); return 1; } + if (audb_limit_reset(h_card) < 0) { EXEC SQL CLOSE lrc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE lrc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_limit_reset_batch COMMIT: cards_reset=%ld\n", rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_limit_topup_batch.pgc b/app/src/au/batch/au_limit_topup_batch.pgc new file mode 100644 index 0000000..74388b3 --- /dev/null +++ b/app/src/au/batch/au_limit_topup_batch.pgc @@ -0,0 +1,41 @@ +/* + * au_limit_topup_batch.pgc - au 임시한도 정리 + 기본한도 상향 (다중 UPDATE, XA). + * + * Runs two sequential set UPDATEs (clear temp_limit, then top up daily_limit for + * low-limit cards) under ONE global XA transaction. Usage: au_limit_topup_batch [DELTA] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + long delta = (argc > 1) ? atol(argv[1]) : 500000; + long cleared = 0, topped = 0; + + EXEC SQL BEGIN DECLARE SECTION; + long h_delta; + EXEC SQL END DECLARE SECTION; + h_delta = delta; + + 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 UPDATE card_limit SET temp_limit = 0, updated_at = now() WHERE temp_limit > 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "clear FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cleared = sqlca.sqlerrd[2]; + + EXEC SQL UPDATE card_limit SET daily_limit = daily_limit + :h_delta, updated_at = now() + WHERE daily_limit < 1000000; + if (sqlca.sqlcode < 0) { fprintf(stderr, "topup FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + topped = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_limit_topup_batch COMMIT: temp_cleared=%ld topped_up=%ld delta=%ld\n", cleared, topped, delta); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_preauth_expire_batch.pgc b/app/src/au/batch/au_preauth_expire_batch.pgc new file mode 100644 index 0000000..ba134f0 --- /dev/null +++ b/app/src/au/batch/au_preauth_expire_batch.pgc @@ -0,0 +1,39 @@ +/* + * au_preauth_expire_batch.pgc - au 미확정 예비승인 만료 (단건 UPDATE, XA). + * + * Expires stale pre-authorizations (status 'P') for a biz_date to 'X' in a single + * set UPDATE, reporting the affected-row count via sqlca.sqlerrd[2], under ONE + * global XA transaction. Usage: au_preauth_expire_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long expired = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + 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 UPDATE au_preauth SET status = 'X' + WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_preauth_expire_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + expired = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_preauth_expire_batch COMMIT: bizdate=%s expired(P->X)=%ld\n", bizdate, expired); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_reversal_audit_batch.pgc b/app/src/au/batch/au_reversal_audit_batch.pgc new file mode 100644 index 0000000..ad04d58 --- /dev/null +++ b/app/src/au/batch/au_reversal_audit_batch.pgc @@ -0,0 +1,44 @@ +/* + * au_reversal_audit_batch.pgc - au 역전건 감사 (집계 후 UPDATE, XA). + * + * First aggregates the day's reversed(R) authorization amount, then stamps those + * rows as audited via a set UPDATE - both under ONE global XA transaction. + * Usage: au_reversal_audit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long stamped = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long 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 SELECT coalesce(sum(amount),0) INTO :h_sum FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SUM FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL UPDATE au_authorization SET updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + stamped = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_reversal_audit_batch COMMIT: bizdate=%s reversed_sum=%ld stamped=%ld\n", bizdate, h_sum, stamped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_summary_build_batch.pgc b/app/src/au/batch/au_summary_build_batch.pgc new file mode 100644 index 0000000..9121872 --- /dev/null +++ b/app/src/au/batch/au_summary_build_batch.pgc @@ -0,0 +1,48 @@ +/* + * au_summary_build_batch.pgc - au 가맹점 승인 일집계 생성 (INSERT..SELECT, XA). + * + * Rolls the day's au_authorization rows up per merchant and INSERTs them into + * au_summary in a single set-based statement (ON CONFLICT merge), all under ONE + * global XA transaction. Usage: au_summary_build_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + 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 INSERT INTO au_summary + (merchant_id, biz_date, auth_count, approve_count, decline_count, auth_amount) + SELECT merchant_id, :h_bizdate, count(*), + count(*) FILTER (WHERE status = 'A'), + count(*) FILTER (WHERE status = 'D'), + coalesce(sum(amount),0) + FROM au_authorization WHERE biz_date = :h_bizdate GROUP BY merchant_id + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET auth_count = EXCLUDED.auth_count, approve_count = EXCLUDED.approve_count, + decline_count = EXCLUDED.decline_count, auth_amount = EXCLUDED.auth_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_summary_build_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_summary_build_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_summary_rate_batch.pgc b/app/src/au/batch/au_summary_rate_batch.pgc new file mode 100644 index 0000000..8266616 --- /dev/null +++ b/app/src/au/batch/au_summary_rate_batch.pgc @@ -0,0 +1,40 @@ +/* + * au_summary_rate_batch.pgc - au 승인율 리포트 (dbio 호출 + 인라인 집계 혼합, XA). + * + * Combines a libaudbio.a helper (audb_summary_approval_rate) with an inline + * EXEC SQL aggregate under ONE global XA transaction. Usage: au_summary_rate_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" +#include "au_summary_rate_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rate; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_merchants; + 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; } + + rate = audb_summary_approval_rate(bizdate); + if (rate < 0) { fprintf(stderr, "rate FAIL\n"); tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_merchants FROM au_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "count FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_summary_rate_batch COMMIT: bizdate=%s merchants=%ld approval_rate=%ld%%\n", bizdate, h_merchants, rate); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_void_sweep_batch.pgc b/app/src/au/batch/au_void_sweep_batch.pgc new file mode 100644 index 0000000..ba23c16 --- /dev/null +++ b/app/src/au/batch/au_void_sweep_batch.pgc @@ -0,0 +1,44 @@ +/* + * au_void_sweep_batch.pgc - au 역전건 무효화 스윕 (UPDATE 후 집계, XA). + * + * First flips reversed(R) authorizations for a biz_date to void(V) with a set + * UPDATE, then aggregates the remaining voided count - both under ONE global XA + * transaction. Usage: au_void_sweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long voided = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_total; + 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 UPDATE au_authorization SET status = 'V', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + voided = sqlca.sqlerrd[2]; + + EXEC SQL SELECT count(*) INTO :h_total FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'V'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_void_sweep_batch COMMIT: bizdate=%s newly_voided=%ld total_void=%ld\n", bizdate, voided, h_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/dbio/au_auth_agg_dbio.h b/app/src/au/dbio/au_auth_agg_dbio.h new file mode 100644 index 0000000..27395a2 --- /dev/null +++ b/app/src/au/dbio/au_auth_agg_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_agg_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_agg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_AGG_DBIO_H +#define AU_AUTH_AGG_DBIO_H + +typedef struct { char biz_date[16]; long gross; long cnt; } au_auth_agg_rec_t; + +long audb_auth_gross_by_date(const char *bizdate); +long audb_auth_count_by_date(const char *bizdate); + +#endif /* AU_AUTH_AGG_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_agg_dbio.pgc b/app/src/au/dbio/au_auth_agg_dbio.pgc new file mode 100644 index 0000000..0e31757 --- /dev/null +++ b/app/src/au/dbio/au_auth_agg_dbio.pgc @@ -0,0 +1,35 @@ +/* + * au_auth_agg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_agg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: aggregate sum/count (일자별 집계). + */ +#include +#include +#include "au_auth_agg_dbio.h" + +/* 당일 승인 총액. */ +long audb_auth_gross_by_date(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'A'; + if (sqlca.sqlcode < 0) { userlog("audb_auth_gross_by_date FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} + +/* 당일 승인 건수. */ +long audb_auth_count_by_date(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("audb_auth_count_by_date FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_auth_bucket_dbio.h b/app/src/au/dbio/au_auth_bucket_dbio.h new file mode 100644 index 0000000..1860f48 --- /dev/null +++ b/app/src/au/dbio/au_auth_bucket_dbio.h @@ -0,0 +1,13 @@ +/* + * au_auth_bucket_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_bucket_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_BUCKET_DBIO_H +#define AU_AUTH_BUCKET_DBIO_H + +typedef struct { char biz_date[16]; long small; long medium; long large; } au_auth_bucket_rec_t; + +long audb_auth_large_bucket(const char *bizdate); + +#endif /* AU_AUTH_BUCKET_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_bucket_dbio.pgc b/app/src/au/dbio/au_auth_bucket_dbio.pgc new file mode 100644 index 0000000..64dded0 --- /dev/null +++ b/app/src/au/dbio/au_auth_bucket_dbio.pgc @@ -0,0 +1,25 @@ +/* + * au_auth_bucket_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_bucket_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: CASE-based bucketing aggregate (금액대별 버킷팅, 고액 버킷 건수). + */ +#include +#include +#include "au_auth_bucket_dbio.h" + +/* 당일 승인을 금액대(소/중/대)로 CASE 버킷팅하여 고액(>=1,000,000) 버킷 건수 반환. */ +long audb_auth_large_bucket(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum( + CASE WHEN amount >= 1000000 THEN 1 + WHEN amount >= 100000 THEN 0 + ELSE 0 END), 0) INTO :h_v + FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_auth_overavg_dbio.h b/app/src/au/dbio/au_auth_overavg_dbio.h new file mode 100644 index 0000000..f96eaeb --- /dev/null +++ b/app/src/au/dbio/au_auth_overavg_dbio.h @@ -0,0 +1,13 @@ +/* + * au_auth_overavg_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_overavg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_OVERAVG_DBIO_H +#define AU_AUTH_OVERAVG_DBIO_H + +typedef struct { char biz_date[16]; long over_avg_cnt; } au_auth_overavg_rec_t; + +long audb_auth_over_avg_count(const char *bizdate); + +#endif /* AU_AUTH_OVERAVG_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_overavg_dbio.pgc b/app/src/au/dbio/au_auth_overavg_dbio.pgc new file mode 100644 index 0000000..e0120df --- /dev/null +++ b/app/src/au/dbio/au_auth_overavg_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_auth_overavg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_overavg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: scalar-subquery comparison WHERE amount > (SELECT avg ...) (평균초과 승인 건수). + */ +#include +#include +#include "au_auth_overavg_dbio.h" + +/* 당일 평균 승인금액을 초과하는 승인 건수 (스칼라 서브쿼리 비교). */ +long audb_auth_over_avg_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM au_authorization + WHERE biz_date = :h_bizdate + AND amount > (SELECT coalesce(avg(amount),0) FROM au_authorization WHERE biz_date = :h_bizdate); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_auth_query_dbio.h b/app/src/au/dbio/au_auth_query_dbio.h new file mode 100644 index 0000000..5591a48 --- /dev/null +++ b/app/src/au/dbio/au_auth_query_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_QUERY_DBIO_H +#define AU_AUTH_QUERY_DBIO_H + +typedef struct { long auth_id; long amount; char status[8]; } au_auth_q_rec_t; + +long audb_auth_amount(long auth_id); +long audb_auth_card_count(const char *card); + +#endif /* AU_AUTH_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_query_dbio.pgc b/app/src/au/dbio/au_auth_query_dbio.pgc new file mode 100644 index 0000000..69c0d5d --- /dev/null +++ b/app/src/au/dbio/au_auth_query_dbio.pgc @@ -0,0 +1,33 @@ +/* + * au_auth_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: plain scalar SELECT (단건 스칼라 조회). + */ +#include +#include +#include "au_auth_query_dbio.h" + +/* 단일 승인의 금액. */ +long audb_auth_amount(long auth_id) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_v = 0; + EXEC SQL END DECLARE SECTION; + h_id = auth_id; + EXEC SQL SELECT coalesce(amount,0) INTO :h_v FROM au_authorization WHERE auth_id = :h_id; + if (sqlca.sqlcode < 0) { userlog("audb_auth_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} + +/* 카드의 전체 승인 건수. */ +long audb_auth_card_count(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM au_authorization WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) { userlog("audb_auth_card_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_auth_status_dbio.h b/app/src/au/dbio/au_auth_status_dbio.h new file mode 100644 index 0000000..6651aa2 --- /dev/null +++ b/app/src/au/dbio/au_auth_status_dbio.h @@ -0,0 +1,13 @@ +/* + * au_auth_status_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_status_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_STATUS_DBIO_H +#define AU_AUTH_STATUS_DBIO_H + +typedef struct { char biz_date[16]; char status[8]; long cnt; } au_auth_status_rec_t; + +long audb_status_declined_count(const char *bizdate); + +#endif /* AU_AUTH_STATUS_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_status_dbio.pgc b/app/src/au/dbio/au_auth_status_dbio.pgc new file mode 100644 index 0000000..7c95bda --- /dev/null +++ b/app/src/au/dbio/au_auth_status_dbio.pgc @@ -0,0 +1,30 @@ +/* + * au_auth_status_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_status_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: cursor loop (DECLARE/OPEN/FETCH/CLOSE) over status groups. + */ +#include +#include +#include "au_auth_status_dbio.h" + +/* 상태별 건수를 커서로 순회하여 거절(D) 건수만 추출. */ +long audb_status_declined_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_st[8]; + long h_cnt, declined = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE ausc CURSOR FOR + SELECT status, count(*) FROM au_authorization WHERE biz_date = :h_bizdate GROUP BY status; + EXEC SQL OPEN ausc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH ausc INTO :h_st, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ausc; return -1; } + if (h_st[0] == 'D') declined = h_cnt; + } + EXEC SQL CLOSE ausc; + return declined; +} diff --git a/app/src/au/dbio/au_auth_type_dbio.h b/app/src/au/dbio/au_auth_type_dbio.h new file mode 100644 index 0000000..adb6efc --- /dev/null +++ b/app/src/au/dbio/au_auth_type_dbio.h @@ -0,0 +1,13 @@ +/* + * au_auth_type_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_type_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_AUTH_TYPE_DBIO_H +#define AU_AUTH_TYPE_DBIO_H + +typedef struct { char biz_date[16]; long variety; } au_auth_type_rec_t; + +long audb_auth_type_variety(const char *bizdate); + +#endif /* AU_AUTH_TYPE_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_type_dbio.pgc b/app/src/au/dbio/au_auth_type_dbio.pgc new file mode 100644 index 0000000..f9a9539 --- /dev/null +++ b/app/src/au/dbio/au_auth_type_dbio.pgc @@ -0,0 +1,21 @@ +/* + * au_auth_type_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_type_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: count(DISTINCT ...) (당일 사용된 승인유형 가짓수). + */ +#include +#include +#include "au_auth_type_dbio.h" + +/* 당일 사용된 승인유형(AUTH/STANDIN/INSTALL/PREAUTH ...) 가짓수. */ +long audb_auth_type_variety(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT auth_type) INTO :h_v FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_blacklist_query_dbio.h b/app/src/au/dbio/au_blacklist_query_dbio.h new file mode 100644 index 0000000..fa9619d --- /dev/null +++ b/app/src/au/dbio/au_blacklist_query_dbio.h @@ -0,0 +1,13 @@ +/* + * au_blacklist_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_blacklist_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_BLACKLIST_QUERY_DBIO_H +#define AU_BLACKLIST_QUERY_DBIO_H + +typedef struct { char card_no[24]; long listed; } au_blacklist_q_rec_t; + +long audb_blacklist_exists(const char *card); + +#endif /* AU_BLACKLIST_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_blacklist_query_dbio.pgc b/app/src/au/dbio/au_blacklist_query_dbio.pgc new file mode 100644 index 0000000..b719154 --- /dev/null +++ b/app/src/au/dbio/au_blacklist_query_dbio.pgc @@ -0,0 +1,22 @@ +/* + * au_blacklist_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_blacklist_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: EXISTS predicate SELECT (블랙리스트 존재여부 0/1). + */ +#include +#include +#include "au_blacklist_query_dbio.h" + +/* 카드가 블랙리스트에 존재하면 1, 없으면 0. */ +long audb_blacklist_exists(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + int h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT CASE WHEN EXISTS (SELECT 1 FROM au_blacklist WHERE card_no = :h_card) + THEN 1 ELSE 0 END INTO :h_v; + if (sqlca.sqlcode < 0) { userlog("audb_blacklist_exists FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long)h_v; +} diff --git a/app/src/au/dbio/au_cancel_agg_dbio.h b/app/src/au/dbio/au_cancel_agg_dbio.h new file mode 100644 index 0000000..297fb53 --- /dev/null +++ b/app/src/au/dbio/au_cancel_agg_dbio.h @@ -0,0 +1,13 @@ +/* + * au_cancel_agg_dbio.h - au 모듈 DB 접근 계층 copybook (au_cancel_agg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_CANCEL_AGG_DBIO_H +#define AU_CANCEL_AGG_DBIO_H + +typedef struct { char biz_date[16]; long full_amt; long partial_amt; } au_cancel_agg_rec_t; + +long audb_cancel_partial_sum(const char *bizdate); + +#endif /* AU_CANCEL_AGG_DBIO_H */ diff --git a/app/src/au/dbio/au_cancel_agg_dbio.pgc b/app/src/au/dbio/au_cancel_agg_dbio.pgc new file mode 100644 index 0000000..41524d1 --- /dev/null +++ b/app/src/au/dbio/au_cancel_agg_dbio.pgc @@ -0,0 +1,25 @@ +/* + * au_cancel_agg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_cancel_agg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: FILTER (WHERE ...) aggregate (취소유형별 분리 집계). + */ +#include +#include +#include "au_cancel_agg_dbio.h" + +/* FULL/PARTIAL 취소를 한 번에 분리 집계하고 PARTIAL 합 반환. */ +long audb_cancel_partial_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_full = 0, h_part = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(amount) FILTER (WHERE cancel_type = 'FULL'), 0), + coalesce(sum(amount) FILTER (WHERE cancel_type = 'PARTIAL'), 0) + INTO :h_full, :h_part FROM au_cancel WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_full < 0) return -1; + return h_part; +} diff --git a/app/src/au/dbio/au_cancel_query_dbio.h b/app/src/au/dbio/au_cancel_query_dbio.h new file mode 100644 index 0000000..0659b55 --- /dev/null +++ b/app/src/au/dbio/au_cancel_query_dbio.h @@ -0,0 +1,13 @@ +/* + * au_cancel_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_cancel_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_CANCEL_QUERY_DBIO_H +#define AU_CANCEL_QUERY_DBIO_H + +typedef struct { long auth_id; long cancel_amount; char cancel_type[8]; } au_cancel_q_rec_t; + +long audb_cancel_amount_by_auth(long auth_id); + +#endif /* AU_CANCEL_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_cancel_query_dbio.pgc b/app/src/au/dbio/au_cancel_query_dbio.pgc new file mode 100644 index 0000000..dbe465d --- /dev/null +++ b/app/src/au/dbio/au_cancel_query_dbio.pgc @@ -0,0 +1,20 @@ +/* + * au_cancel_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_cancel_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: aggregate sum keyed by FK (한 승인의 누적 취소액). + */ +#include +#include +#include "au_cancel_query_dbio.h" + +/* 한 승인건에 대한 누적 취소 금액. */ +long audb_cancel_amount_by_auth(long auth_id) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_auth, h_v = 0; + EXEC SQL END DECLARE SECTION; + h_auth = auth_id; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM au_cancel WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 0) { userlog("audb_cancel_amount_by_auth FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_capture_query_dbio.h b/app/src/au/dbio/au_capture_query_dbio.h new file mode 100644 index 0000000..66ee14a --- /dev/null +++ b/app/src/au/dbio/au_capture_query_dbio.h @@ -0,0 +1,13 @@ +/* + * au_capture_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_capture_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_CAPTURE_QUERY_DBIO_H +#define AU_CAPTURE_QUERY_DBIO_H + +typedef struct { char biz_date[16]; long max_amt; long min_amt; } au_capture_q_rec_t; + +long audb_capture_amount_range(const char *bizdate); + +#endif /* AU_CAPTURE_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_capture_query_dbio.pgc b/app/src/au/dbio/au_capture_query_dbio.pgc new file mode 100644 index 0000000..321f117 --- /dev/null +++ b/app/src/au/dbio/au_capture_query_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_capture_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_capture_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: min AND max in one SELECT, computed range (매입확정 금액 범위). + */ +#include +#include +#include "au_capture_query_dbio.h" + +/* 매입확정(M) 승인의 최대-최소 금액 폭. */ +long audb_capture_amount_range(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(amount),0), coalesce(min(amount),0) + INTO :h_max, :h_min FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'M'; + if (sqlca.sqlcode < 0) return -1; + return h_max - h_min; +} diff --git a/app/src/au/dbio/au_card_velocity_dbio.h b/app/src/au/dbio/au_card_velocity_dbio.h new file mode 100644 index 0000000..55a50ea --- /dev/null +++ b/app/src/au/dbio/au_card_velocity_dbio.h @@ -0,0 +1,13 @@ +/* + * au_card_velocity_dbio.h - au 모듈 DB 접근 계층 copybook (au_card_velocity_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_CARD_VELOCITY_DBIO_H +#define AU_CARD_VELOCITY_DBIO_H + +typedef struct { char biz_date[16]; long blacklisted_auths; } au_card_velocity_rec_t; + +long audb_blacklisted_auth_count(const char *bizdate); + +#endif /* AU_CARD_VELOCITY_DBIO_H */ diff --git a/app/src/au/dbio/au_card_velocity_dbio.pgc b/app/src/au/dbio/au_card_velocity_dbio.pgc new file mode 100644 index 0000000..012846d --- /dev/null +++ b/app/src/au/dbio/au_card_velocity_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_card_velocity_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_card_velocity_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: subquery WHERE col IN (SELECT ...) (블랙리스트 카드의 당일 승인 건수). + */ +#include +#include +#include "au_card_velocity_dbio.h" + +/* 블랙리스트에 등재된 카드가 당일 발생시킨 승인 건수. */ +long audb_blacklisted_auth_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM au_authorization + WHERE biz_date = :h_bizdate + AND card_no IN (SELECT card_no FROM au_blacklist); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_decline_reason_dbio.h b/app/src/au/dbio/au_decline_reason_dbio.h new file mode 100644 index 0000000..949373e --- /dev/null +++ b/app/src/au/dbio/au_decline_reason_dbio.h @@ -0,0 +1,13 @@ +/* + * au_decline_reason_dbio.h - au 모듈 DB 접근 계층 copybook (au_decline_reason_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_DECLINE_REASON_DBIO_H +#define AU_DECLINE_REASON_DBIO_H + +typedef struct { char biz_date[16]; char reason[128]; long cnt; } au_decline_reason_rec_t; + +long audb_decline_total_by_reason(const char *bizdate); + +#endif /* AU_DECLINE_REASON_DBIO_H */ diff --git a/app/src/au/dbio/au_decline_reason_dbio.pgc b/app/src/au/dbio/au_decline_reason_dbio.pgc new file mode 100644 index 0000000..7dc67d7 --- /dev/null +++ b/app/src/au/dbio/au_decline_reason_dbio.pgc @@ -0,0 +1,32 @@ +/* + * au_decline_reason_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_decline_reason_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: cursor over GROUP BY decline_reason, accumulating counts (거절사유 누적). + */ +#include +#include +#include "au_decline_reason_dbio.h" + +/* 거절(D) 건을 사유별로 커서 순회하며 총 거절 건수를 누적. */ +long audb_decline_total_by_reason(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[128]; + long h_cnt, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE drc CURSOR FOR + SELECT decline_reason, count(*) FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'D' + GROUP BY decline_reason; + EXEC SQL OPEN drc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH drc INTO :h_reason, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE drc; return -1; } + total = total + h_cnt; + } + EXEC SQL CLOSE drc; + return total; +} diff --git a/app/src/au/dbio/au_fraud_agg_dbio.h b/app/src/au/dbio/au_fraud_agg_dbio.h new file mode 100644 index 0000000..b2ac8df --- /dev/null +++ b/app/src/au/dbio/au_fraud_agg_dbio.h @@ -0,0 +1,13 @@ +/* + * au_fraud_agg_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_agg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_FRAUD_AGG_DBIO_H +#define AU_FRAUD_AGG_DBIO_H + +typedef struct { char biz_date[16]; long score_sum; long fraud_cnt; } au_fraud_agg_rec_t; + +long audb_fraud_avg_score(const char *bizdate); + +#endif /* AU_FRAUD_AGG_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_agg_dbio.pgc b/app/src/au/dbio/au_fraud_agg_dbio.pgc new file mode 100644 index 0000000..dc6034a --- /dev/null +++ b/app/src/au/dbio/au_fraud_agg_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_fraud_agg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_agg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: computed avg = sum/count (건당 평균 스코어). + */ +#include +#include +#include "au_fraud_agg_dbio.h" + +/* 당일 건당 평균 부정거래 스코어 = sum(score)/count(*). */ +long audb_fraud_avg_score(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(score),0), count(*) + INTO :h_sum, :h_cnt FROM au_fraud_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_cnt <= 0) return 0; + return h_sum / h_cnt; +} diff --git a/app/src/au/dbio/au_fraud_join_dbio.h b/app/src/au/dbio/au_fraud_join_dbio.h new file mode 100644 index 0000000..23dfcef --- /dev/null +++ b/app/src/au/dbio/au_fraud_join_dbio.h @@ -0,0 +1,13 @@ +/* + * au_fraud_join_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_join_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_FRAUD_JOIN_DBIO_H +#define AU_FRAUD_JOIN_DBIO_H + +typedef struct { char biz_date[16]; long high_risk_amount; } au_fraud_join_rec_t; + +long audb_fraud_high_risk_amount(const char *bizdate); + +#endif /* AU_FRAUD_JOIN_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_join_dbio.pgc b/app/src/au/dbio/au_fraud_join_dbio.pgc new file mode 100644 index 0000000..fe33387 --- /dev/null +++ b/app/src/au/dbio/au_fraud_join_dbio.pgc @@ -0,0 +1,24 @@ +/* + * au_fraud_join_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_join_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 2-table JOIN with predicate (고위험 부정거래 연계 승인금액). + */ +#include +#include +#include "au_fraud_join_dbio.h" + +/* fraud_log(score>=50) 와 연결된 승인원장 금액 합계를 조인으로 산출. */ +long audb_fraud_high_risk_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(a.amount),0) INTO :h_v + FROM au_fraud_log f + JOIN au_authorization a ON f.auth_id = a.auth_id + WHERE f.biz_date = :h_bizdate AND f.score >= 50; + if (sqlca.sqlcode < 0) { userlog("audb_fraud_high_risk_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_fraud_query_dbio.h b/app/src/au/dbio/au_fraud_query_dbio.h new file mode 100644 index 0000000..2a3404f --- /dev/null +++ b/app/src/au/dbio/au_fraud_query_dbio.h @@ -0,0 +1,14 @@ +/* + * au_fraud_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_FRAUD_QUERY_DBIO_H +#define AU_FRAUD_QUERY_DBIO_H + +typedef struct { char card_no[24]; long max_score; long min_score; } au_fraud_q_rec_t; + +long audb_fraud_max_score(const char *card); +long audb_fraud_min_score(const char *card); + +#endif /* AU_FRAUD_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_query_dbio.pgc b/app/src/au/dbio/au_fraud_query_dbio.pgc new file mode 100644 index 0000000..854864a --- /dev/null +++ b/app/src/au/dbio/au_fraud_query_dbio.pgc @@ -0,0 +1,34 @@ +/* + * au_fraud_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: min/max aggregate (카드별 부정거래 스코어 극값). + */ +#include +#include +#include "au_fraud_query_dbio.h" + +/* 카드의 최대 부정거래 스코어. */ +long audb_fraud_max_score(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT coalesce(max(score),0) INTO :h_v FROM au_fraud_log WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 카드의 최소 부정거래 스코어. */ +long audb_fraud_min_score(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT coalesce(min(score),0) INTO :h_v FROM au_fraud_log WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_limit_agg_dbio.h b/app/src/au/dbio/au_limit_agg_dbio.h new file mode 100644 index 0000000..37ed6c8 --- /dev/null +++ b/app/src/au/dbio/au_limit_agg_dbio.h @@ -0,0 +1,14 @@ +/* + * au_limit_agg_dbio.h - au 모듈 DB 접근 계층 copybook (au_limit_agg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_LIMIT_AGG_DBIO_H +#define AU_LIMIT_AGG_DBIO_H + +typedef struct { long total_used; long over_cards; } au_limit_agg_rec_t; + +long audb_limit_total_used(void); +long audb_limit_over_cards(void); + +#endif /* AU_LIMIT_AGG_DBIO_H */ diff --git a/app/src/au/dbio/au_limit_agg_dbio.pgc b/app/src/au/dbio/au_limit_agg_dbio.pgc new file mode 100644 index 0000000..f94e28f --- /dev/null +++ b/app/src/au/dbio/au_limit_agg_dbio.pgc @@ -0,0 +1,30 @@ +/* + * au_limit_agg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_limit_agg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: table-wide aggregate + comparison-predicate count (인자 없음). + */ +#include +#include +#include "au_limit_agg_dbio.h" + +/* 전체 카드의 사용금액 총합. */ +long audb_limit_total_used(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(used_amount),0) INTO :h_v FROM card_limit; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 한도를 초과 사용한(used_amount > daily_limit) 카드 수. */ +long audb_limit_over_cards(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_v FROM card_limit WHERE used_amount > daily_limit; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_limit_query_dbio.h b/app/src/au/dbio/au_limit_query_dbio.h new file mode 100644 index 0000000..a6be020 --- /dev/null +++ b/app/src/au/dbio/au_limit_query_dbio.h @@ -0,0 +1,14 @@ +/* + * au_limit_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_limit_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_LIMIT_QUERY_DBIO_H +#define AU_LIMIT_QUERY_DBIO_H + +typedef struct { char card_no[24]; long available; long utilization; } au_limit_q_rec_t; + +long audb_limit_available(const char *card); +long audb_limit_utilization(const char *card); + +#endif /* AU_LIMIT_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_limit_query_dbio.pgc b/app/src/au/dbio/au_limit_query_dbio.pgc new file mode 100644 index 0000000..4c6dd03 --- /dev/null +++ b/app/src/au/dbio/au_limit_query_dbio.pgc @@ -0,0 +1,37 @@ +/* + * au_limit_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_limit_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: computed column SELECT + guarded division. + */ +#include +#include +#include "au_limit_query_dbio.h" + +/* 가용한도 = daily_limit - used_amount (DB 계산). */ +long audb_limit_available(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT coalesce(daily_limit - used_amount, 0) INTO :h_v + FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1; + return h_v; +} + +/* 한도 소진율(%) = used_amount*100/daily_limit (0 나눗셈 방지). */ +long audb_limit_utilization(const char *card) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_lim = 0, h_used = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + EXEC SQL SELECT coalesce(daily_limit,0), coalesce(used_amount,0) + INTO :h_lim, :h_used FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1; + if (h_lim <= 0) return 0; + return h_used * 100 / h_lim; +} diff --git a/app/src/au/dbio/au_limit_upsert_dbio.h b/app/src/au/dbio/au_limit_upsert_dbio.h new file mode 100644 index 0000000..af391b3 --- /dev/null +++ b/app/src/au/dbio/au_limit_upsert_dbio.h @@ -0,0 +1,13 @@ +/* + * au_limit_upsert_dbio.h - au 모듈 DB 접근 계층 copybook (au_limit_upsert_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: 0 ok, -1 = SQL error. + */ +#ifndef AU_LIMIT_UPSERT_DBIO_H +#define AU_LIMIT_UPSERT_DBIO_H + +typedef struct { char card_no[24]; long temp_limit; } au_limit_upsert_rec_t; + +int audb_limit_temp_clear(const char *card); + +#endif /* AU_LIMIT_UPSERT_DBIO_H */ diff --git a/app/src/au/dbio/au_limit_upsert_dbio.pgc b/app/src/au/dbio/au_limit_upsert_dbio.pgc new file mode 100644 index 0000000..062f04f --- /dev/null +++ b/app/src/au/dbio/au_limit_upsert_dbio.pgc @@ -0,0 +1,26 @@ +/* + * au_limit_upsert_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_limit_upsert_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: ON CONFLICT upsert (임시한도 초기화 행 upsert). + */ +#include +#include +#include "au_limit_upsert_dbio.h" + +/* 카드 한도행을 보장하고 임시한도(temp_limit)를 0 으로 초기화(upsert). */ +int audb_limit_temp_clear(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 INSERT INTO card_limit (card_no, daily_limit, used_amount, temp_limit) + VALUES (:h_card, 0, 0, 0) + ON CONFLICT (card_no) DO UPDATE + SET temp_limit = 0, updated_at = now(); + if (sqlca.sqlcode < 0) { + userlog("audb_limit_temp_clear FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + return 0; +} diff --git a/app/src/au/dbio/au_merchant_join_dbio.h b/app/src/au/dbio/au_merchant_join_dbio.h new file mode 100644 index 0000000..e6ccb80 --- /dev/null +++ b/app/src/au/dbio/au_merchant_join_dbio.h @@ -0,0 +1,13 @@ +/* + * au_merchant_join_dbio.h - au 모듈 DB 접근 계층 copybook (au_merchant_join_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_MERCHANT_JOIN_DBIO_H +#define AU_MERCHANT_JOIN_DBIO_H + +typedef struct { char merchant_id[64]; char biz_date[16]; long joined_gross; } au_merchant_join_rec_t; + +long audb_merchant_reconciled_gross(const char *bizdate); + +#endif /* AU_MERCHANT_JOIN_DBIO_H */ diff --git a/app/src/au/dbio/au_merchant_join_dbio.pgc b/app/src/au/dbio/au_merchant_join_dbio.pgc new file mode 100644 index 0000000..45d2568 --- /dev/null +++ b/app/src/au/dbio/au_merchant_join_dbio.pgc @@ -0,0 +1,24 @@ +/* + * au_merchant_join_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_merchant_join_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 2-table JOIN aggregate (승인원장 x 승인집계 조인 총액). + */ +#include +#include +#include "au_merchant_join_dbio.h" + +/* au_summary 에 집계행이 존재하는 가맹점의 당일 승인 총액을 조인으로 집계. */ +long audb_merchant_reconciled_gross(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(a.amount),0) INTO :h_v + FROM au_authorization a + JOIN au_summary s ON a.merchant_id = s.merchant_id AND a.biz_date = s.biz_date + WHERE a.biz_date = :h_bizdate AND a.status = 'A'; + if (sqlca.sqlcode < 0) { userlog("audb_merchant_reconciled_gross FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_preauth_agg_dbio.h b/app/src/au/dbio/au_preauth_agg_dbio.h new file mode 100644 index 0000000..6917709 --- /dev/null +++ b/app/src/au/dbio/au_preauth_agg_dbio.h @@ -0,0 +1,13 @@ +/* + * au_preauth_agg_dbio.h - au 모듈 DB 접근 계층 copybook (au_preauth_agg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_PREAUTH_AGG_DBIO_H +#define AU_PREAUTH_AGG_DBIO_H + +typedef struct { char biz_date[16]; char merchant_id[64]; long open_cnt; } au_preauth_agg_rec_t; + +long audb_preauth_open_merchants(const char *bizdate); + +#endif /* AU_PREAUTH_AGG_DBIO_H */ diff --git a/app/src/au/dbio/au_preauth_agg_dbio.pgc b/app/src/au/dbio/au_preauth_agg_dbio.pgc new file mode 100644 index 0000000..3cfcf49 --- /dev/null +++ b/app/src/au/dbio/au_preauth_agg_dbio.pgc @@ -0,0 +1,32 @@ +/* + * au_preauth_agg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_preauth_agg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: GROUP BY ... HAVING via cursor (미확정 예비승인 보유 가맹점 수). + */ +#include +#include +#include "au_preauth_agg_dbio.h" + +/* 미확정(P) 예비승인을 1건 이상 보유한 가맹점 수를 GROUP BY/HAVING 커서로 계수. */ +long audb_preauth_open_merchants(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cnt, merchants = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE pamc CURSOR FOR + SELECT merchant_id, count(*) FROM au_preauth + WHERE biz_date = :h_bizdate AND status = 'P' + GROUP BY merchant_id HAVING count(*) >= 1; + EXEC SQL OPEN pamc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH pamc INTO :h_merch, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pamc; return -1; } + merchants++; + } + EXEC SQL CLOSE pamc; + return merchants; +} diff --git a/app/src/au/dbio/au_preauth_query_dbio.h b/app/src/au/dbio/au_preauth_query_dbio.h new file mode 100644 index 0000000..e1d41b2 --- /dev/null +++ b/app/src/au/dbio/au_preauth_query_dbio.h @@ -0,0 +1,13 @@ +/* + * au_preauth_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_preauth_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_PREAUTH_QUERY_DBIO_H +#define AU_PREAUTH_QUERY_DBIO_H + +typedef struct { long preauth_id; long amount; char status[8]; } au_preauth_q_rec_t; + +int audb_preauth_detail(long preauth_id, long *amount_out, char *status_out /* >= 2 */); + +#endif /* AU_PREAUTH_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_preauth_query_dbio.pgc b/app/src/au/dbio/au_preauth_query_dbio.pgc new file mode 100644 index 0000000..ed3f23d --- /dev/null +++ b/app/src/au/dbio/au_preauth_query_dbio.pgc @@ -0,0 +1,27 @@ +/* + * au_preauth_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_preauth_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: multi-column SELECT INTO out-params (예비승인 상세). + */ +#include +#include +#include "au_preauth_query_dbio.h" + +/* 예비승인의 금액/상태를 한 번에 조회하여 out 파라미터로 반환. */ +int audb_preauth_detail(long preauth_id, long *amount_out, char *status_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pre = preauth_id, h_amt = 0; + char h_status[8]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(amount,0), status INTO :h_amt, :h_status + FROM au_preauth WHERE preauth_id = :h_pre; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { + userlog("audb_preauth_detail FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + *amount_out = h_amt; + status_out[0] = h_status[0]; + status_out[1] = 0; + return 0; +} diff --git a/app/src/au/dbio/au_summary_query_dbio.h b/app/src/au/dbio/au_summary_query_dbio.h new file mode 100644 index 0000000..1a18d46 --- /dev/null +++ b/app/src/au/dbio/au_summary_query_dbio.h @@ -0,0 +1,13 @@ +/* + * au_summary_query_dbio.h - au 모듈 DB 접근 계층 copybook (au_summary_query_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_SUMMARY_QUERY_DBIO_H +#define AU_SUMMARY_QUERY_DBIO_H + +typedef struct { char merchant_id[64]; char biz_date[16]; long approve_count; } au_summary_q_rec_t; + +long audb_summary_approve_count(const char *merch, const char *bizdate); + +#endif /* AU_SUMMARY_QUERY_DBIO_H */ diff --git a/app/src/au/dbio/au_summary_query_dbio.pgc b/app/src/au/dbio/au_summary_query_dbio.pgc new file mode 100644 index 0000000..447efb4 --- /dev/null +++ b/app/src/au/dbio/au_summary_query_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_summary_query_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_summary_query_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: two-key scalar SELECT (가맹점/일자 승인집계 단건). + */ +#include +#include +#include "au_summary_query_dbio.h" + +/* 가맹점 당일 승인건수(au_summary.approve_count). */ +long audb_summary_approve_count(const char *merch, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_v = 0; + 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 SELECT coalesce(approve_count,0) INTO :h_v FROM au_summary + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("audb_summary_approve_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/au/dbio/au_summary_rate_dbio.h b/app/src/au/dbio/au_summary_rate_dbio.h new file mode 100644 index 0000000..0da5051 --- /dev/null +++ b/app/src/au/dbio/au_summary_rate_dbio.h @@ -0,0 +1,13 @@ +/* + * au_summary_rate_dbio.h - au 모듈 DB 접근 계층 copybook (au_summary_rate_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef AU_SUMMARY_RATE_DBIO_H +#define AU_SUMMARY_RATE_DBIO_H + +typedef struct { char biz_date[16]; long approve; long total; } au_summary_rate_rec_t; + +long audb_summary_approval_rate(const char *bizdate); + +#endif /* AU_SUMMARY_RATE_DBIO_H */ diff --git a/app/src/au/dbio/au_summary_rate_dbio.pgc b/app/src/au/dbio/au_summary_rate_dbio.pgc new file mode 100644 index 0000000..c81981e --- /dev/null +++ b/app/src/au/dbio/au_summary_rate_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_summary_rate_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_summary_rate_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: computed rate = approve*100/auth over an aggregated table (승인율). + */ +#include +#include +#include "au_summary_rate_dbio.h" + +/* 당일 전체 승인율(%) = sum(approve_count)*100/sum(auth_count). */ +long audb_summary_approval_rate(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_appr = 0, h_total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(approve_count),0), coalesce(sum(auth_count),0) + INTO :h_appr, :h_total FROM au_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_total <= 0) return 0; + return h_appr * 100 / h_total; +} diff --git a/app/src/au/run/run_blacklist.sh b/app/src/au/run/run_blacklist.sh new file mode 100755 index 0000000..8d8b53b --- /dev/null +++ b/app/src/au/run/run_blacklist.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_blacklist.sh - 고위험 카드 블랙리스트 승격(INSERT..SELECT) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_blacklist.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_blacklist.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_blacklist_promote_batch "$BIZDATE" diff --git a/app/src/au/run/run_cancel.sh b/app/src/au/run/run_cancel.sh new file mode 100755 index 0000000..ad1b40a --- /dev/null +++ b/app/src/au/run/run_cancel.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_cancel.sh - 일일 취소 집계(FULL/PARTIAL) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_cancel.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_cancel.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_cancel_daily_batch "$BIZDATE" diff --git a/app/src/au/run/run_capture.sh b/app/src/au/run/run_capture.sh new file mode 100755 index 0000000..f7cb483 --- /dev/null +++ b/app/src/au/run/run_capture.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_capture.sh - 승인 매입확정 스윕(A->M) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_capture.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_capture.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_capture_sweep_batch "$BIZDATE" diff --git a/app/src/au/run/run_daily.sh b/app/src/au/run/run_daily.sh new file mode 100755 index 0000000..ed0d91e --- /dev/null +++ b/app/src/au/run/run_daily.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_daily.sh - 일일 승인 총계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_daily.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_daily.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/au_daily_auth_batch "$BIZDATE" diff --git a/app/src/au/run/run_dcc.sh b/app/src/au/run/run_dcc.sh new file mode 100755 index 0000000..7a8db06 --- /dev/null +++ b/app/src/au/run/run_dcc.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_dcc.sh - 대리승인(STANDIN) 정산 집계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_dcc.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_dcc.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_dcc_settle_batch "$BIZDATE" diff --git a/app/src/au/run/run_decline.sh b/app/src/au/run/run_decline.sh new file mode 100755 index 0000000..a565389 --- /dev/null +++ b/app/src/au/run/run_decline.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_decline.sh - 거절사유 버킷 집계(커서 누적) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_decline.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_decline.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_decline_bucket_batch "$BIZDATE" diff --git a/app/src/au/run/run_eod.sh b/app/src/au/run/run_eod.sh new file mode 100755 index 0000000..08c0e08 --- /dev/null +++ b/app/src/au/run/run_eod.sh @@ -0,0 +1,21 @@ +#!/bin/bash +## +## run_eod.sh - 일마감(EOD): 승인/한도 집계·반영 배치 일괄 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_eod.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_eod.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +for b in au_daily_auth_batch au_summary_build_batch au_capture_sweep_batch \ + au_eod_reflect_batch au_settle_reflect_batch au_fraud_scan_batch \ + au_cancel_daily_batch au_decline_bucket_batch au_limit_reset_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/au/run/run_fraud.sh b/app/src/au/run/run_fraud.sh new file mode 100755 index 0000000..8f0e530 --- /dev/null +++ b/app/src/au/run/run_fraud.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_fraud.sh - 부정거래 스캔(GROUP BY HAVING 커서) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fraud.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_fraud.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_fraud_scan_batch "$BIZDATE" diff --git a/app/src/au/run/run_fraud_summary.sh b/app/src/au/run/run_fraud_summary.sh new file mode 100755 index 0000000..7161e55 --- /dev/null +++ b/app/src/au/run/run_fraud_summary.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_fraud_summary.sh - 부정거래 요약(dbio 헬퍼 호출) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fraud_summary.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_fraud_summary.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_fraud_summary_batch "$BIZDATE" diff --git a/app/src/au/run/run_limit_reset.sh b/app/src/au/run/run_limit_reset.sh new file mode 100755 index 0000000..77a3483 --- /dev/null +++ b/app/src/au/run/run_limit_reset.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_limit_reset.sh - 일마감 카드 한도 사용액 초기화 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_limit_reset.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_limit_reset.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_limit_reset_batch diff --git a/app/src/au/run/run_preauth.sh b/app/src/au/run/run_preauth.sh new file mode 100755 index 0000000..2be10b2 --- /dev/null +++ b/app/src/au/run/run_preauth.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_preauth.sh - 미확정 예비승인 만료(P->X) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_preauth.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_preauth.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_preauth_expire_batch "$BIZDATE" diff --git a/app/src/au/run/run_rate.sh b/app/src/au/run/run_rate.sh new file mode 100755 index 0000000..fb9552b --- /dev/null +++ b/app/src/au/run/run_rate.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_rate.sh - 승인율 리포트(dbio 호출 + 인라인 집계) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_rate.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_rate.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_summary_rate_batch "$BIZDATE" diff --git a/app/src/au/run/run_reversal.sh b/app/src/au/run/run_reversal.sh new file mode 100755 index 0000000..79dc91d --- /dev/null +++ b/app/src/au/run/run_reversal.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_reversal.sh - 역전건 감사(집계 후 UPDATE) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_reversal.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_reversal.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_reversal_audit_batch "$BIZDATE" diff --git a/app/src/au/run/run_settle.sh b/app/src/au/run/run_settle.sh new file mode 100755 index 0000000..97102ce --- /dev/null +++ b/app/src/au/run/run_settle.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_settle.sh - 승인 정산반영(커서 M->S) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_settle.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_settle.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_settle_reflect_batch "$BIZDATE" diff --git a/app/src/au/run/run_summary.sh b/app/src/au/run/run_summary.sh new file mode 100755 index 0000000..6969ed5 --- /dev/null +++ b/app/src/au/run/run_summary.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_summary.sh - 가맹점 승인 일집계 생성(INSERT..SELECT) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_summary.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_summary.sh] BIZDATE=$BIZDATE MERCH=$MERCH" +exec /app/bin/au_summary_build_batch "$BIZDATE" diff --git a/app/src/au/run/run_topup.sh b/app/src/au/run/run_topup.sh new file mode 100755 index 0000000..7a3a3c8 --- /dev/null +++ b/app/src/au/run/run_topup.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_topup.sh - 임시한도 정리 + 기본한도 상향(다중 UPDATE) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_topup.sh [DELTA] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +DELTA="${1:-500000}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_topup.sh] DELTA=$DELTA" +exec /app/bin/au_limit_topup_batch "$DELTA" diff --git a/app/src/au/run/run_void.sh b/app/src/au/run/run_void.sh new file mode 100755 index 0000000..8465230 --- /dev/null +++ b/app/src/au/run/run_void.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_void.sh - 역전건 무효화 스윕(UPDATE 후 집계) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_void.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_void.sh] BIZDATE=$BIZDATE" +exec /app/bin/au_void_sweep_batch "$BIZDATE" diff --git a/app/src/au/svc/AUTH.h b/app/src/au/svc/AUTH.h new file mode 100644 index 0000000..dfc3fcd --- /dev/null +++ b/app/src/au/svc/AUTH.h @@ -0,0 +1,24 @@ +/* + * AUTH.h - au 서비스 AUTH 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT, T_AMOUNT, T_BIZDATE + * Response fields: T_ID1(auth_id), T_RC, T_STR2(승인번호), T_STATUS + */ +#ifndef AUTH_H +#define AUTH_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char card_no[24]; + char merchant_id[64]; + long amount; + char approval_code[12]; + char auth_type[12]; + char status[8]; + char biz_date[16]; +} au_auth_rec_t; + +void AUTH(TPSVCINFO *p); + +#endif /* AUTH_H */ diff --git a/app/src/au/svc/AUTH.pgc b/app/src/au/svc/AUTH.pgc new file mode 100644 index 0000000..227be75 --- /dev/null +++ b/app/src/au/svc/AUTH.pgc @@ -0,0 +1,45 @@ +/* + * AUTH.pgc - au 모듈 서비스 AUTH (one service = one file). + * 승인 접수: 한도 점검 후 authorization 원장 기록 + 한도 차감. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH.h" + +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); +} diff --git a/app/src/au/svc/AUTH_APPROVE.h b/app/src/au/svc/AUTH_APPROVE.h new file mode 100644 index 0000000..9b91a21 --- /dev/null +++ b/app/src/au/svc/AUTH_APPROVE.h @@ -0,0 +1,18 @@ +/* + * AUTH_APPROVE.h - au 서비스 AUTH_APPROVE 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id) + * Response fields: T_STATUS + */ +#ifndef AUTH_APPROVE_H +#define AUTH_APPROVE_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char status[8]; +} au_auth_approve_rec_t; + +void AUTH_APPROVE(TPSVCINFO *p); + +#endif /* AUTH_APPROVE_H */ diff --git a/app/src/au/svc/AUTH_APPROVE.pgc b/app/src/au/svc/AUTH_APPROVE.pgc new file mode 100644 index 0000000..714d926 --- /dev/null +++ b/app/src/au/svc/AUTH_APPROVE.pgc @@ -0,0 +1,17 @@ +/* + * AUTH_APPROVE.pgc - au 모듈 서비스 AUTH_APPROVE (one service = one file). + * 승인 확정(pending->approved): dbio 래퍼로 상태만 A 로 확정. + * Kept as the plain dbio-wrapper skeleton (cf. AUTH_HOLD = inline UPDATE ... IN, + * AUTH_RELEASE = SELECT-then-UPDATE) so the three status transitions stay + * structurally distinct. Runs on the caller's XA branch. Advertised by au_svr. + */ +#include "AUTH_APPROVE.h" + +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); +} diff --git a/app/src/au/svc/AUTH_CANCEL.h b/app/src/au/svc/AUTH_CANCEL.h new file mode 100644 index 0000000..02c8974 --- /dev/null +++ b/app/src/au/svc/AUTH_CANCEL.h @@ -0,0 +1,23 @@ +/* + * AUTH_CANCEL.h - au 서비스 AUTH_CANCEL 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT, T_KEY1(카드번호), T_BIZDATE, T_REASON + * Response fields: T_CANCEL_ID, T_STATUS + */ +#ifndef AUTH_CANCEL_H +#define AUTH_CANCEL_H + +#include "au_svc.h" + +typedef struct { + long cancel_id; + long auth_id; + char cancel_type[8]; + long amount; + char card_no[24]; + char reason[128]; + char biz_date[16]; +} au_auth_cancel_rec_t; + +void AUTH_CANCEL(TPSVCINFO *p); + +#endif /* AUTH_CANCEL_H */ diff --git a/app/src/au/svc/AUTH_CANCEL.pgc b/app/src/au/svc/AUTH_CANCEL.pgc new file mode 100644 index 0000000..b7e84c1 --- /dev/null +++ b/app/src/au/svc/AUTH_CANCEL.pgc @@ -0,0 +1,37 @@ +/* + * AUTH_CANCEL.pgc - au 모듈 서비스 AUTH_CANCEL (one service = one file). + * 승인 전체 취소 + 한도 원복. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_CANCEL.h" + +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); +} diff --git a/app/src/au/svc/AUTH_CAPTURE.h b/app/src/au/svc/AUTH_CAPTURE.h new file mode 100644 index 0000000..27bc033 --- /dev/null +++ b/app/src/au/svc/AUTH_CAPTURE.h @@ -0,0 +1,19 @@ +/* + * AUTH_CAPTURE.h - au 서비스 AUTH_CAPTURE 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id) + * Response fields: T_RC(0=매입확정, 1=대상없음), T_STATUS + */ +#ifndef AUTH_CAPTURE_H +#define AUTH_CAPTURE_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + long rc; + char status[8]; +} au_auth_capture_rec_t; + +void AUTH_CAPTURE(TPSVCINFO *p); + +#endif /* AUTH_CAPTURE_H */ diff --git a/app/src/au/svc/AUTH_CAPTURE.pgc b/app/src/au/svc/AUTH_CAPTURE.pgc new file mode 100644 index 0000000..27cbcf2 --- /dev/null +++ b/app/src/au/svc/AUTH_CAPTURE.pgc @@ -0,0 +1,23 @@ +/* + * AUTH_CAPTURE.pgc - au 모듈 서비스 AUTH_CAPTURE (one service = one file). + * 승인 매입확정(capture): 상태 A->M(매입대상). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_CAPTURE.h" + +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); +} diff --git a/app/src/au/svc/AUTH_DECLINE.h b/app/src/au/svc/AUTH_DECLINE.h new file mode 100644 index 0000000..93dd672 --- /dev/null +++ b/app/src/au/svc/AUTH_DECLINE.h @@ -0,0 +1,19 @@ +/* + * AUTH_DECLINE.h - au 서비스 AUTH_DECLINE 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_REASON + * Response fields: T_STATUS + */ +#ifndef AUTH_DECLINE_H +#define AUTH_DECLINE_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char reason[128]; + char status[8]; +} au_auth_decline_rec_t; + +void AUTH_DECLINE(TPSVCINFO *p); + +#endif /* AUTH_DECLINE_H */ diff --git a/app/src/au/svc/AUTH_DECLINE.pgc b/app/src/au/svc/AUTH_DECLINE.pgc new file mode 100644 index 0000000..83c0cf1 --- /dev/null +++ b/app/src/au/svc/AUTH_DECLINE.pgc @@ -0,0 +1,25 @@ +/* + * AUTH_DECLINE.pgc - au 모듈 서비스 AUTH_DECLINE (one service = one file). + * 승인 거절 처리: 거절 사유와 함께 상태 D 로 갱신. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_DECLINE.h" + +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); +} diff --git a/app/src/au/svc/AUTH_HISTORY.h b/app/src/au/svc/AUTH_HISTORY.h new file mode 100644 index 0000000..619bc5c --- /dev/null +++ b/app/src/au/svc/AUTH_HISTORY.h @@ -0,0 +1,18 @@ +/* + * AUTH_HISTORY.h - au 서비스 AUTH_HISTORY 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_COUNT(승인 이력 건수) + */ +#ifndef AUTH_HISTORY_H +#define AUTH_HISTORY_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long hist_count; +} au_auth_history_rec_t; + +void AUTH_HISTORY(TPSVCINFO *p); + +#endif /* AUTH_HISTORY_H */ diff --git a/app/src/au/svc/AUTH_HISTORY.pgc b/app/src/au/svc/AUTH_HISTORY.pgc new file mode 100644 index 0000000..fdfab53 --- /dev/null +++ b/app/src/au/svc/AUTH_HISTORY.pgc @@ -0,0 +1,23 @@ +/* + * AUTH_HISTORY.pgc - au 모듈 서비스 AUTH_HISTORY (one service = one file). + * 카드 승인 이력 건수 조회. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_HISTORY.h" + +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); +} diff --git a/app/src/au/svc/AUTH_HOLD.h b/app/src/au/svc/AUTH_HOLD.h new file mode 100644 index 0000000..31100b6 --- /dev/null +++ b/app/src/au/svc/AUTH_HOLD.h @@ -0,0 +1,19 @@ +/* + * AUTH_HOLD.h - au 서비스 AUTH_HOLD 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id) + * Response fields: T_STATUS, T_RC(0=보류전이, 1=대상없음) + */ +#ifndef AUTH_HOLD_H +#define AUTH_HOLD_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + long affected; + char status[8]; +} au_auth_hold_rec_t; + +void AUTH_HOLD(TPSVCINFO *p); + +#endif /* AUTH_HOLD_H */ diff --git a/app/src/au/svc/AUTH_HOLD.pgc b/app/src/au/svc/AUTH_HOLD.pgc new file mode 100644 index 0000000..61c3c5c --- /dev/null +++ b/app/src/au/svc/AUTH_HOLD.pgc @@ -0,0 +1,26 @@ +/* + * AUTH_HOLD.pgc - au 모듈 서비스 AUTH_HOLD (one service = one file). + * 승인 보류: 승인/예비(A,P) 상태만 보류(H)로 전이, 반영 건수를 T_RC 로. + * Rewritten with an inline UPDATE ... WHERE status IN (...) guard + affected-count + * (via sqlca.sqlerrd[2]) so it is structurally DISTINCT from AUTH_APPROVE (dbio + * wrapper) and AUTH_RELEASE (SELECT-then-UPDATE). Runs on the caller's XA branch. + */ +#include "AUTH_HOLD.h" + +void AUTH_HOLD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_auth, h_aff; + EXEC SQL END DECLARE SECTION; + + h_auth = getl(b, T_ID1); + /* only an approved(A)/pre-authorized(P) authorization may be put on hold */ + EXEC SQL UPDATE au_authorization SET status = 'H', updated_at = now() + WHERE auth_id = :h_auth AND status IN ('A', 'P'); + if (sqlca.sqlcode < 0) { userlog("AUTH_HOLD FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + h_aff = sqlca.sqlerrd[2]; + if (h_aff > 0) Bchg(b, T_STATUS, 0, "H", 0L); + setl(b, T_RC, h_aff > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/au/svc/AUTH_PARTCXL.h b/app/src/au/svc/AUTH_PARTCXL.h new file mode 100644 index 0000000..7bb17c7 --- /dev/null +++ b/app/src/au/svc/AUTH_PARTCXL.h @@ -0,0 +1,23 @@ +/* + * AUTH_PARTCXL.h - au 서비스 AUTH_PARTCXL 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT(취소금액), T_KEY1(카드번호), T_BIZDATE + * Response fields: T_CANCEL_ID, T_AMOUNT(잔여승인금액) + */ +#ifndef AUTH_PARTCXL_H +#define AUTH_PARTCXL_H + +#include "au_svc.h" + +typedef struct { + long cancel_id; + long auth_id; + char cancel_type[8]; + long cancel_amount; + long new_amount; + char card_no[24]; + char biz_date[16]; +} au_auth_partcxl_rec_t; + +void AUTH_PARTCXL(TPSVCINFO *p); + +#endif /* AUTH_PARTCXL_H */ diff --git a/app/src/au/svc/AUTH_PARTCXL.pgc b/app/src/au/svc/AUTH_PARTCXL.pgc new file mode 100644 index 0000000..8834d55 --- /dev/null +++ b/app/src/au/svc/AUTH_PARTCXL.pgc @@ -0,0 +1,37 @@ +/* + * AUTH_PARTCXL.pgc - au 모듈 서비스 AUTH_PARTCXL (one service = one file). + * 부분 취소: 승인 금액 감액 + 한도 부분 원복. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_PARTCXL.h" + +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); +} diff --git a/app/src/au/svc/AUTH_RELEASE.h b/app/src/au/svc/AUTH_RELEASE.h new file mode 100644 index 0000000..652a355 --- /dev/null +++ b/app/src/au/svc/AUTH_RELEASE.h @@ -0,0 +1,20 @@ +/* + * AUTH_RELEASE.h - au 서비스 AUTH_RELEASE 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id) + * Response fields: T_STATUS(전이 후/직전 상태), T_RC(0=해제, 1=미전이) + */ +#ifndef AUTH_RELEASE_H +#define AUTH_RELEASE_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + long affected; + char prev_status[8]; + char status[8]; +} au_auth_release_rec_t; + +void AUTH_RELEASE(TPSVCINFO *p); + +#endif /* AUTH_RELEASE_H */ diff --git a/app/src/au/svc/AUTH_RELEASE.pgc b/app/src/au/svc/AUTH_RELEASE.pgc new file mode 100644 index 0000000..1694fe4 --- /dev/null +++ b/app/src/au/svc/AUTH_RELEASE.pgc @@ -0,0 +1,29 @@ +/* + * AUTH_RELEASE.pgc - au 모듈 서비스 AUTH_RELEASE (one service = one file). + * 보류 해제: 직전 상태를 조회한 뒤 H->A 전이, 이전 상태를 함께 회신. + * Rewritten as SELECT-prev-status-then-UPDATE (WHERE status='H') so it is + * structurally DISTINCT from AUTH_HOLD (inline UPDATE ... IN) and AUTH_APPROVE + * (dbio wrapper). Runs on the caller's XA branch. Advertised by au_svr. + */ +#include "AUTH_RELEASE.h" + +void AUTH_RELEASE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_auth, h_aff; + char h_prev[8]; + EXEC SQL END DECLARE SECTION; + + h_auth = getl(b, T_ID1); + EXEC SQL SELECT status INTO :h_prev FROM au_authorization WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + /* release only from HOLD; report whether the transition actually fired */ + EXEC SQL UPDATE au_authorization SET status = 'A', updated_at = now() + WHERE auth_id = :h_auth AND status = 'H'; + if (sqlca.sqlcode < 0) FAIL(b); + h_aff = sqlca.sqlerrd[2]; + Bchg(b, T_STATUS, 0, h_aff > 0 ? "A" : h_prev, 0L); + setl(b, T_RC, h_aff > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/au/svc/AUTH_RETRY.h b/app/src/au/svc/AUTH_RETRY.h new file mode 100644 index 0000000..01502fa --- /dev/null +++ b/app/src/au/svc/AUTH_RETRY.h @@ -0,0 +1,22 @@ +/* + * AUTH_RETRY.h - au 서비스 AUTH_RETRY 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT, T_KEY1(카드번호) + * Response fields: T_RC, T_STATUS + */ +#ifndef AUTH_RETRY_H +#define AUTH_RETRY_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char card_no[24]; + long amount; + long daily_limit; + long used_amount; + char status[8]; +} au_auth_retry_rec_t; + +void AUTH_RETRY(TPSVCINFO *p); + +#endif /* AUTH_RETRY_H */ diff --git a/app/src/au/svc/AUTH_RETRY.pgc b/app/src/au/svc/AUTH_RETRY.pgc new file mode 100644 index 0000000..2007285 --- /dev/null +++ b/app/src/au/svc/AUTH_RETRY.pgc @@ -0,0 +1,22 @@ +/* + * AUTH_RETRY.pgc - au 모듈 서비스 AUTH_RETRY (one service = one file). + * 거절 승인 재시도 (상태 D->A 재승인). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_RETRY.h" + +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); +} diff --git a/app/src/au/svc/AUTH_REVERSE.h b/app/src/au/svc/AUTH_REVERSE.h new file mode 100644 index 0000000..f52bef1 --- /dev/null +++ b/app/src/au/svc/AUTH_REVERSE.h @@ -0,0 +1,20 @@ +/* + * AUTH_REVERSE.h - au 서비스 AUTH_REVERSE 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT, T_KEY1(카드번호) + * Response fields: T_STATUS + */ +#ifndef AUTH_REVERSE_H +#define AUTH_REVERSE_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char card_no[24]; + long amount; + char status[8]; +} au_auth_reverse_rec_t; + +void AUTH_REVERSE(TPSVCINFO *p); + +#endif /* AUTH_REVERSE_H */ diff --git a/app/src/au/svc/AUTH_REVERSE.pgc b/app/src/au/svc/AUTH_REVERSE.pgc new file mode 100644 index 0000000..5587cec --- /dev/null +++ b/app/src/au/svc/AUTH_REVERSE.pgc @@ -0,0 +1,19 @@ +/* + * AUTH_REVERSE.pgc - au 모듈 서비스 AUTH_REVERSE (one service = one file). + * 승인 역전(reversal) + 한도 원복. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_REVERSE.h" + +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); +} diff --git a/app/src/au/svc/AUTH_ROUTE.h b/app/src/au/svc/AUTH_ROUTE.h new file mode 100644 index 0000000..ec8c158 --- /dev/null +++ b/app/src/au/svc/AUTH_ROUTE.h @@ -0,0 +1,19 @@ +/* + * AUTH_ROUTE.h - au 서비스 AUTH_ROUTE 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_STR1(라우팅코드), T_ARG1(BIN) + */ +#ifndef AUTH_ROUTE_H +#define AUTH_ROUTE_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char route[8]; + long bin; +} au_auth_route_rec_t; + +void AUTH_ROUTE(TPSVCINFO *p); + +#endif /* AUTH_ROUTE_H */ diff --git a/app/src/au/svc/AUTH_ROUTE.pgc b/app/src/au/svc/AUTH_ROUTE.pgc new file mode 100644 index 0000000..b8bfa40 --- /dev/null +++ b/app/src/au/svc/AUTH_ROUTE.pgc @@ -0,0 +1,23 @@ +/* + * AUTH_ROUTE.pgc - au 모듈 서비스 AUTH_ROUTE (one service = one file). + * 승인 라우팅(발급사 BIN 기준 라우팅 코드 산출). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_ROUTE.h" + +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); +} diff --git a/app/src/au/svc/AUTH_STATUS.h b/app/src/au/svc/AUTH_STATUS.h new file mode 100644 index 0000000..8eb05a4 --- /dev/null +++ b/app/src/au/svc/AUTH_STATUS.h @@ -0,0 +1,18 @@ +/* + * AUTH_STATUS.h - au 서비스 AUTH_STATUS 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id) + * Response fields: T_STATUS + */ +#ifndef AUTH_STATUS_H +#define AUTH_STATUS_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char status[8]; +} au_auth_status_rec_t; + +void AUTH_STATUS(TPSVCINFO *p); + +#endif /* AUTH_STATUS_H */ diff --git a/app/src/au/svc/AUTH_STATUS.pgc b/app/src/au/svc/AUTH_STATUS.pgc new file mode 100644 index 0000000..f3d4400 --- /dev/null +++ b/app/src/au/svc/AUTH_STATUS.pgc @@ -0,0 +1,17 @@ +/* + * AUTH_STATUS.pgc - au 모듈 서비스 AUTH_STATUS (one service = one file). + * 승인 상태 조회 (dbio). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_STATUS.h" + +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); +} diff --git a/app/src/au/svc/AUTH_SUMMARY.h b/app/src/au/svc/AUTH_SUMMARY.h new file mode 100644 index 0000000..04e9fb1 --- /dev/null +++ b/app/src/au/svc/AUTH_SUMMARY.h @@ -0,0 +1,22 @@ +/* + * AUTH_SUMMARY.h - au 서비스 AUTH_SUMMARY 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT(승인건수), T_GROSS(승인금액) + */ +#ifndef AUTH_SUMMARY_H +#define AUTH_SUMMARY_H + +#include "au_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long auth_count; + long approve_count; + long decline_count; + long auth_amount; +} au_auth_summary_rec_t; + +void AUTH_SUMMARY(TPSVCINFO *p); + +#endif /* AUTH_SUMMARY_H */ diff --git a/app/src/au/svc/AUTH_SUMMARY.pgc b/app/src/au/svc/AUTH_SUMMARY.pgc new file mode 100644 index 0000000..97dafce --- /dev/null +++ b/app/src/au/svc/AUTH_SUMMARY.pgc @@ -0,0 +1,32 @@ +/* + * AUTH_SUMMARY.pgc - au 모듈 서비스 AUTH_SUMMARY (one service = one file). + * 가맹점 당일 승인 집계 upsert (dbio). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_SUMMARY.h" + +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); +} diff --git a/app/src/au/svc/AUTH_VOID.h b/app/src/au/svc/AUTH_VOID.h new file mode 100644 index 0000000..5ad3dbe --- /dev/null +++ b/app/src/au/svc/AUTH_VOID.h @@ -0,0 +1,22 @@ +/* + * AUTH_VOID.h - au 서비스 AUTH_VOID 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT, T_KEY1(카드번호), T_BIZDATE + * Response fields: T_CANCEL_ID, T_STATUS + */ +#ifndef AUTH_VOID_H +#define AUTH_VOID_H + +#include "au_svc.h" + +typedef struct { + long cancel_id; + long auth_id; + char cancel_type[8]; + long amount; + char card_no[24]; + char biz_date[16]; +} au_auth_void_rec_t; + +void AUTH_VOID(TPSVCINFO *p); + +#endif /* AUTH_VOID_H */ diff --git a/app/src/au/svc/AUTH_VOID.pgc b/app/src/au/svc/AUTH_VOID.pgc new file mode 100644 index 0000000..c18eadc --- /dev/null +++ b/app/src/au/svc/AUTH_VOID.pgc @@ -0,0 +1,31 @@ +/* + * AUTH_VOID.pgc - au 모듈 서비스 AUTH_VOID (one service = one file). + * 당일 승인 무효화(void) + 한도 원복. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "AUTH_VOID.h" + +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); +} diff --git a/app/src/au/svc/BLACKLIST_CHK.h b/app/src/au/svc/BLACKLIST_CHK.h new file mode 100644 index 0000000..4beb591 --- /dev/null +++ b/app/src/au/svc/BLACKLIST_CHK.h @@ -0,0 +1,18 @@ +/* + * BLACKLIST_CHK.h - au 서비스 BLACKLIST_CHK 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_RC(1=블랙리스트) + */ +#ifndef BLACKLIST_CHK_H +#define BLACKLIST_CHK_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long blacklisted; +} au_blacklist_chk_rec_t; + +void BLACKLIST_CHK(TPSVCINFO *p); + +#endif /* BLACKLIST_CHK_H */ diff --git a/app/src/au/svc/BLACKLIST_CHK.pgc b/app/src/au/svc/BLACKLIST_CHK.pgc new file mode 100644 index 0000000..38e81fd --- /dev/null +++ b/app/src/au/svc/BLACKLIST_CHK.pgc @@ -0,0 +1,23 @@ +/* + * BLACKLIST_CHK.pgc - au 모듈 서비스 BLACKLIST_CHK (one service = one file). + * 블랙리스트 카드 조회. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "BLACKLIST_CHK.h" + +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); +} diff --git a/app/src/au/svc/CARD_VALIDATE.h b/app/src/au/svc/CARD_VALIDATE.h new file mode 100644 index 0000000..c7f91cd --- /dev/null +++ b/app/src/au/svc/CARD_VALIDATE.h @@ -0,0 +1,18 @@ +/* + * CARD_VALIDATE.h - au 서비스 CARD_VALIDATE 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_RC(0=유효, 1=오류) + */ +#ifndef CARD_VALIDATE_H +#define CARD_VALIDATE_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long valid_flag; +} au_card_validate_rec_t; + +void CARD_VALIDATE(TPSVCINFO *p); + +#endif /* CARD_VALIDATE_H */ diff --git a/app/src/au/svc/CARD_VALIDATE.pgc b/app/src/au/svc/CARD_VALIDATE.pgc new file mode 100644 index 0000000..b5a76bf --- /dev/null +++ b/app/src/au/svc/CARD_VALIDATE.pgc @@ -0,0 +1,16 @@ +/* + * CARD_VALIDATE.pgc - au 모듈 서비스 CARD_VALIDATE (one service = one file). + * 카드번호 Luhn 검증 (common lib). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "CARD_VALIDATE.h" + +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); +} diff --git a/app/src/au/svc/DCC_QUOTE.h b/app/src/au/svc/DCC_QUOTE.h new file mode 100644 index 0000000..4301581 --- /dev/null +++ b/app/src/au/svc/DCC_QUOTE.h @@ -0,0 +1,20 @@ +/* + * DCC_QUOTE.h - au 서비스 DCC_QUOTE 카피북 (copybook / record header). + * Request fields: T_FX_AMT(외화금액), T_CCY(통화) + * Response fields: T_AMOUNT(원화환산), T_ARG1(환율) + */ +#ifndef DCC_QUOTE_H +#define DCC_QUOTE_H + +#include "au_svc.h" + +typedef struct { + char ccy[8]; + long fx_amount; + long rate; + long krw_amount; +} au_dcc_quote_rec_t; + +void DCC_QUOTE(TPSVCINFO *p); + +#endif /* DCC_QUOTE_H */ diff --git a/app/src/au/svc/DCC_QUOTE.pgc b/app/src/au/svc/DCC_QUOTE.pgc new file mode 100644 index 0000000..c4f1d29 --- /dev/null +++ b/app/src/au/svc/DCC_QUOTE.pgc @@ -0,0 +1,20 @@ +/* + * DCC_QUOTE.pgc - au 모듈 서비스 DCC_QUOTE (one service = one file). + * 해외승인 DCC 환산 견적(통화 -> 원화). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "DCC_QUOTE.h" + +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); +} diff --git a/app/src/au/svc/FRAUD_DETECT.h b/app/src/au/svc/FRAUD_DETECT.h new file mode 100644 index 0000000..17aa81b --- /dev/null +++ b/app/src/au/svc/FRAUD_DETECT.h @@ -0,0 +1,23 @@ +/* + * FRAUD_DETECT.h - au 서비스 FRAUD_DETECT 카피북 (copybook / record header). + * Request fields: T_ID1(auth_id), T_AMOUNT, T_KEY1(카드번호), T_BIZDATE + * Response fields: T_ARG1(score), T_ID2(fraud_id), T_RC + */ +#ifndef FRAUD_DETECT_H +#define FRAUD_DETECT_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + long fraud_id; + char card_no[24]; + long amount; + long score; + char reason[128]; + char biz_date[16]; +} au_fraud_detect_rec_t; + +void FRAUD_DETECT(TPSVCINFO *p); + +#endif /* FRAUD_DETECT_H */ diff --git a/app/src/au/svc/FRAUD_DETECT.pgc b/app/src/au/svc/FRAUD_DETECT.pgc new file mode 100644 index 0000000..9e9ca1a --- /dev/null +++ b/app/src/au/svc/FRAUD_DETECT.pgc @@ -0,0 +1,43 @@ +/* + * FRAUD_DETECT.pgc - au 모듈 서비스 FRAUD_DETECT (one service = one file). + * 부정거래 스코어링 + 위험시 fraud_log 기록. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "FRAUD_DETECT.h" + +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); +} diff --git a/app/src/au/svc/INSTALL_AUTH.h b/app/src/au/svc/INSTALL_AUTH.h new file mode 100644 index 0000000..32e367e --- /dev/null +++ b/app/src/au/svc/INSTALL_AUTH.h @@ -0,0 +1,22 @@ +/* + * INSTALL_AUTH.h - au 서비스 INSTALL_AUTH 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT, T_AMOUNT, T_INSTALL_N(개월수), T_BIZDATE + * Response fields: T_COUNT(할부개월수) + */ +#ifndef INSTALL_AUTH_H +#define INSTALL_AUTH_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char card_no[24]; + char merchant_id[64]; + long amount; + long months; + char biz_date[16]; +} au_install_auth_rec_t; + +void INSTALL_AUTH(TPSVCINFO *p); + +#endif /* INSTALL_AUTH_H */ diff --git a/app/src/au/svc/INSTALL_AUTH.pgc b/app/src/au/svc/INSTALL_AUTH.pgc new file mode 100644 index 0000000..373d79a --- /dev/null +++ b/app/src/au/svc/INSTALL_AUTH.pgc @@ -0,0 +1,27 @@ +/* + * INSTALL_AUTH.pgc - au 모듈 서비스 INSTALL_AUTH (one service = one file). + * 할부 승인: 개월 수만큼 분할 승인 레코드. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "INSTALL_AUTH.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_CHK.h b/app/src/au/svc/LIMIT_CHK.h new file mode 100644 index 0000000..999b41d --- /dev/null +++ b/app/src/au/svc/LIMIT_CHK.h @@ -0,0 +1,21 @@ +/* + * LIMIT_CHK.h - au 서비스 LIMIT_CHK 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMOUNT + * Response fields: T_AMT1(한도), T_AMT2(사용액), T_RC(초과여부) + */ +#ifndef LIMIT_CHK_H +#define LIMIT_CHK_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long amount; + long daily_limit; + long used_amount; + long over_flag; +} au_limit_chk_rec_t; + +void LIMIT_CHK(TPSVCINFO *p); + +#endif /* LIMIT_CHK_H */ diff --git a/app/src/au/svc/LIMIT_CHK.pgc b/app/src/au/svc/LIMIT_CHK.pgc new file mode 100644 index 0000000..3101b1a --- /dev/null +++ b/app/src/au/svc/LIMIT_CHK.pgc @@ -0,0 +1,21 @@ +/* + * LIMIT_CHK.pgc - au 모듈 서비스 LIMIT_CHK (one service = one file). + * 카드 한도 점검 (사용가능 여부만 리턴). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_CHK.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_DEC.h b/app/src/au/svc/LIMIT_DEC.h new file mode 100644 index 0000000..74b0d17 --- /dev/null +++ b/app/src/au/svc/LIMIT_DEC.h @@ -0,0 +1,18 @@ +/* + * LIMIT_DEC.h - au 서비스 LIMIT_DEC 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMOUNT(차감액) + * Response fields: (none) + */ +#ifndef LIMIT_DEC_H +#define LIMIT_DEC_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long amount; +} au_limit_dec_rec_t; + +void LIMIT_DEC(TPSVCINFO *p); + +#endif /* LIMIT_DEC_H */ diff --git a/app/src/au/svc/LIMIT_DEC.pgc b/app/src/au/svc/LIMIT_DEC.pgc new file mode 100644 index 0000000..44bfc63 --- /dev/null +++ b/app/src/au/svc/LIMIT_DEC.pgc @@ -0,0 +1,17 @@ +/* + * LIMIT_DEC.pgc - au 모듈 서비스 LIMIT_DEC (one service = one file). + * 한도 차감(사용금액 증가). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_DEC.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_INQ.h b/app/src/au/svc/LIMIT_INQ.h new file mode 100644 index 0000000..c938846 --- /dev/null +++ b/app/src/au/svc/LIMIT_INQ.h @@ -0,0 +1,20 @@ +/* + * LIMIT_INQ.h - au 서비스 LIMIT_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_AMT1(한도), T_AMT2(사용액), T_AMT3(가용한도) + */ +#ifndef LIMIT_INQ_H +#define LIMIT_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long daily_limit; + long used_amount; + long available; +} au_limit_inq_rec_t; + +void LIMIT_INQ(TPSVCINFO *p); + +#endif /* LIMIT_INQ_H */ diff --git a/app/src/au/svc/LIMIT_INQ.pgc b/app/src/au/svc/LIMIT_INQ.pgc new file mode 100644 index 0000000..951b7fe --- /dev/null +++ b/app/src/au/svc/LIMIT_INQ.pgc @@ -0,0 +1,20 @@ +/* + * LIMIT_INQ.pgc - au 모듈 서비스 LIMIT_INQ (one service = one file). + * 한도 조회 (가용한도 계산). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_INQ.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_RST.h b/app/src/au/svc/LIMIT_RST.h new file mode 100644 index 0000000..a0a559e --- /dev/null +++ b/app/src/au/svc/LIMIT_RST.h @@ -0,0 +1,17 @@ +/* + * LIMIT_RST.h - au 서비스 LIMIT_RST 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: (none) + */ +#ifndef LIMIT_RST_H +#define LIMIT_RST_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; +} au_limit_rst_rec_t; + +void LIMIT_RST(TPSVCINFO *p); + +#endif /* LIMIT_RST_H */ diff --git a/app/src/au/svc/LIMIT_RST.pgc b/app/src/au/svc/LIMIT_RST.pgc new file mode 100644 index 0000000..28f97ec --- /dev/null +++ b/app/src/au/svc/LIMIT_RST.pgc @@ -0,0 +1,16 @@ +/* + * LIMIT_RST.pgc - au 모듈 서비스 LIMIT_RST (one service = one file). + * 한도 사용금액 초기화(일마감 원복). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_RST.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_SET.h b/app/src/au/svc/LIMIT_SET.h new file mode 100644 index 0000000..1c3fe28 --- /dev/null +++ b/app/src/au/svc/LIMIT_SET.h @@ -0,0 +1,18 @@ +/* + * LIMIT_SET.h - au 서비스 LIMIT_SET 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMT1(한도) + * Response fields: (none) + */ +#ifndef LIMIT_SET_H +#define LIMIT_SET_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long daily_limit; +} au_limit_set_rec_t; + +void LIMIT_SET(TPSVCINFO *p); + +#endif /* LIMIT_SET_H */ diff --git a/app/src/au/svc/LIMIT_SET.pgc b/app/src/au/svc/LIMIT_SET.pgc new file mode 100644 index 0000000..c635d38 --- /dev/null +++ b/app/src/au/svc/LIMIT_SET.pgc @@ -0,0 +1,18 @@ +/* + * LIMIT_SET.pgc - au 모듈 서비스 LIMIT_SET (one service = one file). + * 카드 한도 설정/등록 (dbio upsert). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_SET.h" + +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); +} diff --git a/app/src/au/svc/LIMIT_TEMP.h b/app/src/au/svc/LIMIT_TEMP.h new file mode 100644 index 0000000..7b4d541 --- /dev/null +++ b/app/src/au/svc/LIMIT_TEMP.h @@ -0,0 +1,18 @@ +/* + * LIMIT_TEMP.h - au 서비스 LIMIT_TEMP 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMT1(임시한도) + * Response fields: (none) + */ +#ifndef LIMIT_TEMP_H +#define LIMIT_TEMP_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long temp_limit; +} au_limit_temp_rec_t; + +void LIMIT_TEMP(TPSVCINFO *p); + +#endif /* LIMIT_TEMP_H */ diff --git a/app/src/au/svc/LIMIT_TEMP.pgc b/app/src/au/svc/LIMIT_TEMP.pgc new file mode 100644 index 0000000..eece714 --- /dev/null +++ b/app/src/au/svc/LIMIT_TEMP.pgc @@ -0,0 +1,25 @@ +/* + * LIMIT_TEMP.pgc - au 모듈 서비스 LIMIT_TEMP (one service = one file). + * 임시 한도 상향 (temp_limit 저장 + 당일 상향). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "LIMIT_TEMP.h" + +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); +} diff --git a/app/src/au/svc/PREAUTH.h b/app/src/au/svc/PREAUTH.h new file mode 100644 index 0000000..316bdb9 --- /dev/null +++ b/app/src/au/svc/PREAUTH.h @@ -0,0 +1,22 @@ +/* + * PREAUTH.h - au 서비스 PREAUTH 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT, T_AMOUNT, T_BIZDATE + * Response fields: T_ID2(preauth_id) + */ +#ifndef PREAUTH_H +#define PREAUTH_H + +#include "au_svc.h" + +typedef struct { + long preauth_id; + char card_no[24]; + char merchant_id[64]; + long amount; + char status[8]; + char biz_date[16]; +} au_preauth_rec_t; + +void PREAUTH(TPSVCINFO *p); + +#endif /* PREAUTH_H */ diff --git a/app/src/au/svc/PREAUTH.pgc b/app/src/au/svc/PREAUTH.pgc new file mode 100644 index 0000000..915fdc6 --- /dev/null +++ b/app/src/au/svc/PREAUTH.pgc @@ -0,0 +1,31 @@ +/* + * PREAUTH.pgc - au 모듈 서비스 PREAUTH (one service = one file). + * 예비승인(pre-authorization) 기록. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "PREAUTH.h" + +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); +} diff --git a/app/src/au/svc/PREAUTH_COMPLETE.h b/app/src/au/svc/PREAUTH_COMPLETE.h new file mode 100644 index 0000000..763ec69 --- /dev/null +++ b/app/src/au/svc/PREAUTH_COMPLETE.h @@ -0,0 +1,22 @@ +/* + * PREAUTH_COMPLETE.h - au 서비스 PREAUTH_COMPLETE 카피북 (copybook / record header). + * Request fields: T_ID2(preauth_id), T_BIZDATE + * Response fields: T_ID1(auth_id) + */ +#ifndef PREAUTH_COMPLETE_H +#define PREAUTH_COMPLETE_H + +#include "au_svc.h" + +typedef struct { + long preauth_id; + long auth_id; + char card_no[24]; + char merchant_id[64]; + long amount; + char biz_date[16]; +} au_preauth_complete_rec_t; + +void PREAUTH_COMPLETE(TPSVCINFO *p); + +#endif /* PREAUTH_COMPLETE_H */ diff --git a/app/src/au/svc/PREAUTH_COMPLETE.pgc b/app/src/au/svc/PREAUTH_COMPLETE.pgc new file mode 100644 index 0000000..ffb22e4 --- /dev/null +++ b/app/src/au/svc/PREAUTH_COMPLETE.pgc @@ -0,0 +1,32 @@ +/* + * PREAUTH_COMPLETE.pgc - au 모듈 서비스 PREAUTH_COMPLETE (one service = one file). + * 예비승인 확정: preauth 소진 + 본승인 생성. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "PREAUTH_COMPLETE.h" + +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); +} diff --git a/app/src/au/svc/STANDIN.h b/app/src/au/svc/STANDIN.h new file mode 100644 index 0000000..c658dd5 --- /dev/null +++ b/app/src/au/svc/STANDIN.h @@ -0,0 +1,23 @@ +/* + * STANDIN.h - au 서비스 STANDIN 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT, T_AMOUNT, T_BIZDATE + * Response fields: T_ID1(auth_id), T_RC, T_STATUS + */ +#ifndef STANDIN_H +#define STANDIN_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char card_no[24]; + char merchant_id[64]; + long amount; + long standin_limit; + char status[8]; + char biz_date[16]; +} au_standin_rec_t; + +void STANDIN(TPSVCINFO *p); + +#endif /* STANDIN_H */ diff --git a/app/src/au/svc/STANDIN.pgc b/app/src/au/svc/STANDIN.pgc new file mode 100644 index 0000000..f469dbf --- /dev/null +++ b/app/src/au/svc/STANDIN.pgc @@ -0,0 +1,26 @@ +/* + * STANDIN.pgc - au 모듈 서비스 STANDIN (one service = one file). + * 발급사 무응답 시 대리승인(STANDIN, 소액 한도 내). + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "STANDIN.h" + +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); +} diff --git a/app/src/au/svc/VELOCITY_CHK.h b/app/src/au/svc/VELOCITY_CHK.h new file mode 100644 index 0000000..6b2fc42 --- /dev/null +++ b/app/src/au/svc/VELOCITY_CHK.h @@ -0,0 +1,21 @@ +/* + * VELOCITY_CHK.h - au 서비스 VELOCITY_CHK 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_BIZDATE + * Response fields: T_COUNT(당일건수), T_GROSS(당일합계), T_RC(속도초과) + */ +#ifndef VELOCITY_CHK_H +#define VELOCITY_CHK_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char biz_date[16]; + long txn_count; + long txn_sum; + long over_flag; +} au_velocity_chk_rec_t; + +void VELOCITY_CHK(TPSVCINFO *p); + +#endif /* VELOCITY_CHK_H */ diff --git a/app/src/au/svc/VELOCITY_CHK.pgc b/app/src/au/svc/VELOCITY_CHK.pgc new file mode 100644 index 0000000..891c22c --- /dev/null +++ b/app/src/au/svc/VELOCITY_CHK.pgc @@ -0,0 +1,28 @@ +/* + * VELOCITY_CHK.pgc - au 모듈 서비스 VELOCITY_CHK (one service = one file). + * 속도(velocity) 점검: 당일 카드 승인건수/합계. + * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit. + */ +#include "VELOCITY_CHK.h" + +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); +} diff --git a/app/src/au/svc/au_svc.h b/app/src/au/svc/au_svc.h new file mode 100644 index 0000000..994abc4 --- /dev/null +++ b/app/src/au/svc/au_svc.h @@ -0,0 +1,33 @@ +/* + * au_svc.h - au (승인/한도) 서비스 공통 카피북 (shared copybook for all au svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the au dbio layer (libaudbio.a) and the + * common math (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros every service uses. No business logic and no service function lives here. + */ +#ifndef AU_SVC_H +#define AU_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "au_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* AU_SVC_H */ diff --git a/app/src/cl/batch/cl_approve_batch.pgc b/app/src/cl/batch/cl_approve_batch.pgc new file mode 100644 index 0000000..d0bed6b --- /dev/null +++ b/app/src/cl/batch/cl_approve_batch.pgc @@ -0,0 +1,45 @@ +/* + * cl_approve_batch.pgc - cl 마감완료(C) 일괄 승인 배치 (커서 C->A) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_approve_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cid; + 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; } + + long rows = 0; + EXEC SQL DECLARE cab CURSOR FOR + SELECT close_id FROM cl_close_log + WHERE biz_date = :h_bizdate AND status = 'C' ORDER BY close_id; + EXEC SQL OPEN cab; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH cab INTO :h_cid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cab; tpabort(0); return 1; } + if (cldb_update_close_status(h_cid, "A") < 0) { EXEC SQL CLOSE cab; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE cab; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_approve_batch COMMIT: bizdate=%s 승인=%ld건\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_archive_batch.pgc b/app/src/cl/batch/cl_archive_batch.pgc new file mode 100644 index 0000000..ed8740e --- /dev/null +++ b/app/src/cl/batch/cl_archive_batch.pgc @@ -0,0 +1,35 @@ +/* + * cl_archive_batch.pgc - cl 완료마감 아카이브 표시 배치 (dbio update) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_archive_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" +#include "cl_archive_mark_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + + EXEC SQL END DECLARE SECTION; + 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; } + + long marked; + marked = clqdb_archive_mark(bizdate); + if (marked < 0) { fprintf(stderr, "cl_archive_batch FAIL\n"); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_archive_batch COMMIT: %s 이전 완료마감 %ld건 아카이브\n", bizdate, marked); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_lock_batch.pgc b/app/src/cl/batch/cl_lock_batch.pgc new file mode 100644 index 0000000..762904f --- /dev/null +++ b/app/src/cl/batch/cl_lock_batch.pgc @@ -0,0 +1,47 @@ +/* + * cl_lock_batch.pgc - cl 기간 잠금 배치 (커서, 월 close_log 기간 잠금) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_lock_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_period[16]; + 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; } + + long rows = 0; + EXEC SQL DECLARE clb CURSOR FOR + SELECT DISTINCT period_key FROM cl_close_log + WHERE close_type = 'MONTHLY' AND status IN ('A','C') ORDER BY period_key; + EXEC SQL OPEN clb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH clb INTO :h_period; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clb; tpabort(0); return 1; } + 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) { EXEC SQL CLOSE clb; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE clb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_lock_batch COMMIT: %ld개 기간 잠금\n", rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_midclose_batch.pgc b/app/src/cl/batch/cl_midclose_batch.pgc new file mode 100644 index 0000000..517d489 --- /dev/null +++ b/app/src/cl/batch/cl_midclose_batch.pgc @@ -0,0 +1,38 @@ +/* + * cl_midclose_batch.pgc - cl 중간 마감 배치 (접수/대사 A,M 집계 + MID close_log) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_midclose_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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; } + + long cid; + 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) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cid = cldb_next_close_id(); + if (cid < 0) { tpabort(0); return 1; } + if (cldb_insert_close(cid, "MID", bizdate, bizdate, "O", 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_midclose_batch COMMIT: bizdate=%s cid=%ld 미마감대상=%ld\n", bizdate, cid, h_cnt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_monthly_batch.pgc b/app/src/cl/batch/cl_monthly_batch.pgc new file mode 100644 index 0000000..92cfb44 --- /dev/null +++ b/app/src/cl/batch/cl_monthly_batch.pgc @@ -0,0 +1,40 @@ +/* + * cl_monthly_batch.pgc - cl 월 마감 배치 (월 집계 + close_log MONTHLY) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_monthly_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_period[16]; + long 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; } + + long cid; + strncpy(h_period, bizdate, 7); h_period[7] = 0; + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE to_char(biz_date,'YYYY-MM') = :h_period; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cid = cldb_next_close_id(); + if (cid < 0) { tpabort(0); return 1; } + if (cldb_insert_close(cid, "MONTHLY", h_period, 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_monthly_batch COMMIT: period=%s cid=%ld 건수=%ld 총액=%ld\n", h_period, cid, h_cnt, h_amt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_purge_batch.pgc b/app/src/cl/batch/cl_purge_batch.pgc new file mode 100644 index 0000000..c2ac30c --- /dev/null +++ b/app/src/cl/batch/cl_purge_batch.pgc @@ -0,0 +1,35 @@ +/* + * cl_purge_batch.pgc - cl 취소(X) 마감 정리 배치 (dbio delete) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_purge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" +#include "cl_cancel_purge_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + + EXEC SQL END DECLARE SECTION; + 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; } + + long purged; + purged = clqdb_cancel_purge(bizdate); + if (purged < 0) { fprintf(stderr, "cl_purge_batch FAIL\n"); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_purge_batch COMMIT: %s 이전 취소마감 %ld건 삭제\n", bizdate, purged); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_quarter_batch.pgc b/app/src/cl/batch/cl_quarter_batch.pgc new file mode 100644 index 0000000..0d8a1af --- /dev/null +++ b/app/src/cl/batch/cl_quarter_batch.pgc @@ -0,0 +1,45 @@ +/* + * cl_quarter_batch.pgc - cl 분기 마감 배치 (분기 프리픽스 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_quarter_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_year[8]; + long h_cnt, h_amt, h_q; + 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; } + + long cid; + char period[16]; + int mm = (bizdate[5]-'0')*10 + (bizdate[6]-'0'); + h_q = (mm - 1) / 3 + 1; + strncpy(h_year, bizdate, 4); h_year[4] = 0; + snprintf(period, sizeof(period), "%s-Q%ld", h_year, h_q); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE to_char(biz_date,'YYYY') = :h_year + AND (extract(month from biz_date)::int - 1) / 3 + 1 = :h_q; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cid = cldb_next_close_id(); + if (cid < 0) { tpabort(0); return 1; } + if (cldb_insert_close(cid, "QUARTER", period, 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_quarter_batch COMMIT: period=%s cid=%ld 건수=%ld\n", period, cid, h_cnt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_recon_batch.pgc b/app/src/cl/batch/cl_recon_batch.pgc new file mode 100644 index 0000000..c694777 --- /dev/null +++ b/app/src/cl/batch/cl_recon_batch.pgc @@ -0,0 +1,48 @@ +/* + * cl_recon_batch.pgc - cl 마감 대사 배치 (커서, close_log vs purchase 총액 대사) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_recon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cid, h_tsum, h_asum; + 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; } + + long checked = 0, bad = 0; + EXEC SQL DECLARE cnb CURSOR FOR + SELECT close_id, total_amount FROM cl_close_log + WHERE close_type = 'DAILY' AND biz_date = :h_bizdate AND status <> 'X' + ORDER BY close_id; + EXEC SQL OPEN cnb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH cnb INTO :h_cid, :h_tsum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cnb; tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_asum FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cnb; tpabort(0); return 1; } + if (h_tsum != h_asum) { bad++; printf(" cid=%ld 총액 불일치 마감=%ld 실제=%ld\n", h_cid, h_tsum, h_asum); } + checked++; + } + EXEC SQL CLOSE cnb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_recon_batch COMMIT: bizdate=%s 대사=%ld 불일치=%ld\n", bizdate, checked, bad); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_report_batch.pgc b/app/src/cl/batch/cl_report_batch.pgc new file mode 100644 index 0000000..825e1e2 --- /dev/null +++ b/app/src/cl/batch/cl_report_batch.pgc @@ -0,0 +1,46 @@ +/* + * cl_report_batch.pgc - cl 마감 리포트 배치 (커서, 마감별 총계 출력) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_report_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cid, h_cnt, h_amt; + char h_type[16], h_status[8]; + 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; } + + long rows = 0; + EXEC SQL DECLARE crb CURSOR FOR + SELECT close_id, close_type, status, txn_count, total_amount FROM cl_close_log + WHERE biz_date = :h_bizdate ORDER BY close_id; + EXEC SQL OPEN crb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH crb INTO :h_cid, :h_type, :h_status, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crb; tpabort(0); return 1; } + printf(" cid=%ld %-8s [%s] 건수=%ld 총액=%ld\n", h_cid, h_type, h_status, h_cnt, h_amt); + rows++; + } + EXEC SQL CLOSE crb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_report_batch COMMIT: bizdate=%s 마감 %ld건\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_restore_batch.pgc b/app/src/cl/batch/cl_restore_batch.pgc new file mode 100644 index 0000000..733a98b --- /dev/null +++ b/app/src/cl/batch/cl_restore_batch.pgc @@ -0,0 +1,35 @@ +/* + * cl_restore_batch.pgc - cl 취소(X) 마감 복원 배치 (dbio update) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_restore_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" +#include "cl_restore_count_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + + EXEC SQL END DECLARE SECTION; + 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; } + + long restored; + restored = clqdb_restore_count(bizdate); + if (restored < 0) { fprintf(stderr, "cl_restore_batch FAIL\n"); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_restore_batch COMMIT: %s 취소마감 %ld건 개시복원\n", bizdate, restored); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_rollup_batch.pgc b/app/src/cl/batch/cl_rollup_batch.pgc new file mode 100644 index 0000000..0d05d9c --- /dev/null +++ b/app/src/cl/batch/cl_rollup_batch.pgc @@ -0,0 +1,48 @@ +/* + * cl_rollup_batch.pgc - cl 마감 스냅샷 롤업 배치 (커서, 마감별 gross/fee/net 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_rollup_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cid, 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; } + + long rows = 0; + EXEC SQL DECLARE crl CURSOR FOR + SELECT s.close_id, coalesce(sum(s.gross_amount),0), + coalesce(sum(s.fee_amount),0), coalesce(sum(s.net_amount),0) + FROM cl_snapshot s JOIN cl_close_log c ON c.close_id = s.close_id + WHERE c.biz_date = :h_bizdate GROUP BY s.close_id ORDER BY s.close_id; + EXEC SQL OPEN crl; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH crl INTO :h_cid, :h_gross, :h_fee, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crl; tpabort(0); return 1; } + if (cldb_update_close_totals(h_cid, 0, h_gross) < 0) { EXEC SQL CLOSE crl; tpabort(0); return 1; } + printf(" cid=%ld gross=%ld fee=%ld net=%ld\n", h_cid, h_gross, h_fee, h_net); + rows++; + } + EXEC SQL CLOSE crl; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_rollup_batch COMMIT: bizdate=%s 롤업 %ld마감\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_seq_batch.pgc b/app/src/cl/batch/cl_seq_batch.pgc new file mode 100644 index 0000000..7ba4f72 --- /dev/null +++ b/app/src/cl/batch/cl_seq_batch.pgc @@ -0,0 +1,38 @@ +/* + * cl_seq_batch.pgc - cl 마감유형 순번 일괄 발번 배치 (dbio upsert 반복) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_seq_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + + EXEC SQL END DECLARE SECTION; + 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; } + + const char *types[] = { "DAILY", "MONTHLY", "QUARTER", "YEAREND", NULL }; + long seq; int i; + for (i = 0; types[i] != NULL; i++) { + seq = cldb_bump_seq(types[i]); + if (seq < 0) { fprintf(stderr, "bump %s FAIL\n", types[i]); tpabort(0); return 1; } + printf(" %-8s -> seq %ld\n", types[i], seq); + } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_seq_batch COMMIT: bizdate=%s 유형 %d종 발번\n", bizdate, i); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_unclosed_batch.pgc b/app/src/cl/batch/cl_unclosed_batch.pgc new file mode 100644 index 0000000..d25f999 --- /dev/null +++ b/app/src/cl/batch/cl_unclosed_batch.pgc @@ -0,0 +1,37 @@ +/* + * cl_unclosed_batch.pgc - cl 미마감 영업일 점검 배치 (단건 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_unclosed_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_days, h_alert; + 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 SELECT count(DISTINCT p.biz_date) INTO :h_days 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) { fprintf(stderr, "DAYS FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_alert FROM purchase WHERE biz_date = :h_bizdate AND status = 'M'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_unclosed_batch COMMIT: 미마감 영업일=%ld 당일 대사완료 미마감=%ld\n", h_days, h_alert); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_verify_batch.pgc b/app/src/cl/batch/cl_verify_batch.pgc new file mode 100644 index 0000000..a27cca8 --- /dev/null +++ b/app/src/cl/batch/cl_verify_batch.pgc @@ -0,0 +1,49 @@ +/* + * cl_verify_batch.pgc - cl 마감 검증 배치 (커서, 저장건수 vs 실제 대사) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_verify_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cid, h_stored, h_actual; + char h_period[16]; + 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; } + + long checked = 0, bad = 0; + EXEC SQL DECLARE cvb CURSOR FOR + SELECT close_id, txn_count, period_key FROM cl_close_log + WHERE close_type = 'DAILY' AND biz_date = :h_bizdate AND status <> 'X' + ORDER BY close_id; + EXEC SQL OPEN cvb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH cvb INTO :h_cid, :h_stored, :h_period; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvb; tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_actual FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvb; tpabort(0); return 1; } + if (h_stored != h_actual) { bad++; printf(" cid=%ld 불일치 저장=%ld 실제=%ld\n", h_cid, h_stored, h_actual); } + checked++; + } + EXEC SQL CLOSE cvb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_verify_batch COMMIT: bizdate=%s 검증=%ld 불일치=%ld\n", bizdate, checked, bad); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_yearend_batch.pgc b/app/src/cl/batch/cl_yearend_batch.pgc new file mode 100644 index 0000000..5291da0 --- /dev/null +++ b/app/src/cl/batch/cl_yearend_batch.pgc @@ -0,0 +1,40 @@ +/* + * cl_yearend_batch.pgc - cl 연말 마감 배치 (연도 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: cl_yearend_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cl_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_year[8]; + long 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; } + + long cid; + strncpy(h_year, bizdate, 4); h_year[4] = 0; + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE to_char(biz_date,'YYYY') = :h_year; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cid = cldb_next_close_id(); + if (cid < 0) { tpabort(0); return 1; } + if (cldb_insert_close(cid, "YEAREND", h_year, 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_yearend_batch COMMIT: year=%s cid=%ld 건수=%ld 총액=%ld\n", h_year, cid, h_cnt, h_amt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/cl_svr.pgc b/app/src/cl/cl_svr.pgc index 6868cab..d3b156f 100644 --- a/app/src/cl/cl_svr.pgc +++ b/app/src/cl/cl_svr.pgc @@ -1,10 +1,15 @@ /* - * cl_svr.pgc - cl (마감/closing) MODULE SERVER. + * cl_svr.pgc - cl (마감/closing) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of cl's 30 online services now lives in its own file + * (app/src/cl/svc/.pgc) with its own copybook header + * (app/src/cl/svc/.h); those objects are archived into libclsvc.a and + * linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all services from a {name, fn} table, + * - tpclose() at shutdown. * * 마감 도메인: 일/월/분기/연 마감 이력(close_log), 가맹점별 마감 스냅샷(snapshot), * 기간 잠금(period_lock). 마감 상태 O=개시 -> C=마감완료 -> A=승인 -> L=잠금, @@ -13,44 +18,43 @@ * 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 -#include -#include #include #include #include -#include -#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); } +/* every cl service's copybook header (declares its record struct + prototype) */ +#include "CL_DAILY.h" +#include "CL_MONTHLY.h" +#include "CL_QUARTER.h" +#include "CL_SNAPSHOT.h" +#include "CL_RECLOSE.h" +#include "CL_VERIFY.h" +#include "CL_CANCEL.h" +#include "CL_STATS.h" +#include "CL_BALVERIFY.h" +#include "CL_SEQ.h" +#include "CL_REPORT.h" +#include "CL_ALERT.h" +#include "CL_UNCLOSED.h" +#include "CL_APPROVE.h" +#include "CL_LOCK.h" +#include "CL_UNLOCK.h" +#include "CL_AGGREGATE.h" +#include "CL_DIFFCHK.h" +#include "CL_SUMMARY.h" +#include "CL_STATUS.h" +#include "CL_ROLLBACK.h" +#include "CL_CONFIRM.h" +#include "CL_PERIOD.h" +#include "CL_YEAREND.h" +#include "CL_MIDCLOSE.h" +#include "CL_RECON.h" +#include "CL_ARCHIVE.h" +#include "CL_PURGE.h" +#include "CL_RESTORE.h" +#include "CL_INQUIRY.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, @@ -93,558 +97,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/cl/dbio/cl_approve_pending_dbio.h b/app/src/cl/dbio/cl_approve_pending_dbio.h new file mode 100644 index 0000000..827a432 --- /dev/null +++ b/app/src/cl/dbio/cl_approve_pending_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_approve_pending_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_approve_pending_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 승인 대기(C) 마감 수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_APPROVE_PENDING_DBIO_H +#define CL_APPROVE_PENDING_DBIO_H + +long clqdb_approve_pending(void); +#endif /* CL_APPROVE_PENDING_DBIO_H */ diff --git a/app/src/cl/dbio/cl_approve_pending_dbio.pgc b/app/src/cl/dbio/cl_approve_pending_dbio.pgc new file mode 100644 index 0000000..1223f24 --- /dev/null +++ b/app/src/cl/dbio/cl_approve_pending_dbio.pgc @@ -0,0 +1,19 @@ +/* + * cl_approve_pending_dbio.pgc - cl 모듈 DB 접근 함수 (cl_approve_pending_dbio), archived into libcldbio.a. + * 승인 대기(C) 마감 수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_approve_pending_dbio.h" + +/* 마감완료(C) 이나 아직 승인(A)되지 않은 마감 수. */ +long clqdb_approve_pending(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM cl_close_log WHERE status = 'C'; + if (sqlca.sqlcode < 0) { userlog("clqdb_approve_pending FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_archive_mark_dbio.h b/app/src/cl/dbio/cl_archive_mark_dbio.h new file mode 100644 index 0000000..e3e9828 --- /dev/null +++ b/app/src/cl/dbio/cl_archive_mark_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_archive_mark_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_archive_mark_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 완료/승인 마감 아카이브 표시 (update count). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_ARCHIVE_MARK_DBIO_H +#define CL_ARCHIVE_MARK_DBIO_H + +long clqdb_archive_mark(const char *before); +#endif /* CL_ARCHIVE_MARK_DBIO_H */ diff --git a/app/src/cl/dbio/cl_archive_mark_dbio.pgc b/app/src/cl/dbio/cl_archive_mark_dbio.pgc new file mode 100644 index 0000000..61cb3e7 --- /dev/null +++ b/app/src/cl/dbio/cl_archive_mark_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_archive_mark_dbio.pgc - cl 모듈 DB 접근 함수 (cl_archive_mark_dbio), archived into libcldbio.a. + * 완료/승인 마감 아카이브 표시 (update count). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_archive_mark_dbio.h" + +/* before 이전 완료(C)/승인(A) 마감을 ARCHIVED 로 표시, 표시 건수 반환. */ +long clqdb_archive_mark(const char *before) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_before[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_before, before, sizeof(h_before)-1); h_before[sizeof(h_before)-1] = 0; + EXEC SQL UPDATE cl_close_log SET memo = 'ARCHIVED', updated_at = now() + WHERE biz_date < :h_before AND status IN ('C','A','L') AND memo <> 'ARCHIVED'; + if (sqlca.sqlcode < 0) { userlog("clqdb_archive_mark FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/cl/dbio/cl_cancel_purge_dbio.h b/app/src/cl/dbio/cl_cancel_purge_dbio.h new file mode 100644 index 0000000..bcb54f7 --- /dev/null +++ b/app/src/cl/dbio/cl_cancel_purge_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_cancel_purge_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_cancel_purge_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 오래된 취소(X) 마감 및 스냅샷 삭제. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_CANCEL_PURGE_DBIO_H +#define CL_CANCEL_PURGE_DBIO_H + +long clqdb_cancel_purge(const char *before); +#endif /* CL_CANCEL_PURGE_DBIO_H */ diff --git a/app/src/cl/dbio/cl_cancel_purge_dbio.pgc b/app/src/cl/dbio/cl_cancel_purge_dbio.pgc new file mode 100644 index 0000000..88624f6 --- /dev/null +++ b/app/src/cl/dbio/cl_cancel_purge_dbio.pgc @@ -0,0 +1,25 @@ +/* + * cl_cancel_purge_dbio.pgc - cl 모듈 DB 접근 함수 (cl_cancel_purge_dbio), archived into libcldbio.a. + * 오래된 취소(X) 마감 및 스냅샷 삭제. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_cancel_purge_dbio.h" + +/* before 이전 취소(X) 마감과 스냅샷 삭제, 삭제 마감 수 반환. */ +long clqdb_cancel_purge(const char *before) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_before[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + strncpy(h_before, before, sizeof(h_before)-1); h_before[sizeof(h_before)-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_before); + if (sqlca.sqlcode < 0) { userlog("clqdb_cancel_purge snap FAIL [%d]", sqlca.sqlcode); return -1; } + EXEC SQL DELETE FROM cl_close_log WHERE status = 'X' AND biz_date < :h_before; + if (sqlca.sqlcode < 0) { userlog("clqdb_cancel_purge log FAIL [%d]", sqlca.sqlcode); return -1; } + h_cnt = sqlca.sqlerrd[2]; + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_close_count_dbio.h b/app/src/cl/dbio/cl_close_count_dbio.h new file mode 100644 index 0000000..e741a9b --- /dev/null +++ b/app/src/cl/dbio/cl_close_count_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_close_count_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_close_count_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감유형별 유효 마감 건수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_CLOSE_COUNT_DBIO_H +#define CL_CLOSE_COUNT_DBIO_H + +long clqdb_close_count(const char *ctype); +#endif /* CL_CLOSE_COUNT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_close_count_dbio.pgc b/app/src/cl/dbio/cl_close_count_dbio.pgc new file mode 100644 index 0000000..7e015af --- /dev/null +++ b/app/src/cl/dbio/cl_close_count_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_close_count_dbio.pgc - cl 모듈 DB 접근 함수 (cl_close_count_dbio), archived into libcldbio.a. + * 마감유형별 유효 마감 건수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_close_count_dbio.h" + +/* close_type 별 취소되지 않은 마감 수. */ +long clqdb_close_count(const char *ctype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM cl_close_log + WHERE close_type = :h_type AND status <> 'X'; + if (sqlca.sqlcode < 0) { userlog("clqdb_close_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_close_total_dbio.h b/app/src/cl/dbio/cl_close_total_dbio.h new file mode 100644 index 0000000..410d7fd --- /dev/null +++ b/app/src/cl/dbio/cl_close_total_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_close_total_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_close_total_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 헤더 총액/건수 조회. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_CLOSE_TOTAL_DBIO_H +#define CL_CLOSE_TOTAL_DBIO_H + +int clqdb_close_total(long cid, long *cnt, long *amt); +#endif /* CL_CLOSE_TOTAL_DBIO_H */ diff --git a/app/src/cl/dbio/cl_close_total_dbio.pgc b/app/src/cl/dbio/cl_close_total_dbio.pgc new file mode 100644 index 0000000..71faa29 --- /dev/null +++ b/app/src/cl/dbio/cl_close_total_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_close_total_dbio.pgc - cl 모듈 DB 접근 함수 (cl_close_total_dbio), archived into libcldbio.a. + * 마감 헤더 총액/건수 조회. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_close_total_dbio.h" + +/* 마감 헤더의 저장된 txn_count/total_amount. */ +int clqdb_close_total(long cid, long *cnt, long *amt) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT txn_count, total_amount INTO :h_cnt, :h_amt + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("clqdb_close_total miss [%d]", sqlca.sqlcode); return -1; } + *cnt = h_cnt; *amt = h_amt; + return 0; +} diff --git a/app/src/cl/dbio/cl_daily_gap_dbio.h b/app/src/cl/dbio/cl_daily_gap_dbio.h new file mode 100644 index 0000000..f9d7957 --- /dev/null +++ b/app/src/cl/dbio/cl_daily_gap_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_daily_gap_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_daily_gap_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 일마감 저장건수 vs 실제 매입건수 차이. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_DAILY_GAP_DBIO_H +#define CL_DAILY_GAP_DBIO_H + +int clqdb_daily_gap(long cid, const char *bizdate, long *stored, long *actual); +#endif /* CL_DAILY_GAP_DBIO_H */ diff --git a/app/src/cl/dbio/cl_daily_gap_dbio.pgc b/app/src/cl/dbio/cl_daily_gap_dbio.pgc new file mode 100644 index 0000000..330a4d6 --- /dev/null +++ b/app/src/cl/dbio/cl_daily_gap_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cl_daily_gap_dbio.pgc - cl 모듈 DB 접근 함수 (cl_daily_gap_dbio), archived into libcldbio.a. + * 일마감 저장건수 vs 실제 매입건수 차이. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_daily_gap_dbio.h" + +/* close_log txn_count 와 실제 매입 건수 대사. */ +int clqdb_daily_gap(long cid, const char *bizdate, long *stored, long *actual) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_stored, h_actual; + 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 txn_count INTO :h_stored FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1; + EXEC SQL SELECT count(*) INTO :h_actual FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + *stored = h_stored; *actual = h_actual; + return 0; +} diff --git a/app/src/cl/dbio/cl_dbio.h b/app/src/cl/dbio/cl_dbio.h index b8c9729..c39a97f 100644 --- a/app/src/cl/dbio/cl_dbio.h +++ b/app/src/cl/dbio/cl_dbio.h @@ -25,4 +25,9 @@ int cldb_insert_snapshot(long sid, long cid, const char *period, const char *bi /* 마감 순번 관리 (seq registry) */ long cldb_bump_seq(const char *ctype); +/* 일/월/분기/연 공용: 매입 집계(count/sum) 후 마감이력(status=C) 생성. + * cnt_out/amt_out 로 집계값 반환. DAILY 는 당일, 그 외는 period 프리픽스 집계. */ +int cldb_open_close(const char *ctype, const char *period, const char *bizdate, + long *cid_out, long *cnt_out, long *amt_out); + #endif /* CL_DBIO_H */ diff --git a/app/src/cl/dbio/cl_lock_walk_dbio.h b/app/src/cl/dbio/cl_lock_walk_dbio.h new file mode 100644 index 0000000..a37e41d --- /dev/null +++ b/app/src/cl/dbio/cl_lock_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_lock_walk_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_lock_walk_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 기간 커서 순회 - 잠긴 기간 수 누적. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_LOCK_WALK_DBIO_H +#define CL_LOCK_WALK_DBIO_H + +long clqdb_lock_walk(void); +#endif /* CL_LOCK_WALK_DBIO_H */ diff --git a/app/src/cl/dbio/cl_lock_walk_dbio.pgc b/app/src/cl/dbio/cl_lock_walk_dbio.pgc new file mode 100644 index 0000000..1d4232f --- /dev/null +++ b/app/src/cl/dbio/cl_lock_walk_dbio.pgc @@ -0,0 +1,30 @@ +/* + * cl_lock_walk_dbio.pgc - cl 모듈 DB 접근 함수 (cl_lock_walk_dbio), archived into libcldbio.a. + * 기간 커서 순회 - 잠긴 기간 수 누적. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_lock_walk_dbio.h" + +/* period_lock 을 커서로 훑어 잠긴(locked) 기간 수 누적. */ +long clqdb_lock_walk(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_period[16]; + int h_locked; + EXEC SQL END DECLARE SECTION; + long locked = 0; + EXEC SQL DECLARE clw CURSOR FOR + SELECT period_key, CASE WHEN locked THEN 1 ELSE 0 END FROM cl_period_lock; + EXEC SQL OPEN clw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH clw INTO :h_period, :h_locked; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clw; return -1; } + if (h_locked) locked++; + } + EXEC SQL CLOSE clw; + return locked; +} diff --git a/app/src/cl/dbio/cl_max_close_dbio.h b/app/src/cl/dbio/cl_max_close_dbio.h new file mode 100644 index 0000000..54eaeeb --- /dev/null +++ b/app/src/cl/dbio/cl_max_close_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_max_close_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_max_close_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 당일 최대 마감 ID. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_MAX_CLOSE_DBIO_H +#define CL_MAX_CLOSE_DBIO_H + +long clqdb_max_close(const char *bizdate); +#endif /* CL_MAX_CLOSE_DBIO_H */ diff --git a/app/src/cl/dbio/cl_max_close_dbio.pgc b/app/src/cl/dbio/cl_max_close_dbio.pgc new file mode 100644 index 0000000..2a5bc80 --- /dev/null +++ b/app/src/cl/dbio/cl_max_close_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_max_close_dbio.pgc - cl 모듈 DB 접근 함수 (cl_max_close_dbio), archived into libcldbio.a. + * 당일 최대 마감 ID. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_max_close_dbio.h" + +/* 당일 채번된 최대 마감 ID (없으면 0). */ +long clqdb_max_close(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(close_id), 0) INTO :h_max + FROM cl_close_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("clqdb_max_close FAIL [%d]", sqlca.sqlcode); return -1; } + return h_max; +} diff --git a/app/src/cl/dbio/cl_month_walk_dbio.h b/app/src/cl/dbio/cl_month_walk_dbio.h new file mode 100644 index 0000000..86f6e29 --- /dev/null +++ b/app/src/cl/dbio/cl_month_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_month_walk_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_month_walk_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 월별 마감 커서 순회 - 마감 총액 누적. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_MONTH_WALK_DBIO_H +#define CL_MONTH_WALK_DBIO_H + +long clqdb_month_walk(const char *prefix); +#endif /* CL_MONTH_WALK_DBIO_H */ diff --git a/app/src/cl/dbio/cl_month_walk_dbio.pgc b/app/src/cl/dbio/cl_month_walk_dbio.pgc new file mode 100644 index 0000000..6f6f9d6 --- /dev/null +++ b/app/src/cl/dbio/cl_month_walk_dbio.pgc @@ -0,0 +1,33 @@ +/* + * cl_month_walk_dbio.pgc - cl 모듈 DB 접근 함수 (cl_month_walk_dbio), archived into libcldbio.a. + * 월별 마감 커서 순회 - 마감 총액 누적. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_month_walk_dbio.h" + +/* 프리픽스에 맞는 월 마감을 커서로 훑어 총액 누적. */ +long clqdb_month_walk(const char *prefix) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16], h_period[16]; + long h_amt; + EXEC SQL END DECLARE SECTION; + long total = 0; + strncpy(h_prefix, prefix, sizeof(h_prefix)-1); h_prefix[sizeof(h_prefix)-1] = 0; + EXEC SQL DECLARE cmw CURSOR FOR + SELECT period_key, total_amount FROM cl_close_log + WHERE close_type = 'MONTHLY' AND period_key LIKE :h_prefix || '%' AND status <> 'X' + ORDER BY period_key; + EXEC SQL OPEN cmw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH cmw INTO :h_period, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cmw; return -1; } + total += h_amt; + } + EXEC SQL CLOSE cmw; + return total; +} diff --git a/app/src/cl/dbio/cl_open_count_dbio.h b/app/src/cl/dbio/cl_open_count_dbio.h new file mode 100644 index 0000000..1b23448 --- /dev/null +++ b/app/src/cl/dbio/cl_open_count_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_open_count_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_open_count_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 개시(O) 상태 미완료 마감 수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_OPEN_COUNT_DBIO_H +#define CL_OPEN_COUNT_DBIO_H + +long clqdb_open_count(void); +#endif /* CL_OPEN_COUNT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_open_count_dbio.pgc b/app/src/cl/dbio/cl_open_count_dbio.pgc new file mode 100644 index 0000000..948adb1 --- /dev/null +++ b/app/src/cl/dbio/cl_open_count_dbio.pgc @@ -0,0 +1,19 @@ +/* + * cl_open_count_dbio.pgc - cl 모듈 DB 접근 함수 (cl_open_count_dbio), archived into libcldbio.a. + * 개시(O) 상태 미완료 마감 수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_open_count_dbio.h" + +/* 아직 마감완료되지 않은 개시(O) 상태 마감 수. */ +long clqdb_open_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM cl_close_log WHERE status = 'O'; + if (sqlca.sqlcode < 0) { userlog("clqdb_open_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_openclose_dbio.pgc b/app/src/cl/dbio/cl_openclose_dbio.pgc new file mode 100644 index 0000000..db9f271 --- /dev/null +++ b/app/src/cl/dbio/cl_openclose_dbio.pgc @@ -0,0 +1,42 @@ +/* + * cl_openclose_dbio.pgc - 일/월/분기/연 공용 마감 집계+이력생성 (part of libcldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * CL_DAILY/MONTHLY/QUARTER/YEAREND 서비스가 공유한다. (예전 cl_svr 인라인 + * static cl_open_close 를 dbio 계층으로 이전; EXEC SQL 은 .pgc 에서만 허용됨.) + */ +#include +#include +#include "cl_dbio.h" + +int cldb_open_close(const char *ctype, const char *period, const char *bizdate, + long *cid_out, long *cnt_out, long *amt_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) { + userlog("cldb_open_close agg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + 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; + *cid_out = h_cid; + *cnt_out = h_cnt; + *amt_out = h_amt; + return 0; +} diff --git a/app/src/cl/dbio/cl_period_locked_dbio.h b/app/src/cl/dbio/cl_period_locked_dbio.h new file mode 100644 index 0000000..aaa2226 --- /dev/null +++ b/app/src/cl/dbio/cl_period_locked_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_period_locked_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_period_locked_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 기간 잠금 여부 조회 (1=잠김). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_PERIOD_LOCKED_DBIO_H +#define CL_PERIOD_LOCKED_DBIO_H + +int clqdb_period_locked(const char *period); +#endif /* CL_PERIOD_LOCKED_DBIO_H */ diff --git a/app/src/cl/dbio/cl_period_locked_dbio.pgc b/app/src/cl/dbio/cl_period_locked_dbio.pgc new file mode 100644 index 0000000..69c49a0 --- /dev/null +++ b/app/src/cl/dbio/cl_period_locked_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cl_period_locked_dbio.pgc - cl 모듈 DB 접근 함수 (cl_period_locked_dbio), archived into libcldbio.a. + * 기간 잠금 여부 조회 (1=잠김). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_period_locked_dbio.h" + +/* period_key 가 잠겨있으면 1, 아니면 0, 없으면 0. */ +int clqdb_period_locked(const char *period) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_period[16]; + int h_locked; + EXEC SQL END DECLARE SECTION; + strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0; + EXEC SQL SELECT CASE WHEN locked THEN 1 ELSE 0 END INTO :h_locked + FROM cl_period_lock WHERE period_key = :h_period; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("clqdb_period_locked FAIL [%d]", sqlca.sqlcode); return -1; } + return h_locked; +} diff --git a/app/src/cl/dbio/cl_period_sum_dbio.h b/app/src/cl/dbio/cl_period_sum_dbio.h new file mode 100644 index 0000000..60411fb --- /dev/null +++ b/app/src/cl/dbio/cl_period_sum_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_period_sum_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_period_sum_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 기간 프리픽스 마감 총액 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_PERIOD_SUM_DBIO_H +#define CL_PERIOD_SUM_DBIO_H + +long clqdb_period_sum(const char *prefix); +#endif /* CL_PERIOD_SUM_DBIO_H */ diff --git a/app/src/cl/dbio/cl_period_sum_dbio.pgc b/app/src/cl/dbio/cl_period_sum_dbio.pgc new file mode 100644 index 0000000..282941f --- /dev/null +++ b/app/src/cl/dbio/cl_period_sum_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_period_sum_dbio.pgc - cl 모듈 DB 접근 함수 (cl_period_sum_dbio), archived into libcldbio.a. + * 기간 프리픽스 마감 총액 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_period_sum_dbio.h" + +/* period_key 프리픽스로 마감 total_amount 누계. */ +long clqdb_period_sum(const char *prefix) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16]; + long h_amt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_prefix, prefix, sizeof(h_prefix)-1); h_prefix[sizeof(h_prefix)-1] = 0; + EXEC SQL SELECT coalesce(sum(total_amount),0) INTO :h_amt FROM cl_close_log + WHERE period_key LIKE :h_prefix || '%' AND status <> 'X'; + if (sqlca.sqlcode < 0) { userlog("clqdb_period_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_amt; +} diff --git a/app/src/cl/dbio/cl_reclose_count_dbio.h b/app/src/cl/dbio/cl_reclose_count_dbio.h new file mode 100644 index 0000000..8a047d7 --- /dev/null +++ b/app/src/cl/dbio/cl_reclose_count_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_reclose_count_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_reclose_count_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 재마감(R) 상태 마감 수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_RECLOSE_COUNT_DBIO_H +#define CL_RECLOSE_COUNT_DBIO_H + +long clqdb_reclose_count(const char *ctype); +#endif /* CL_RECLOSE_COUNT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_reclose_count_dbio.pgc b/app/src/cl/dbio/cl_reclose_count_dbio.pgc new file mode 100644 index 0000000..4d571cd --- /dev/null +++ b/app/src/cl/dbio/cl_reclose_count_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_reclose_count_dbio.pgc - cl 모듈 DB 접근 함수 (cl_reclose_count_dbio), archived into libcldbio.a. + * 재마감(R) 상태 마감 수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_reclose_count_dbio.h" + +/* close_type 별 재마감(R) 상태 수. */ +long clqdb_reclose_count(const char *ctype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM cl_close_log + WHERE close_type = :h_type AND status = 'R'; + if (sqlca.sqlcode < 0) { userlog("clqdb_reclose_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_restore_count_dbio.h b/app/src/cl/dbio/cl_restore_count_dbio.h new file mode 100644 index 0000000..620851c --- /dev/null +++ b/app/src/cl/dbio/cl_restore_count_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_restore_count_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_restore_count_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 취소(X) 마감 개시(O) 복원 (update count). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_RESTORE_COUNT_DBIO_H +#define CL_RESTORE_COUNT_DBIO_H + +long clqdb_restore_count(const char *bizdate); +#endif /* CL_RESTORE_COUNT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_restore_count_dbio.pgc b/app/src/cl/dbio/cl_restore_count_dbio.pgc new file mode 100644 index 0000000..e6f4721 --- /dev/null +++ b/app/src/cl/dbio/cl_restore_count_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_restore_count_dbio.pgc - cl 모듈 DB 접근 함수 (cl_restore_count_dbio), archived into libcldbio.a. + * 취소(X) 마감 개시(O) 복원 (update count). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_restore_count_dbio.h" + +/* 당일 취소(X) 마감을 개시(O) 로 복원, 복원 건수 반환. */ +long clqdb_restore_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE cl_close_log SET status = 'O', memo = 'RESTORED', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'X'; + if (sqlca.sqlcode < 0) { userlog("clqdb_restore_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/cl/dbio/cl_seq_last_dbio.h b/app/src/cl/dbio/cl_seq_last_dbio.h new file mode 100644 index 0000000..e6188b0 --- /dev/null +++ b/app/src/cl/dbio/cl_seq_last_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_seq_last_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_seq_last_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감유형 마지막 순번 조회. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SEQ_LAST_DBIO_H +#define CL_SEQ_LAST_DBIO_H + +long clqdb_seq_last(const char *ctype); +#endif /* CL_SEQ_LAST_DBIO_H */ diff --git a/app/src/cl/dbio/cl_seq_last_dbio.pgc b/app/src/cl/dbio/cl_seq_last_dbio.pgc new file mode 100644 index 0000000..044a784 --- /dev/null +++ b/app/src/cl/dbio/cl_seq_last_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_seq_last_dbio.pgc - cl 모듈 DB 접근 함수 (cl_seq_last_dbio), archived into libcldbio.a. + * 마감유형 마지막 순번 조회. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_seq_last_dbio.h" + +/* close_type 의 마지막 발번 순번(없으면 0). */ +long clqdb_seq_last(const char *ctype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_seq = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT coalesce(last_seq,0) INTO :h_seq FROM cl_seq_reg WHERE close_type = :h_type; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("clqdb_seq_last FAIL [%d]", sqlca.sqlcode); return -1; } + return h_seq; +} diff --git a/app/src/cl/dbio/cl_snap_fee_dbio.h b/app/src/cl/dbio/cl_snap_fee_dbio.h new file mode 100644 index 0000000..e59648d --- /dev/null +++ b/app/src/cl/dbio/cl_snap_fee_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_snap_fee_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_fee_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 스냅샷 수수료(fee) 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_FEE_DBIO_H +#define CL_SNAP_FEE_DBIO_H + +long clqdb_snap_fee(long cid); +#endif /* CL_SNAP_FEE_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_fee_dbio.pgc b/app/src/cl/dbio/cl_snap_fee_dbio.pgc new file mode 100644 index 0000000..1d09664 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_fee_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cl_snap_fee_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_fee_dbio), archived into libcldbio.a. + * 마감 스냅샷 수수료(fee) 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_fee_dbio.h" + +/* 특정 마감 스냅샷의 fee 합. */ +long clqdb_snap_fee(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_fee = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(fee_amount),0) INTO :h_fee + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) { userlog("clqdb_snap_fee FAIL [%d]", sqlca.sqlcode); return -1; } + return h_fee; +} diff --git a/app/src/cl/dbio/cl_snap_gross_dbio.h b/app/src/cl/dbio/cl_snap_gross_dbio.h new file mode 100644 index 0000000..2f080b0 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_gross_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_snap_gross_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_gross_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 스냅샷 총매출(gross) 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_GROSS_DBIO_H +#define CL_SNAP_GROSS_DBIO_H + +long clqdb_snap_gross(long cid); +#endif /* CL_SNAP_GROSS_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_gross_dbio.pgc b/app/src/cl/dbio/cl_snap_gross_dbio.pgc new file mode 100644 index 0000000..28b26c5 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_gross_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cl_snap_gross_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_gross_dbio), archived into libcldbio.a. + * 마감 스냅샷 총매출(gross) 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_gross_dbio.h" + +/* 특정 마감 스냅샷의 gross 합. */ +long clqdb_snap_gross(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_gross = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(gross_amount),0) INTO :h_gross + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) { userlog("clqdb_snap_gross FAIL [%d]", sqlca.sqlcode); return -1; } + return h_gross; +} diff --git a/app/src/cl/dbio/cl_snap_merch_dbio.h b/app/src/cl/dbio/cl_snap_merch_dbio.h new file mode 100644 index 0000000..3c6e5f9 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_merch_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_snap_merch_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_merch_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 스냅샷 가맹점 수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_MERCH_DBIO_H +#define CL_SNAP_MERCH_DBIO_H + +long clqdb_snap_merch(long cid); +#endif /* CL_SNAP_MERCH_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_merch_dbio.pgc b/app/src/cl/dbio/cl_snap_merch_dbio.pgc new file mode 100644 index 0000000..a0286ee --- /dev/null +++ b/app/src/cl/dbio/cl_snap_merch_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cl_snap_merch_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_merch_dbio), archived into libcldbio.a. + * 마감 스냅샷 가맹점 수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_merch_dbio.h" + +/* 특정 마감에 포함된 가맹점(스냅샷 행) 수. */ +long clqdb_snap_merch(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(DISTINCT merchant_id) INTO :h_cnt + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) { userlog("clqdb_snap_merch FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_snap_net_dbio.h b/app/src/cl/dbio/cl_snap_net_dbio.h new file mode 100644 index 0000000..0355032 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_net_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_snap_net_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_net_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 스냅샷 순매출(net) 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_NET_DBIO_H +#define CL_SNAP_NET_DBIO_H + +long clqdb_snap_net(long cid); +#endif /* CL_SNAP_NET_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_net_dbio.pgc b/app/src/cl/dbio/cl_snap_net_dbio.pgc new file mode 100644 index 0000000..313cbe6 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_net_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_snap_net_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_net_dbio), archived into libcldbio.a. + * 마감 스냅샷 순매출(net) 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_net_dbio.h" + +/* 특정 마감 스냅샷의 net 합. */ +long clqdb_snap_net(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_net = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_net + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) { userlog("clqdb_snap_net FAIL [%d]", sqlca.sqlcode); return -1; } + if (h_net < 0) return -1; + return h_net; +} diff --git a/app/src/cl/dbio/cl_snap_top_dbio.h b/app/src/cl/dbio/cl_snap_top_dbio.h new file mode 100644 index 0000000..8286e52 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_top_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_snap_top_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_top_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 내 최대 매출 가맹점. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_TOP_DBIO_H +#define CL_SNAP_TOP_DBIO_H + +int clqdb_snap_top(long cid, char *merch_out, long *gross_out); +#endif /* CL_SNAP_TOP_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_top_dbio.pgc b/app/src/cl/dbio/cl_snap_top_dbio.pgc new file mode 100644 index 0000000..bfd831d --- /dev/null +++ b/app/src/cl/dbio/cl_snap_top_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cl_snap_top_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_top_dbio), archived into libcldbio.a. + * 마감 내 최대 매출 가맹점. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_top_dbio.h" + +/* 스냅샷에서 gross 최대 가맹점 (order by limit 1). */ +int clqdb_snap_top(long cid, char *merch_out, long *gross_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_gross; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT merchant_id, gross_amount INTO :h_merch, :h_gross + FROM cl_snapshot WHERE close_id = :h_cid ORDER BY gross_amount DESC LIMIT 1; + if (sqlca.sqlcode == 100) { merch_out[0] = 0; *gross_out = 0; return 0; } + if (sqlca.sqlcode < 0) return -1; + strcpy(merch_out, h_merch); *gross_out = h_gross; + return 0; +} diff --git a/app/src/cl/dbio/cl_status_walk_dbio.h b/app/src/cl/dbio/cl_status_walk_dbio.h new file mode 100644 index 0000000..aa01bd7 --- /dev/null +++ b/app/src/cl/dbio/cl_status_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_status_walk_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_status_walk_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 커서 순회 - 특정 상태 마감 수 누적. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_STATUS_WALK_DBIO_H +#define CL_STATUS_WALK_DBIO_H + +long clqdb_status_walk(const char *ctype, const char *want); +#endif /* CL_STATUS_WALK_DBIO_H */ diff --git a/app/src/cl/dbio/cl_status_walk_dbio.pgc b/app/src/cl/dbio/cl_status_walk_dbio.pgc new file mode 100644 index 0000000..a6c1a55 --- /dev/null +++ b/app/src/cl/dbio/cl_status_walk_dbio.pgc @@ -0,0 +1,31 @@ +/* + * cl_status_walk_dbio.pgc - cl 모듈 DB 접근 함수 (cl_status_walk_dbio), archived into libcldbio.a. + * 마감 커서 순회 - 특정 상태 마감 수 누적. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_status_walk_dbio.h" + +/* 커서로 유형별 마감을 훑어 want 상태 건수를 누적. */ +long clqdb_status_walk(const char *ctype, const char *want) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16], h_st[8]; + EXEC SQL END DECLARE SECTION; + long hit = 0; + char want0 = want[0]; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL DECLARE csw CURSOR FOR + SELECT status FROM cl_close_log WHERE close_type = :h_type ORDER BY close_id; + EXEC SQL OPEN csw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH csw INTO :h_st; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE csw; return -1; } + if (h_st[0] == want0) hit++; + } + EXEC SQL CLOSE csw; + return hit; +} diff --git a/app/src/cl/dbio/cl_unclosed_days_dbio.h b/app/src/cl/dbio/cl_unclosed_days_dbio.h new file mode 100644 index 0000000..824a235 --- /dev/null +++ b/app/src/cl/dbio/cl_unclosed_days_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_unclosed_days_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_unclosed_days_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 일마감 누락 영업일 수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_UNCLOSED_DAYS_DBIO_H +#define CL_UNCLOSED_DAYS_DBIO_H + +long clqdb_unclosed_days(void); +#endif /* CL_UNCLOSED_DAYS_DBIO_H */ diff --git a/app/src/cl/dbio/cl_unclosed_days_dbio.pgc b/app/src/cl/dbio/cl_unclosed_days_dbio.pgc new file mode 100644 index 0000000..e356811 --- /dev/null +++ b/app/src/cl/dbio/cl_unclosed_days_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_unclosed_days_dbio.pgc - cl 모듈 DB 접근 함수 (cl_unclosed_days_dbio), archived into libcldbio.a. + * 일마감 누락 영업일 수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_unclosed_days_dbio.h" + +/* DAILY 마감이 없는 매입 영업일 수. */ +long clqdb_unclosed_days(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0; + 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) { userlog("clqdb_unclosed_days FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/cl/dbio/cl_verify_amt_dbio.h b/app/src/cl/dbio/cl_verify_amt_dbio.h new file mode 100644 index 0000000..7aef2f0 --- /dev/null +++ b/app/src/cl/dbio/cl_verify_amt_dbio.h @@ -0,0 +1,11 @@ +/* + * cl_verify_amt_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_verify_amt_dbio, part of libcldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 마감 저장총액 vs 실제 매입총액 검증. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_VERIFY_AMT_DBIO_H +#define CL_VERIFY_AMT_DBIO_H + +int clqdb_verify_amt(long cid, const char *bizdate, long *stored, long *actual); +#endif /* CL_VERIFY_AMT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_verify_amt_dbio.pgc b/app/src/cl/dbio/cl_verify_amt_dbio.pgc new file mode 100644 index 0000000..7f01567 --- /dev/null +++ b/app/src/cl/dbio/cl_verify_amt_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cl_verify_amt_dbio.pgc - cl 모듈 DB 접근 함수 (cl_verify_amt_dbio), archived into libcldbio.a. + * 마감 저장총액 vs 실제 매입총액 검증. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_verify_amt_dbio.h" + +/* close_log total_amount 와 실제 매입 총액 대사. */ +int clqdb_verify_amt(long cid, const char *bizdate, long *stored, long *actual) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_stored, h_actual; + 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 total_amount INTO :h_stored FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_actual FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + *stored = h_stored; *actual = h_actual; + return 0; +} diff --git a/app/src/cl/run/run_approve.sh b/app/src/cl/run/run_approve.sh new file mode 100755 index 0000000..309493c --- /dev/null +++ b/app/src/cl/run/run_approve.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_approve.sh - 마감 일괄 승인 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_approve.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_approve.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_approve_batch "$BIZDATE" diff --git a/app/src/cl/run/run_archive.sh b/app/src/cl/run/run_archive.sh new file mode 100755 index 0000000..c5451b4 --- /dev/null +++ b/app/src/cl/run/run_archive.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_archive.sh - 완료마감 아카이브 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_archive.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_archive.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_archive_batch "$BIZDATE" diff --git a/app/src/cl/run/run_close.sh b/app/src/cl/run/run_close.sh new file mode 100755 index 0000000..a1d2648 --- /dev/null +++ b/app/src/cl/run/run_close.sh @@ -0,0 +1,19 @@ +#!/bin/bash +## +## run_close.sh - cl 일마감 파이프라인 (여러 배치 일괄 실행). +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치들을 순차 실행. +## Usage: run_close.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_close.sh] BIZDATE=$BIZDATE" +for b in cl_daily_batch \ + cl_snapshot_batch \ + cl_rollup_batch \ + cl_verify_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/cl/run/run_daily.sh b/app/src/cl/run/run_daily.sh new file mode 100755 index 0000000..a487184 --- /dev/null +++ b/app/src/cl/run/run_daily.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_daily.sh - 일 마감 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_daily.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_daily.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_daily_batch "$BIZDATE" diff --git a/app/src/cl/run/run_eom.sh b/app/src/cl/run/run_eom.sh new file mode 100755 index 0000000..6eb995f --- /dev/null +++ b/app/src/cl/run/run_eom.sh @@ -0,0 +1,21 @@ +#!/bin/bash +## +## run_eom.sh - cl 월마감 파이프라인 (여러 배치 일괄 실행). +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치들을 순차 실행. +## Usage: run_eom.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_eom.sh] BIZDATE=$BIZDATE" +for b in cl_monthly_batch \ + cl_snapshot_batch \ + cl_rollup_batch \ + cl_approve_batch \ + cl_lock_batch \ + cl_archive_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/cl/run/run_lock.sh b/app/src/cl/run/run_lock.sh new file mode 100755 index 0000000..7a8e410 --- /dev/null +++ b/app/src/cl/run/run_lock.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_lock.sh - 기간 잠금 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_lock.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_lock.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_lock_batch "$BIZDATE" diff --git a/app/src/cl/run/run_midclose.sh b/app/src/cl/run/run_midclose.sh new file mode 100755 index 0000000..26e09ac --- /dev/null +++ b/app/src/cl/run/run_midclose.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_midclose.sh - 중간 마감 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_midclose.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_midclose.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_midclose_batch "$BIZDATE" diff --git a/app/src/cl/run/run_monthly.sh b/app/src/cl/run/run_monthly.sh new file mode 100755 index 0000000..37739d6 --- /dev/null +++ b/app/src/cl/run/run_monthly.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_monthly.sh - 월 마감 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_monthly.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_monthly.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_monthly_batch "$BIZDATE" diff --git a/app/src/cl/run/run_purge.sh b/app/src/cl/run/run_purge.sh new file mode 100755 index 0000000..bcf365c --- /dev/null +++ b/app/src/cl/run/run_purge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_purge.sh - 취소마감 정리 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_purge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_purge.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_purge_batch "$BIZDATE" diff --git a/app/src/cl/run/run_quarter.sh b/app/src/cl/run/run_quarter.sh new file mode 100755 index 0000000..0a90b2f --- /dev/null +++ b/app/src/cl/run/run_quarter.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_quarter.sh - 분기 마감 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_quarter.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_quarter.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_quarter_batch "$BIZDATE" diff --git a/app/src/cl/run/run_recon.sh b/app/src/cl/run/run_recon.sh new file mode 100755 index 0000000..c723ea1 --- /dev/null +++ b/app/src/cl/run/run_recon.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_recon.sh - 마감 대사 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_recon.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_recon.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_recon_batch "$BIZDATE" diff --git a/app/src/cl/run/run_report.sh b/app/src/cl/run/run_report.sh new file mode 100755 index 0000000..c15cbb6 --- /dev/null +++ b/app/src/cl/run/run_report.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_report.sh - 마감 리포트 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_report.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_report.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_report_batch "$BIZDATE" diff --git a/app/src/cl/run/run_seq.sh b/app/src/cl/run/run_seq.sh new file mode 100755 index 0000000..af897be --- /dev/null +++ b/app/src/cl/run/run_seq.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_seq.sh - 마감 순번 발번 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_seq.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_seq.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_seq_batch "$BIZDATE" diff --git a/app/src/cl/run/run_snapshot.sh b/app/src/cl/run/run_snapshot.sh new file mode 100755 index 0000000..9c521d5 --- /dev/null +++ b/app/src/cl/run/run_snapshot.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_snapshot.sh - 마감 스냅샷 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_snapshot.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_snapshot.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_snapshot_batch "$BIZDATE" diff --git a/app/src/cl/run/run_unclosed.sh b/app/src/cl/run/run_unclosed.sh new file mode 100755 index 0000000..a09e02f --- /dev/null +++ b/app/src/cl/run/run_unclosed.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_unclosed.sh - 미마감 영업일 점검 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_unclosed.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_unclosed.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_unclosed_batch "$BIZDATE" diff --git a/app/src/cl/run/run_verify.sh b/app/src/cl/run/run_verify.sh new file mode 100755 index 0000000..a48f63a --- /dev/null +++ b/app/src/cl/run/run_verify.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_verify.sh - 마감 검증 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_verify.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_verify.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_verify_batch "$BIZDATE" diff --git a/app/src/cl/run/run_yearend.sh b/app/src/cl/run/run_yearend.sh new file mode 100755 index 0000000..791836e --- /dev/null +++ b/app/src/cl/run/run_yearend.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_yearend.sh - 연말 마감 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_yearend.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_yearend.sh] BIZDATE=$BIZDATE" +exec /app/bin/cl_yearend_batch "$BIZDATE" diff --git a/app/src/cl/svc/CL_AGGREGATE.h b/app/src/cl/svc/CL_AGGREGATE.h new file mode 100644 index 0000000..8f7bf83 --- /dev/null +++ b/app/src/cl/svc/CL_AGGREGATE.h @@ -0,0 +1,21 @@ +/* + * CL_AGGREGATE.h - cl 서비스 CL_AGGREGATE 카피북 (copybook / record header). + * 마감 총계 재집계 후 close_log 반영. + */ +#ifndef CL_AGGREGATE_H +#define CL_AGGREGATE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_aggregate_rec_t; + +void CL_AGGREGATE(TPSVCINFO *p); + +#endif /* CL_AGGREGATE_H */ diff --git a/app/src/cl/svc/CL_AGGREGATE.pgc b/app/src/cl/svc/CL_AGGREGATE.pgc new file mode 100644 index 0000000..f736a2c --- /dev/null +++ b/app/src/cl/svc/CL_AGGREGATE.pgc @@ -0,0 +1,29 @@ +/* + * CL_AGGREGATE.pgc - cl 모듈 서비스 CL_AGGREGATE (one service = one file). + * 마감 총계 재집계 후 close_log 반영. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_AGGREGATE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_ALERT.h b/app/src/cl/svc/CL_ALERT.h new file mode 100644 index 0000000..8f35716 --- /dev/null +++ b/app/src/cl/svc/CL_ALERT.h @@ -0,0 +1,21 @@ +/* + * CL_ALERT.h - cl 서비스 CL_ALERT 카피북 (copybook / record header). + * 미마감 경보: 당일 대사완료(M) 미전기 매입 건수. + */ +#ifndef CL_ALERT_H +#define CL_ALERT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_alert_rec_t; + +void CL_ALERT(TPSVCINFO *p); + +#endif /* CL_ALERT_H */ diff --git a/app/src/cl/svc/CL_ALERT.pgc b/app/src/cl/svc/CL_ALERT.pgc new file mode 100644 index 0000000..7cabbd6 --- /dev/null +++ b/app/src/cl/svc/CL_ALERT.pgc @@ -0,0 +1,28 @@ +/* + * CL_ALERT.pgc - cl 모듈 서비스 CL_ALERT (one service = one file). + * 미마감 경보: 당일 대사완료(M) 미전기 매입 건수. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_ALERT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_APPROVE.h b/app/src/cl/svc/CL_APPROVE.h new file mode 100644 index 0000000..725db2d --- /dev/null +++ b/app/src/cl/svc/CL_APPROVE.h @@ -0,0 +1,21 @@ +/* + * CL_APPROVE.h - cl 서비스 CL_APPROVE 카피북 (copybook / record header). + * 마감 승인: 상태 A. + */ +#ifndef CL_APPROVE_H +#define CL_APPROVE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_approve_rec_t; + +void CL_APPROVE(TPSVCINFO *p); + +#endif /* CL_APPROVE_H */ diff --git a/app/src/cl/svc/CL_APPROVE.pgc b/app/src/cl/svc/CL_APPROVE.pgc new file mode 100644 index 0000000..9f68bfa --- /dev/null +++ b/app/src/cl/svc/CL_APPROVE.pgc @@ -0,0 +1,18 @@ +/* + * CL_APPROVE.pgc - cl 모듈 서비스 CL_APPROVE (one service = one file). + * 마감 승인: 상태 A. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_APPROVE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_ARCHIVE.h b/app/src/cl/svc/CL_ARCHIVE.h new file mode 100644 index 0000000..3e1cde9 --- /dev/null +++ b/app/src/cl/svc/CL_ARCHIVE.h @@ -0,0 +1,21 @@ +/* + * CL_ARCHIVE.h - cl 서비스 CL_ARCHIVE 카피북 (copybook / record header). + * 마감 아카이브: 승인/완료 마감을 보관 상태로(memo 표시). + */ +#ifndef CL_ARCHIVE_H +#define CL_ARCHIVE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_archive_rec_t; + +void CL_ARCHIVE(TPSVCINFO *p); + +#endif /* CL_ARCHIVE_H */ diff --git a/app/src/cl/svc/CL_ARCHIVE.pgc b/app/src/cl/svc/CL_ARCHIVE.pgc new file mode 100644 index 0000000..17cbbef --- /dev/null +++ b/app/src/cl/svc/CL_ARCHIVE.pgc @@ -0,0 +1,24 @@ +/* + * CL_ARCHIVE.pgc - cl 모듈 서비스 CL_ARCHIVE (one service = one file). + * 마감 아카이브: 승인/완료 마감을 보관 상태로(memo 표시). + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_ARCHIVE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_BALVERIFY.h b/app/src/cl/svc/CL_BALVERIFY.h new file mode 100644 index 0000000..7d23697 --- /dev/null +++ b/app/src/cl/svc/CL_BALVERIFY.h @@ -0,0 +1,21 @@ +/* + * CL_BALVERIFY.h - cl 서비스 CL_BALVERIFY 카피북 (copybook / record header). + * 잔액 검증: 스냅샷 net 합 vs close_log total 비교. + */ +#ifndef CL_BALVERIFY_H +#define CL_BALVERIFY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_balverify_rec_t; + +void CL_BALVERIFY(TPSVCINFO *p); + +#endif /* CL_BALVERIFY_H */ diff --git a/app/src/cl/svc/CL_BALVERIFY.pgc b/app/src/cl/svc/CL_BALVERIFY.pgc new file mode 100644 index 0000000..a1f2d83 --- /dev/null +++ b/app/src/cl/svc/CL_BALVERIFY.pgc @@ -0,0 +1,27 @@ +/* + * CL_BALVERIFY.pgc - cl 모듈 서비스 CL_BALVERIFY (one service = one file). + * 잔액 검증: 스냅샷 net 합 vs close_log total 비교. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_BALVERIFY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CANCEL.h b/app/src/cl/svc/CL_CANCEL.h new file mode 100644 index 0000000..5995f0b --- /dev/null +++ b/app/src/cl/svc/CL_CANCEL.h @@ -0,0 +1,21 @@ +/* + * CL_CANCEL.h - cl 서비스 CL_CANCEL 카피북 (copybook / record header). + * 마감 취소: 상태 X. + */ +#ifndef CL_CANCEL_H +#define CL_CANCEL_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_cancel_rec_t; + +void CL_CANCEL(TPSVCINFO *p); + +#endif /* CL_CANCEL_H */ diff --git a/app/src/cl/svc/CL_CANCEL.pgc b/app/src/cl/svc/CL_CANCEL.pgc new file mode 100644 index 0000000..6f44644 --- /dev/null +++ b/app/src/cl/svc/CL_CANCEL.pgc @@ -0,0 +1,18 @@ +/* + * CL_CANCEL.pgc - cl 모듈 서비스 CL_CANCEL (one service = one file). + * 마감 취소: 상태 X. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_CANCEL.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CONFIRM.h b/app/src/cl/svc/CL_CONFIRM.h new file mode 100644 index 0000000..f93b31a --- /dev/null +++ b/app/src/cl/svc/CL_CONFIRM.h @@ -0,0 +1,21 @@ +/* + * CL_CONFIRM.h - cl 서비스 CL_CONFIRM 카피북 (copybook / record header). + * 마감 확정: 상태 C. + */ +#ifndef CL_CONFIRM_H +#define CL_CONFIRM_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_confirm_rec_t; + +void CL_CONFIRM(TPSVCINFO *p); + +#endif /* CL_CONFIRM_H */ diff --git a/app/src/cl/svc/CL_CONFIRM.pgc b/app/src/cl/svc/CL_CONFIRM.pgc new file mode 100644 index 0000000..0dc145d --- /dev/null +++ b/app/src/cl/svc/CL_CONFIRM.pgc @@ -0,0 +1,18 @@ +/* + * CL_CONFIRM.pgc - cl 모듈 서비스 CL_CONFIRM (one service = one file). + * 마감 확정: 상태 C. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_CONFIRM.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DAILY.h b/app/src/cl/svc/CL_DAILY.h new file mode 100644 index 0000000..eb5aeb9 --- /dev/null +++ b/app/src/cl/svc/CL_DAILY.h @@ -0,0 +1,21 @@ +/* + * CL_DAILY.h - cl 서비스 CL_DAILY 카피북 (copybook / record header). + * 일 마감: 당일 매입 집계 후 마감이력(DAILY) 생성. + */ +#ifndef CL_DAILY_H +#define CL_DAILY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_daily_rec_t; + +void CL_DAILY(TPSVCINFO *p); + +#endif /* CL_DAILY_H */ diff --git a/app/src/cl/svc/CL_DAILY.pgc b/app/src/cl/svc/CL_DAILY.pgc new file mode 100644 index 0000000..045ed80 --- /dev/null +++ b/app/src/cl/svc/CL_DAILY.pgc @@ -0,0 +1,22 @@ +/* + * CL_DAILY.pgc - cl 모듈 서비스 CL_DAILY (one service = one file). + * 일 마감: 당일 매입 집계 후 마감이력(DAILY) 생성. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_DAILY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DIFFCHK.h b/app/src/cl/svc/CL_DIFFCHK.h new file mode 100644 index 0000000..a4ff677 --- /dev/null +++ b/app/src/cl/svc/CL_DIFFCHK.h @@ -0,0 +1,21 @@ +/* + * CL_DIFFCHK.h - cl 서비스 CL_DIFFCHK 카피북 (copybook / record header). + * 차액 점검: close_log total vs 스냅샷 gross 합. + */ +#ifndef CL_DIFFCHK_H +#define CL_DIFFCHK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_diffchk_rec_t; + +void CL_DIFFCHK(TPSVCINFO *p); + +#endif /* CL_DIFFCHK_H */ diff --git a/app/src/cl/svc/CL_DIFFCHK.pgc b/app/src/cl/svc/CL_DIFFCHK.pgc new file mode 100644 index 0000000..6443342 --- /dev/null +++ b/app/src/cl/svc/CL_DIFFCHK.pgc @@ -0,0 +1,26 @@ +/* + * CL_DIFFCHK.pgc - cl 모듈 서비스 CL_DIFFCHK (one service = one file). + * 차액 점검: close_log total vs 스냅샷 gross 합. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_DIFFCHK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_INQUIRY.h b/app/src/cl/svc/CL_INQUIRY.h new file mode 100644 index 0000000..6a2c922 --- /dev/null +++ b/app/src/cl/svc/CL_INQUIRY.h @@ -0,0 +1,21 @@ +/* + * CL_INQUIRY.h - cl 서비스 CL_INQUIRY 카피북 (copybook / record header). + * 마감 조회: close_type/기간/상태/총건수 반환. + */ +#ifndef CL_INQUIRY_H +#define CL_INQUIRY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_inquiry_rec_t; + +void CL_INQUIRY(TPSVCINFO *p); + +#endif /* CL_INQUIRY_H */ diff --git a/app/src/cl/svc/CL_INQUIRY.pgc b/app/src/cl/svc/CL_INQUIRY.pgc new file mode 100644 index 0000000..dea51b8 --- /dev/null +++ b/app/src/cl/svc/CL_INQUIRY.pgc @@ -0,0 +1,28 @@ +/* + * CL_INQUIRY.pgc - cl 모듈 서비스 CL_INQUIRY (one service = one file). + * 마감 조회: close_type/기간/상태/총건수 반환. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_INQUIRY.h" + +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: */ diff --git a/app/src/cl/svc/CL_LOCK.h b/app/src/cl/svc/CL_LOCK.h new file mode 100644 index 0000000..7592650 --- /dev/null +++ b/app/src/cl/svc/CL_LOCK.h @@ -0,0 +1,21 @@ +/* + * CL_LOCK.h - cl 서비스 CL_LOCK 카피북 (copybook / record header). + * 기간 잠금: cl_period_lock upsert(locked=true) + 마감 L. + */ +#ifndef CL_LOCK_H +#define CL_LOCK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_lock_rec_t; + +void CL_LOCK(TPSVCINFO *p); + +#endif /* CL_LOCK_H */ diff --git a/app/src/cl/svc/CL_LOCK.pgc b/app/src/cl/svc/CL_LOCK.pgc new file mode 100644 index 0000000..89ee102 --- /dev/null +++ b/app/src/cl/svc/CL_LOCK.pgc @@ -0,0 +1,28 @@ +/* + * CL_LOCK.pgc - cl 모듈 서비스 CL_LOCK (one service = one file). + * 기간 잠금: cl_period_lock upsert(locked=true) + 마감 L. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_LOCK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_MIDCLOSE.h b/app/src/cl/svc/CL_MIDCLOSE.h new file mode 100644 index 0000000..ab02df4 --- /dev/null +++ b/app/src/cl/svc/CL_MIDCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_MIDCLOSE.h - cl 서비스 CL_MIDCLOSE 카피북 (copybook / record header). + * 중간 마감: 당일 중간 스냅 마감이력(MID) 생성. + */ +#ifndef CL_MIDCLOSE_H +#define CL_MIDCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_midclose_rec_t; + +void CL_MIDCLOSE(TPSVCINFO *p); + +#endif /* CL_MIDCLOSE_H */ diff --git a/app/src/cl/svc/CL_MIDCLOSE.pgc b/app/src/cl/svc/CL_MIDCLOSE.pgc new file mode 100644 index 0000000..a1db8c1 --- /dev/null +++ b/app/src/cl/svc/CL_MIDCLOSE.pgc @@ -0,0 +1,30 @@ +/* + * CL_MIDCLOSE.pgc - cl 모듈 서비스 CL_MIDCLOSE (one service = one file). + * 중간 마감: 당일 중간 스냅 마감이력(MID) 생성. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_MIDCLOSE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_MONTHLY.h b/app/src/cl/svc/CL_MONTHLY.h new file mode 100644 index 0000000..c6c0cfb --- /dev/null +++ b/app/src/cl/svc/CL_MONTHLY.h @@ -0,0 +1,21 @@ +/* + * CL_MONTHLY.h - cl 서비스 CL_MONTHLY 카피북 (copybook / record header). + * 월 마감: 해당 월(YYYY-MM) 매입 집계 후 마감이력(MONTHLY). + */ +#ifndef CL_MONTHLY_H +#define CL_MONTHLY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_monthly_rec_t; + +void CL_MONTHLY(TPSVCINFO *p); + +#endif /* CL_MONTHLY_H */ diff --git a/app/src/cl/svc/CL_MONTHLY.pgc b/app/src/cl/svc/CL_MONTHLY.pgc new file mode 100644 index 0000000..0284156 --- /dev/null +++ b/app/src/cl/svc/CL_MONTHLY.pgc @@ -0,0 +1,23 @@ +/* + * CL_MONTHLY.pgc - cl 모듈 서비스 CL_MONTHLY (one service = one file). + * 월 마감: 해당 월(YYYY-MM) 매입 집계 후 마감이력(MONTHLY). + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_MONTHLY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_PERIOD.h b/app/src/cl/svc/CL_PERIOD.h new file mode 100644 index 0000000..4e27b68 --- /dev/null +++ b/app/src/cl/svc/CL_PERIOD.h @@ -0,0 +1,21 @@ +/* + * CL_PERIOD.h - cl 서비스 CL_PERIOD 카피북 (copybook / record header). + * 기간 마감 개시(open): close_log 를 O 상태로 등록. + */ +#ifndef CL_PERIOD_H +#define CL_PERIOD_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_period_rec_t; + +void CL_PERIOD(TPSVCINFO *p); + +#endif /* CL_PERIOD_H */ diff --git a/app/src/cl/svc/CL_PERIOD.pgc b/app/src/cl/svc/CL_PERIOD.pgc new file mode 100644 index 0000000..6885702 --- /dev/null +++ b/app/src/cl/svc/CL_PERIOD.pgc @@ -0,0 +1,25 @@ +/* + * CL_PERIOD.pgc - cl 모듈 서비스 CL_PERIOD (one service = one file). + * 기간 마감 개시(open): close_log 를 O 상태로 등록. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_PERIOD.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_PURGE.h b/app/src/cl/svc/CL_PURGE.h new file mode 100644 index 0000000..8ee80a9 --- /dev/null +++ b/app/src/cl/svc/CL_PURGE.h @@ -0,0 +1,21 @@ +/* + * CL_PURGE.h - cl 서비스 CL_PURGE 카피북 (copybook / record header). + * 취소 마감 정리: 오래된 X 상태 마감과 그 스냅샷 삭제. + */ +#ifndef CL_PURGE_H +#define CL_PURGE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_purge_rec_t; + +void CL_PURGE(TPSVCINFO *p); + +#endif /* CL_PURGE_H */ diff --git a/app/src/cl/svc/CL_PURGE.pgc b/app/src/cl/svc/CL_PURGE.pgc new file mode 100644 index 0000000..3b2edd5 --- /dev/null +++ b/app/src/cl/svc/CL_PURGE.pgc @@ -0,0 +1,30 @@ +/* + * CL_PURGE.pgc - cl 모듈 서비스 CL_PURGE (one service = one file). + * 취소 마감 정리: 오래된 X 상태 마감과 그 스냅샷 삭제. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_PURGE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_QUARTER.h b/app/src/cl/svc/CL_QUARTER.h new file mode 100644 index 0000000..50e57dc --- /dev/null +++ b/app/src/cl/svc/CL_QUARTER.h @@ -0,0 +1,21 @@ +/* + * CL_QUARTER.h - cl 서비스 CL_QUARTER 카피북 (copybook / record header). + * 분기 마감: 분기 프리픽스(YYYY-MM LIKE)로 집계. + */ +#ifndef CL_QUARTER_H +#define CL_QUARTER_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_quarter_rec_t; + +void CL_QUARTER(TPSVCINFO *p); + +#endif /* CL_QUARTER_H */ diff --git a/app/src/cl/svc/CL_QUARTER.pgc b/app/src/cl/svc/CL_QUARTER.pgc new file mode 100644 index 0000000..6b2f86e --- /dev/null +++ b/app/src/cl/svc/CL_QUARTER.pgc @@ -0,0 +1,22 @@ +/* + * CL_QUARTER.pgc - cl 모듈 서비스 CL_QUARTER (one service = one file). + * 분기 마감: 분기 프리픽스(YYYY-MM LIKE)로 집계. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_QUARTER.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RECLOSE.h b/app/src/cl/svc/CL_RECLOSE.h new file mode 100644 index 0000000..29cbabc --- /dev/null +++ b/app/src/cl/svc/CL_RECLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_RECLOSE.h - cl 서비스 CL_RECLOSE 카피북 (copybook / record header). + * 재마감: 상태 R 로 표시하고 총계 재집계. + */ +#ifndef CL_RECLOSE_H +#define CL_RECLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_reclose_rec_t; + +void CL_RECLOSE(TPSVCINFO *p); + +#endif /* CL_RECLOSE_H */ diff --git a/app/src/cl/svc/CL_RECLOSE.pgc b/app/src/cl/svc/CL_RECLOSE.pgc new file mode 100644 index 0000000..2fbe126 --- /dev/null +++ b/app/src/cl/svc/CL_RECLOSE.pgc @@ -0,0 +1,30 @@ +/* + * CL_RECLOSE.pgc - cl 모듈 서비스 CL_RECLOSE (one service = one file). + * 재마감: 상태 R 로 표시하고 총계 재집계. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_RECLOSE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RECON.h b/app/src/cl/svc/CL_RECON.h new file mode 100644 index 0000000..8e467a9 --- /dev/null +++ b/app/src/cl/svc/CL_RECON.h @@ -0,0 +1,21 @@ +/* + * CL_RECON.h - cl 서비스 CL_RECON 카피북 (copybook / record header). + * 마감 대사: 실제 매입건수 vs close_log txn_count 대사. + */ +#ifndef CL_RECON_H +#define CL_RECON_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_recon_rec_t; + +void CL_RECON(TPSVCINFO *p); + +#endif /* CL_RECON_H */ diff --git a/app/src/cl/svc/CL_RECON.pgc b/app/src/cl/svc/CL_RECON.pgc new file mode 100644 index 0000000..a063be8 --- /dev/null +++ b/app/src/cl/svc/CL_RECON.pgc @@ -0,0 +1,32 @@ +/* + * CL_RECON.pgc - cl 모듈 서비스 CL_RECON (one service = one file). + * 마감 대사: 실제 매입건수 vs close_log txn_count 대사. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_RECON.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_REPORT.h b/app/src/cl/svc/CL_REPORT.h new file mode 100644 index 0000000..6dbe18f --- /dev/null +++ b/app/src/cl/svc/CL_REPORT.h @@ -0,0 +1,21 @@ +/* + * CL_REPORT.h - cl 서비스 CL_REPORT 카피북 (copybook / record header). + * 마감 리포트: 특정 마감의 스냅샷 건수/총매출. + */ +#ifndef CL_REPORT_H +#define CL_REPORT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_report_rec_t; + +void CL_REPORT(TPSVCINFO *p); + +#endif /* CL_REPORT_H */ diff --git a/app/src/cl/svc/CL_REPORT.pgc b/app/src/cl/svc/CL_REPORT.pgc new file mode 100644 index 0000000..eff23eb --- /dev/null +++ b/app/src/cl/svc/CL_REPORT.pgc @@ -0,0 +1,24 @@ +/* + * CL_REPORT.pgc - cl 모듈 서비스 CL_REPORT (one service = one file). + * 마감 리포트: 특정 마감의 스냅샷 건수/총매출. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_REPORT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RESTORE.h b/app/src/cl/svc/CL_RESTORE.h new file mode 100644 index 0000000..51b636e --- /dev/null +++ b/app/src/cl/svc/CL_RESTORE.h @@ -0,0 +1,21 @@ +/* + * CL_RESTORE.h - cl 서비스 CL_RESTORE 카피북 (copybook / record header). + * 마감 복원: 취소(X) 마감을 개시(O) 상태로 되돌림. + */ +#ifndef CL_RESTORE_H +#define CL_RESTORE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_restore_rec_t; + +void CL_RESTORE(TPSVCINFO *p); + +#endif /* CL_RESTORE_H */ diff --git a/app/src/cl/svc/CL_RESTORE.pgc b/app/src/cl/svc/CL_RESTORE.pgc new file mode 100644 index 0000000..86eb0e1 --- /dev/null +++ b/app/src/cl/svc/CL_RESTORE.pgc @@ -0,0 +1,23 @@ +/* + * CL_RESTORE.pgc - cl 모듈 서비스 CL_RESTORE (one service = one file). + * 마감 복원: 취소(X) 마감을 개시(O) 상태로 되돌림. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_RESTORE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_ROLLBACK.h b/app/src/cl/svc/CL_ROLLBACK.h new file mode 100644 index 0000000..0f85d15 --- /dev/null +++ b/app/src/cl/svc/CL_ROLLBACK.h @@ -0,0 +1,21 @@ +/* + * CL_ROLLBACK.h - cl 서비스 CL_ROLLBACK 카피북 (copybook / record header). + * 마감 롤백: 스냅샷 삭제 + 상태 O 로 복귀. + */ +#ifndef CL_ROLLBACK_H +#define CL_ROLLBACK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_rollback_rec_t; + +void CL_ROLLBACK(TPSVCINFO *p); + +#endif /* CL_ROLLBACK_H */ diff --git a/app/src/cl/svc/CL_ROLLBACK.pgc b/app/src/cl/svc/CL_ROLLBACK.pgc new file mode 100644 index 0000000..926b7b9 --- /dev/null +++ b/app/src/cl/svc/CL_ROLLBACK.pgc @@ -0,0 +1,24 @@ +/* + * CL_ROLLBACK.pgc - cl 모듈 서비스 CL_ROLLBACK (one service = one file). + * 마감 롤백: 스냅샷 삭제 + 상태 O 로 복귀. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_ROLLBACK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SEQ.h b/app/src/cl/svc/CL_SEQ.h new file mode 100644 index 0000000..044719c --- /dev/null +++ b/app/src/cl/svc/CL_SEQ.h @@ -0,0 +1,21 @@ +/* + * CL_SEQ.h - cl 서비스 CL_SEQ 카피북 (copybook / record header). + * 마감 순번 발번(dbio upsert). + */ +#ifndef CL_SEQ_H +#define CL_SEQ_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_seq_rec_t; + +void CL_SEQ(TPSVCINFO *p); + +#endif /* CL_SEQ_H */ diff --git a/app/src/cl/svc/CL_SEQ.pgc b/app/src/cl/svc/CL_SEQ.pgc new file mode 100644 index 0000000..cd0f5f8 --- /dev/null +++ b/app/src/cl/svc/CL_SEQ.pgc @@ -0,0 +1,21 @@ +/* + * CL_SEQ.pgc - cl 모듈 서비스 CL_SEQ (one service = one file). + * 마감 순번 발번(dbio upsert). + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_SEQ.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SNAPSHOT.h b/app/src/cl/svc/CL_SNAPSHOT.h new file mode 100644 index 0000000..439ca2d --- /dev/null +++ b/app/src/cl/svc/CL_SNAPSHOT.h @@ -0,0 +1,21 @@ +/* + * CL_SNAPSHOT.h - cl 서비스 CL_SNAPSHOT 카피북 (copybook / record header). + * 마감 스냅샷: merchant_summary 를 close_id 로 고정 복사. + */ +#ifndef CL_SNAPSHOT_H +#define CL_SNAPSHOT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_snapshot_rec_t; + +void CL_SNAPSHOT(TPSVCINFO *p); + +#endif /* CL_SNAPSHOT_H */ diff --git a/app/src/cl/svc/CL_SNAPSHOT.pgc b/app/src/cl/svc/CL_SNAPSHOT.pgc new file mode 100644 index 0000000..2cbc0d3 --- /dev/null +++ b/app/src/cl/svc/CL_SNAPSHOT.pgc @@ -0,0 +1,37 @@ +/* + * CL_SNAPSHOT.pgc - cl 모듈 서비스 CL_SNAPSHOT (one service = one file). + * 마감 스냅샷: merchant_summary 를 close_id 로 고정 복사. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_SNAPSHOT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_STATS.h b/app/src/cl/svc/CL_STATS.h new file mode 100644 index 0000000..013ef57 --- /dev/null +++ b/app/src/cl/svc/CL_STATS.h @@ -0,0 +1,21 @@ +/* + * CL_STATS.h - cl 서비스 CL_STATS 카피북 (copybook / record header). + * 마감 통계: close_type 별 마감 건수/총액. + */ +#ifndef CL_STATS_H +#define CL_STATS_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_stats_rec_t; + +void CL_STATS(TPSVCINFO *p); + +#endif /* CL_STATS_H */ diff --git a/app/src/cl/svc/CL_STATS.pgc b/app/src/cl/svc/CL_STATS.pgc new file mode 100644 index 0000000..91232a4 --- /dev/null +++ b/app/src/cl/svc/CL_STATS.pgc @@ -0,0 +1,27 @@ +/* + * CL_STATS.pgc - cl 모듈 서비스 CL_STATS (one service = one file). + * 마감 통계: close_type 별 마감 건수/총액. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_STATS.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_STATUS.h b/app/src/cl/svc/CL_STATUS.h new file mode 100644 index 0000000..3a3c631 --- /dev/null +++ b/app/src/cl/svc/CL_STATUS.h @@ -0,0 +1,21 @@ +/* + * CL_STATUS.h - cl 서비스 CL_STATUS 카피북 (copybook / record header). + * 마감 상태 조회(dbio getter). + */ +#ifndef CL_STATUS_H +#define CL_STATUS_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_status_rec_t; + +void CL_STATUS(TPSVCINFO *p); + +#endif /* CL_STATUS_H */ diff --git a/app/src/cl/svc/CL_STATUS.pgc b/app/src/cl/svc/CL_STATUS.pgc new file mode 100644 index 0000000..cccbddf --- /dev/null +++ b/app/src/cl/svc/CL_STATUS.pgc @@ -0,0 +1,19 @@ +/* + * CL_STATUS.pgc - cl 모듈 서비스 CL_STATUS (one service = one file). + * 마감 상태 조회(dbio getter). + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_STATUS.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SUMMARY.h b/app/src/cl/svc/CL_SUMMARY.h new file mode 100644 index 0000000..eb54abb --- /dev/null +++ b/app/src/cl/svc/CL_SUMMARY.h @@ -0,0 +1,21 @@ +/* + * CL_SUMMARY.h - cl 서비스 CL_SUMMARY 카피북 (copybook / record header). + * 마감 요약: 스냅샷 gross/fee/net 합계 반환. + */ +#ifndef CL_SUMMARY_H +#define CL_SUMMARY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_summary_rec_t; + +void CL_SUMMARY(TPSVCINFO *p); + +#endif /* CL_SUMMARY_H */ diff --git a/app/src/cl/svc/CL_SUMMARY.pgc b/app/src/cl/svc/CL_SUMMARY.pgc new file mode 100644 index 0000000..74bdcf4 --- /dev/null +++ b/app/src/cl/svc/CL_SUMMARY.pgc @@ -0,0 +1,26 @@ +/* + * CL_SUMMARY.pgc - cl 모듈 서비스 CL_SUMMARY (one service = one file). + * 마감 요약: 스냅샷 gross/fee/net 합계 반환. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_SUMMARY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_UNCLOSED.h b/app/src/cl/svc/CL_UNCLOSED.h new file mode 100644 index 0000000..dfb43b3 --- /dev/null +++ b/app/src/cl/svc/CL_UNCLOSED.h @@ -0,0 +1,21 @@ +/* + * CL_UNCLOSED.h - cl 서비스 CL_UNCLOSED 카피북 (copybook / record header). + * 미마감일 점검: close_log 없는 매입 영업일 건수. + */ +#ifndef CL_UNCLOSED_H +#define CL_UNCLOSED_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_unclosed_rec_t; + +void CL_UNCLOSED(TPSVCINFO *p); + +#endif /* CL_UNCLOSED_H */ diff --git a/app/src/cl/svc/CL_UNCLOSED.pgc b/app/src/cl/svc/CL_UNCLOSED.pgc new file mode 100644 index 0000000..5819025 --- /dev/null +++ b/app/src/cl/svc/CL_UNCLOSED.pgc @@ -0,0 +1,25 @@ +/* + * CL_UNCLOSED.pgc - cl 모듈 서비스 CL_UNCLOSED (one service = one file). + * 미마감일 점검: close_log 없는 매입 영업일 건수. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_UNCLOSED.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_UNLOCK.h b/app/src/cl/svc/CL_UNLOCK.h new file mode 100644 index 0000000..39c7f5f --- /dev/null +++ b/app/src/cl/svc/CL_UNLOCK.h @@ -0,0 +1,21 @@ +/* + * CL_UNLOCK.h - cl 서비스 CL_UNLOCK 카피북 (copybook / record header). + * 기간 잠금 해제. + */ +#ifndef CL_UNLOCK_H +#define CL_UNLOCK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_unlock_rec_t; + +void CL_UNLOCK(TPSVCINFO *p); + +#endif /* CL_UNLOCK_H */ diff --git a/app/src/cl/svc/CL_UNLOCK.pgc b/app/src/cl/svc/CL_UNLOCK.pgc new file mode 100644 index 0000000..9252c61 --- /dev/null +++ b/app/src/cl/svc/CL_UNLOCK.pgc @@ -0,0 +1,25 @@ +/* + * CL_UNLOCK.pgc - cl 모듈 서비스 CL_UNLOCK (one service = one file). + * 기간 잠금 해제. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_UNLOCK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_VERIFY.h b/app/src/cl/svc/CL_VERIFY.h new file mode 100644 index 0000000..d6003dd --- /dev/null +++ b/app/src/cl/svc/CL_VERIFY.h @@ -0,0 +1,21 @@ +/* + * CL_VERIFY.h - cl 서비스 CL_VERIFY 카피북 (copybook / record header). + * 마감 검증: close_log 총건수 vs 실제 매입건수 비교. + */ +#ifndef CL_VERIFY_H +#define CL_VERIFY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_verify_rec_t; + +void CL_VERIFY(TPSVCINFO *p); + +#endif /* CL_VERIFY_H */ diff --git a/app/src/cl/svc/CL_VERIFY.pgc b/app/src/cl/svc/CL_VERIFY.pgc new file mode 100644 index 0000000..e7d1762 --- /dev/null +++ b/app/src/cl/svc/CL_VERIFY.pgc @@ -0,0 +1,29 @@ +/* + * CL_VERIFY.pgc - cl 모듈 서비스 CL_VERIFY (one service = one file). + * 마감 검증: close_log 총건수 vs 실제 매입건수 비교. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_VERIFY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_YEAREND.h b/app/src/cl/svc/CL_YEAREND.h new file mode 100644 index 0000000..c621d9d --- /dev/null +++ b/app/src/cl/svc/CL_YEAREND.h @@ -0,0 +1,21 @@ +/* + * CL_YEAREND.h - cl 서비스 CL_YEAREND 카피북 (copybook / record header). + * 연말 마감: 연도(YYYY) 프리픽스로 집계. + */ +#ifndef CL_YEAREND_H +#define CL_YEAREND_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_yearend_rec_t; + +void CL_YEAREND(TPSVCINFO *p); + +#endif /* CL_YEAREND_H */ diff --git a/app/src/cl/svc/CL_YEAREND.pgc b/app/src/cl/svc/CL_YEAREND.pgc new file mode 100644 index 0000000..6c53f6d --- /dev/null +++ b/app/src/cl/svc/CL_YEAREND.pgc @@ -0,0 +1,23 @@ +/* + * CL_YEAREND.pgc - cl 모듈 서비스 CL_YEAREND (one service = one file). + * 연말 마감: 연도(YYYY) 프리픽스로 집계. + * 이전 인라인 cl_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * cl_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "CL_YEAREND.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/cl_svc.h b/app/src/cl/svc/cl_svc.h new file mode 100644 index 0000000..76ce795 --- /dev/null +++ b/app/src/cl/svc/cl_svc.h @@ -0,0 +1,48 @@ +/* + * cl_svc.h - cl (마감/closing) 서비스 공통 카피북 (shared copybook for all cl svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the cl dbio layer (libcldbio.a) and the + * common helpers (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros + the shared 일/월/분기/연 마감 집계 헬퍼(cl_open_close). + * + * 마감 상태 O=개시 -> C=마감완료 -> A=승인 -> L=잠금, 취소 X / 재마감 R. + */ +#ifndef CL_SVC_H +#define CL_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "cl_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +/* 공통: 일/월/분기/연 마감 집계+이력생성 래퍼. 실제 EXEC SQL 은 dbio 계층의 + * cldb_open_close() 에 있고(헤더에는 EXEC SQL 금지), 여기서는 순수 C 로 감싸 + * 집계 결과를 UBF(T_COUNT/T_GROSS)에 실어준다. CL_DAILY/MONTHLY/QUARTER/YEAREND 공유. */ +static int cl_open_close(UBFH *b, const char *ctype, const char *period, + const char *bizdate, long *cid_out) +{ + long cnt = 0, amt = 0; + if (cldb_open_close(ctype, period, bizdate, cid_out, &cnt, &amt) < 0) return -1; + setl(b, T_COUNT, cnt); + setl(b, T_GROSS, amt); + return 0; +} + +#endif /* CL_SVC_H */ diff --git a/app/src/cm/batch/cm_archive_batch.pgc b/app/src/cm/batch/cm_archive_batch.pgc new file mode 100644 index 0000000..2c18418 --- /dev/null +++ b/app/src/cm/batch/cm_archive_batch.pgc @@ -0,0 +1,38 @@ +/* + * cm_archive_batch.pgc - cm 모듈 상태 일괄 전이 업데이트 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 상태 일괄 전이 업데이트; then tpcommit drives XA 2PC. + * Usage: cm_archive_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_upd = 0; + 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 UPDATE cm_log SET log_level = 'A', updated_at = now() + WHERE biz_date = :h_bizdate AND log_level = 'I'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_archive_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_upd = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_archive_batch COMMIT: bizdate=%s updated=%ld\n", bizdate, h_upd); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_dbgpurge_batch.pgc b/app/src/cm/batch/cm_dbgpurge_batch.pgc new file mode 100644 index 0000000..c1cf15d --- /dev/null +++ b/app/src/cm/batch/cm_dbgpurge_batch.pgc @@ -0,0 +1,37 @@ +/* + * cm_dbgpurge_batch.pgc - cm 모듈 상태 조건 삭제 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 상태 조건 삭제; then tpcommit drives XA 2PC. + * Usage: cm_dbgpurge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_del = 0; + 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 DELETE FROM cm_log WHERE biz_date = :h_bizdate AND log_level = 'D'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_dbgpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_del = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_dbgpurge_batch COMMIT: bizdate=%s deleted=%ld\n", bizdate, h_del); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_distinct_batch.pgc b/app/src/cm/batch/cm_distinct_batch.pgc new file mode 100644 index 0000000..3d0c604 --- /dev/null +++ b/app/src/cm/batch/cm_distinct_batch.pgc @@ -0,0 +1,36 @@ +/* + * cm_distinct_batch.pgc - cm 모듈 고유값(distinct) 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 고유값(distinct) 집계; then tpcommit drives XA 2PC. + * Usage: cm_distinct_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_d = 0; + 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 SELECT count(DISTINCT svc_name) INTO :h_d FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_distinct_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_distinct_batch COMMIT: bizdate=%s distinct=%ld\n", bizdate, h_d); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_errbps_batch.pgc b/app/src/cm/batch/cm_errbps_batch.pgc new file mode 100644 index 0000000..f1b0b02 --- /dev/null +++ b/app/src/cm/batch/cm_errbps_batch.pgc @@ -0,0 +1,40 @@ +/* + * cm_errbps_batch.pgc - cm 모듈 두 SELECT 비율(bps) 산출 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 두 SELECT 비율(bps) 산출; then tpcommit drives XA 2PC. + * Usage: cm_errbps_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, h_bps = 0; + 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 SELECT count(*) INTO :h_a FROM cm_log + WHERE biz_date = :h_bizdate AND log_level = 'E'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_b FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + h_bps = (h_b > 0) ? h_a * 10000 / h_b : 0; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_errbps_batch COMMIT: bizdate=%s hit=%ld total=%ld bps=%ld\n", bizdate, h_a, h_b, h_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxjoin_batch.pgc b/app/src/cm/batch/cm_fxjoin_batch.pgc new file mode 100644 index 0000000..baaa0d4 --- /dev/null +++ b/app/src/cm/batch/cm_fxjoin_batch.pgc @@ -0,0 +1,38 @@ +/* + * cm_fxjoin_batch.pgc - cm 모듈 조인 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 조인 집계; then tpcommit drives XA 2PC. + * Usage: cm_fxjoin_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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 SELECT coalesce(sum(a.rate_bps),0) INTO :h_v + FROM cm_fx_rate a JOIN cm_holiday b ON a.base_date = b.holi_date + WHERE a.base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxjoin_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxjoin_batch COMMIT: bizdate=%s joined_sum=%ld\n", bizdate, h_v); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxspread_batch.pgc b/app/src/cm/batch/cm_fxspread_batch.pgc new file mode 100644 index 0000000..4399ff9 --- /dev/null +++ b/app/src/cm/batch/cm_fxspread_batch.pgc @@ -0,0 +1,38 @@ +/* + * cm_fxspread_batch.pgc - cm 모듈 최대-최소 스프레드 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 최대-최소 스프레드 집계; then tpcommit drives XA 2PC. + * Usage: cm_fxspread_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0, h_spread = 0; + 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 SELECT coalesce(max(rate_bps),0), coalesce(min(rate_bps),0) + INTO :h_max, :h_min FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxspread_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_spread = h_max - h_min; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxspread_batch COMMIT: bizdate=%s max=%ld min=%ld spread=%ld\n", bizdate, h_max, h_min, h_spread); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxsum_batch.pgc b/app/src/cm/batch/cm_fxsum_batch.pgc new file mode 100644 index 0000000..3126b36 --- /dev/null +++ b/app/src/cm/batch/cm_fxsum_batch.pgc @@ -0,0 +1,36 @@ +/* + * cm_fxsum_batch.pgc - cm 모듈 스칼라 합계 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 스칼라 합계 집계; then tpcommit drives XA 2PC. + * Usage: cm_fxsum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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 SELECT coalesce(sum(rate_bps),0) INTO :h_v FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxsum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxsum_batch COMMIT: bizdate=%s total=%ld\n", bizdate, h_v); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_levelstat_batch.pgc b/app/src/cm/batch/cm_levelstat_batch.pgc new file mode 100644 index 0000000..a22fb76 --- /dev/null +++ b/app/src/cm/batch/cm_levelstat_batch.pgc @@ -0,0 +1,40 @@ +/* + * cm_levelstat_batch.pgc - cm 모듈 3분류 CASE 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 3분류 CASE 집계; then tpcommit drives XA 2PC. + * Usage: cm_levelstat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, h_c = 0; + 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 SELECT + coalesce(sum(CASE WHEN log_level = 'I' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN log_level = 'W' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN log_level = 'E' THEN 1 ELSE 0 END),0) + INTO :h_a, :h_b, :h_c FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_levelstat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_levelstat_batch COMMIT: bizdate=%s a=%ld b=%ld c=%ld\n", bizdate, h_a, h_b, h_c); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_logcnt_batch.pgc b/app/src/cm/batch/cm_logcnt_batch.pgc new file mode 100644 index 0000000..554467a --- /dev/null +++ b/app/src/cm/batch/cm_logcnt_batch.pgc @@ -0,0 +1,37 @@ +/* + * cm_logcnt_batch.pgc - cm 모듈 건수+금액 단건 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 건수+금액 단건 집계; then tpcommit drives XA 2PC. + * Usage: cm_logcnt_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0, h_sum = 0; + 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 SELECT count(*), coalesce(sum(log_id),0) INTO :h_cnt, :h_sum + FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_logcnt_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_logcnt_batch COMMIT: bizdate=%s rows=%ld sum=%ld\n", bizdate, h_cnt, h_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_logprune_batch.pgc b/app/src/cm/batch/cm_logprune_batch.pgc new file mode 100644 index 0000000..8309bf5 --- /dev/null +++ b/app/src/cm/batch/cm_logprune_batch.pgc @@ -0,0 +1,38 @@ +/* + * cm_logprune_batch.pgc - cm 모듈 서브셀렉트 IN 조건 삭제 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 서브셀렉트 IN 조건 삭제; then tpcommit drives XA 2PC. + * Usage: cm_logprune_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_del = 0; + 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 DELETE FROM cm_log + WHERE log_id IN (SELECT log_id FROM cm_log WHERE biz_date = :h_bizdate AND log_level = 'D'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_logprune_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_del = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_logprune_batch COMMIT: bizdate=%s deleted=%ld\n", bizdate, h_del); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_logroll_batch.pgc b/app/src/cm/batch/cm_logroll_batch.pgc new file mode 100644 index 0000000..e16e68b --- /dev/null +++ b/app/src/cm/batch/cm_logroll_batch.pgc @@ -0,0 +1,39 @@ +/* + * cm_logroll_batch.pgc - cm 모듈 INSERT ... SELECT 그룹 롤업 적재 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; INSERT ... SELECT 그룹 롤업 적재; then tpcommit drives XA 2PC. + * Usage: cm_logroll_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rows = 0; + 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 INSERT INTO cm_log_rollup (svc_name, biz_date, cnt, amt) + SELECT svc_name, biz_date, count(*), coalesce(sum(log_id),0) + FROM cm_log WHERE biz_date = :h_bizdate GROUP BY svc_name, biz_date; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_logroll_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_logroll_batch COMMIT: bizdate=%s rolled=%ld\n", bizdate, h_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_marker_batch.pgc b/app/src/cm/batch/cm_marker_batch.pgc new file mode 100644 index 0000000..c1ae88e --- /dev/null +++ b/app/src/cm/batch/cm_marker_batch.pgc @@ -0,0 +1,40 @@ +/* + * cm_marker_batch.pgc - cm 모듈 시퀀스 채번 + 마커 행 INSERT 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 시퀀스 채번 + 마커 행 INSERT; then tpcommit drives XA 2PC. + * Usage: cm_marker_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_id = 0, h_cnt = 0; + 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 SELECT count(*) INTO :h_cnt FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT nextval('cm_log_seq') INTO :h_id; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL INSERT INTO cm_eod_mark (mark_id, biz_date, cnt) VALUES (:h_id, :h_bizdate, :h_cnt); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_marker_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_marker_batch COMMIT: bizdate=%s mark_id=%ld cnt=%ld\n", bizdate, h_id, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_reconf_batch.pgc b/app/src/cm/batch/cm_reconf_batch.pgc new file mode 100644 index 0000000..c2a5f73 --- /dev/null +++ b/app/src/cm/batch/cm_reconf_batch.pgc @@ -0,0 +1,43 @@ +/* + * cm_reconf_batch.pgc - cm 모듈 건수 확인 후 조건부 업데이트 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 건수 확인 후 조건부 업데이트; then tpcommit drives XA 2PC. + * Usage: cm_reconf_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0, h_upd = 0; + 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 SELECT count(*) INTO :h_cnt FROM cm_log + WHERE biz_date = :h_bizdate AND log_level = 'W'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (h_cnt > 0) { + EXEC SQL UPDATE cm_log SET log_level = 'A', updated_at = now() + WHERE biz_date = :h_bizdate AND log_level = 'W'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_reconf_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_upd = sqlca.sqlerrd[2]; + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_reconf_batch COMMIT: bizdate=%s pending=%ld updated=%ld\n", bizdate, h_cnt, h_upd); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_svcacc_batch.pgc b/app/src/cm/batch/cm_svcacc_batch.pgc new file mode 100644 index 0000000..54cc520 --- /dev/null +++ b/app/src/cm/batch/cm_svcacc_batch.pgc @@ -0,0 +1,46 @@ +/* + * cm_svcacc_batch.pgc - cm 모듈 커서 누적 합계 (행별 DML 없음) 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서 누적 합계 (행별 DML 없음); then tpcommit drives XA 2PC. + * Usage: cm_svcacc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, total = 0, rows = 0; + 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 bc CURSOR FOR + SELECT svc_name, coalesce(sum(log_id),0) FROM cm_log + WHERE biz_date = :h_bizdate GROUP BY svc_name; + EXEC SQL OPEN bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + total += h_amt; rows++; + } + EXEC SQL CLOSE bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_svcacc_batch COMMIT: bizdate=%s groups=%ld total=%ld\n", bizdate, rows, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_svcsnap_batch.pgc b/app/src/cm/batch/cm_svcsnap_batch.pgc new file mode 100644 index 0000000..e799de5 --- /dev/null +++ b/app/src/cm/batch/cm_svcsnap_batch.pgc @@ -0,0 +1,49 @@ +/* + * cm_svcsnap_batch.pgc - cm 모듈 커서 순회 후 대상 테이블 행별 INSERT 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서 순회 후 대상 테이블 행별 INSERT; then tpcommit drives XA 2PC. + * Usage: cm_svcsnap_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, rows = 0; + 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 bc CURSOR FOR + SELECT svc_name, coalesce(sum(log_id),0) FROM cm_log + WHERE biz_date = :h_bizdate GROUP BY svc_name; + EXEC SQL OPEN bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + EXEC SQL INSERT INTO cm_svc_snap (svc_name, biz_date, amt) + VALUES (:h_key, :h_bizdate, :h_amt); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_svcsnap_batch COMMIT: bizdate=%s inserted=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/cm_svr.pgc b/app/src/cm/cm_svr.pgc index 756b89d..2b69f51 100644 --- a/app/src/cm/cm_svr.pgc +++ b/app/src/cm/cm_svr.pgc @@ -1,99 +1,72 @@ /* - * cm_svr.pgc - cm (공통 / common) MODULE SERVER. + * cm_svr.pgc - cm (공통 / common) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of cm's 30 online services now lives in its own file + * (app/src/cm/svc/.pgc) with its own copybook header + * (app/src/cm/svc/.h); those objects are archived into libcmsvc.a + * and linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all 30 services from a {name, fn} table, + * - tpclose() at shutdown. * * 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 -#include -#include #include #include #include -#include -#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); } +/* every cm service's copybook header (declares its prototype) */ +#include "CM_CODE.h" +#include "CM_CODENAME.h" +#include "CM_BIZDAY.h" +#include "CM_NEXTBIZ.h" +#include "CM_ADDDAYS.h" +#include "CM_WEEKDAY.h" +#include "CM_MONTHEND.h" +#include "CM_AMT_ROUND.h" +#include "CM_AMT_SPLIT.h" +#include "CM_SEQ.h" +#include "CM_FX.h" +#include "CM_FX_CONV.h" +#include "CM_LUHN.h" +#include "CM_CRC.h" +#include "CM_MASK.h" +#include "CM_FMT_DATE.h" +#include "CM_FMT_AMT.h" +#include "CM_PARAM.h" +#include "CM_PARAM_SET.h" +#include "CM_CACHE_LOAD.h" +#include "CM_CACHE_INV.h" +#include "CM_ERRMSG.h" +#include "CM_LOG.h" +#include "CM_HOLIDAY_CHK.h" +#include "CM_CHECKDIGIT.h" +#include "CM_INTEREST.h" +#include "CM_PRORATE.h" +#include "CM_NETTING_CALC.h" +#include "CM_STATUS.h" +#include "CM_PING.h" -#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 --------------------------------------------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, + {"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} }; @@ -110,7 +83,7 @@ int tpsvrinit(int argc, char **argv) return -1; } } - userlog("cm_svr: %d cm 공통 서비스 광고 완료, RM 오픈", i); + userlog("cm_svr: %d cm 서비스 광고 완료, RM 오픈", i); return 0; } @@ -120,435 +93,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/cm/dbio/cm_code_like_dbio.h b/app/src/cm/dbio/cm_code_like_dbio.h new file mode 100644 index 0000000..7d3a2a7 --- /dev/null +++ b/app/src/cm/dbio/cm_code_like_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_code_like_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_code_like_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_CODE_LIKE_DBIO_H +#define CM_CODE_LIKE_DBIO_H + +/* 코드명 패턴 검색 건수. */ +long cmdb_code_name_like(const char *pat); + +#endif /* CM_CODE_LIKE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_code_like_dbio.pgc b/app/src/cm/dbio/cm_code_like_dbio.pgc new file mode 100644 index 0000000..2ff43c8 --- /dev/null +++ b/app/src/cm/dbio/cm_code_like_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_code_like_dbio.pgc - cm 모듈 DB 접근 함수 (cm_code_like_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 코드명 패턴 검색 건수. + */ +#include +#include +#include "cm_code_like_dbio.h" + +long cmdb_code_name_like(const char *pat) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_pat[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_pat, pat, sizeof(h_pat)-1); h_pat[sizeof(h_pat)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_code WHERE code_name LIKE :h_pat; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_counter_add_dbio.h b/app/src/cm/dbio/cm_counter_add_dbio.h new file mode 100644 index 0000000..f69d679 --- /dev/null +++ b/app/src/cm/dbio/cm_counter_add_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_counter_add_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_counter_add_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_COUNTER_ADD_DBIO_H +#define CM_COUNTER_ADD_DBIO_H + +/* 카운터 채번 후 적재. */ +long cmdb_counter_add(const char *bizdate, long amount); + +#endif /* CM_COUNTER_ADD_DBIO_H */ diff --git a/app/src/cm/dbio/cm_counter_add_dbio.pgc b/app/src/cm/dbio/cm_counter_add_dbio.pgc new file mode 100644 index 0000000..8ca7969 --- /dev/null +++ b/app/src/cm/dbio/cm_counter_add_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cm_counter_add_dbio.pgc - cm 모듈 DB 접근 함수 (cm_counter_add_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 카운터 채번 후 적재. + */ +#include +#include +#include "cm_counter_add_dbio.h" + +long cmdb_counter_add(const char *bizdate, long amount) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id = 0, h_amt = amount; + 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 nextval('cm_counter_seq') INTO :h_id; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL INSERT INTO cm_counter (cnt_id, cnt_val, biz_date) + VALUES (:h_id, :h_amt, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("cmdb_counter_add FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_id; +} diff --git a/app/src/cm/dbio/cm_dbio.h b/app/src/cm/dbio/cm_dbio.h index e7db49c..cecd1e6 100644 --- a/app/src/cm/dbio/cm_dbio.h +++ b/app/src/cm/dbio/cm_dbio.h @@ -30,4 +30,30 @@ int cmdb_is_holiday(const char *date); /* 공통 로그 (cm_log) */ long cmdb_log(const char *svc, const char *level, const char *msg, const char *bizdate); + +/* ----- expanded query/analytics dbio (each in its own cm_*_dbio.pgc) ----- */ +long cmdb_fx_rate_sum(const char *bizdate); /* 기준일 환율 합계. */ +long cmdb_log_level_count(const char *status); /* 로그 레벨별 건수. */ +long cmdb_fx_spread(const char *bizdate); /* 환율 최대-최소 스프레드. */ +long cmdb_log_err_ratio(const char *bizdate); /* 오류 로그 비율(bps). */ +long cmdb_log_svc_rollup(const char *bizdate); /* 서비스별 로그 커서 누적. */ +long cmdb_fx_avg(const char *bizdate); /* 환율 평균. */ +long cmdb_log_err_idsum(const char *bizdate); /* 오류 로그 ID 조건 합. */ +long cmdb_log_purge_debug(const char *bizdate); /* 디버그 로그 삭제. */ +long cmdb_log_archive(const char *bizdate); /* 정보 로그 보관(A) 전이. */ +int cmdb_holiday_present(const char *key); /* 휴일 등록 존재 여부. */ +int cmdb_log_message(long id, char *out); /* 로그 ID 메시지 조회. */ +long cmdb_fx_sq_sum(const char *bizdate); /* 환율 제곱 가중 합(커서). */ +long cmdb_fx_peak_count(const char *bizdate); /* 최고 환율 건수. */ +long cmdb_code_name_like(const char *pat); /* 코드명 패턴 검색 건수. */ +long cmdb_log_distinct_svc(const char *bizdate); /* 고유 서비스 수. */ +long cmdb_fx_on_holiday(const char *bizdate); /* 휴일자 환율 합(조인). */ +long cmdb_fx_min(const char *bizdate); /* 최소 환율. */ +long cmdb_fx_high_pct(const char *bizdate); /* 고환율 비율(%). */ +int cmdb_param_is_yes(const char *key); /* 파라미터 Y 여부. */ +long cmdb_seq_bump_active(const char *bizdate); /* 활동 서비스 시퀀스 증가. */ +long cmdb_counter_add(const char *bizdate, long amount); /* 카운터 채번 후 적재. */ +long cmdb_netting_gross(const char *bizdate); /* 차변+대변 총액. */ +long cmdb_fx_positive_count(const char *bizdate); /* 환율>0 건수(커서). */ + #endif /* CM_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_avg_dbio.h b/app/src/cm/dbio/cm_fx_avg_dbio.h new file mode 100644 index 0000000..41d0b68 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_avg_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_avg_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_avg_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_AVG_DBIO_H +#define CM_FX_AVG_DBIO_H + +/* 환율 평균. */ +long cmdb_fx_avg(const char *bizdate); + +#endif /* CM_FX_AVG_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_avg_dbio.pgc b/app/src/cm/dbio/cm_fx_avg_dbio.pgc new file mode 100644 index 0000000..51fbfd7 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_avg_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_fx_avg_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_avg_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 환율 평균. + */ +#include +#include +#include "cm_fx_avg_dbio.h" + +long cmdb_fx_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(rate_bps),0), count(*) INTO :h_sum, :h_cnt + FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_cnt <= 0) return 0; + return h_sum / h_cnt; +} diff --git a/app/src/cm/dbio/cm_fx_highpct_dbio.h b/app/src/cm/dbio/cm_fx_highpct_dbio.h new file mode 100644 index 0000000..7d9aea6 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_highpct_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_highpct_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_highpct_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_HIGHPCT_DBIO_H +#define CM_FX_HIGHPCT_DBIO_H + +/* 고환율 비율(%). */ +long cmdb_fx_high_pct(const char *bizdate); + +#endif /* CM_FX_HIGHPCT_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_highpct_dbio.pgc b/app/src/cm/dbio/cm_fx_highpct_dbio.pgc new file mode 100644 index 0000000..a07c3df --- /dev/null +++ b/app/src/cm/dbio/cm_fx_highpct_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_fx_highpct_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_highpct_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 고환율 비율(%). + */ +#include +#include +#include "cm_fx_highpct_dbio.h" + +long cmdb_fx_high_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_hi = 0, h_all = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE WHEN rate_bps > 1000000 THEN 1 ELSE 0 END),0), count(*) + INTO :h_hi, :h_all FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_all <= 0) return 0; + return h_hi * 100 / h_all; +} diff --git a/app/src/cm/dbio/cm_fx_min_dbio.h b/app/src/cm/dbio/cm_fx_min_dbio.h new file mode 100644 index 0000000..b670e9c --- /dev/null +++ b/app/src/cm/dbio/cm_fx_min_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_min_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_min_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_MIN_DBIO_H +#define CM_FX_MIN_DBIO_H + +/* 최소 환율. */ +long cmdb_fx_min(const char *bizdate); + +#endif /* CM_FX_MIN_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_min_dbio.pgc b/app/src/cm/dbio/cm_fx_min_dbio.pgc new file mode 100644 index 0000000..6550cc4 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_min_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_fx_min_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_min_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 최소 환율. + */ +#include +#include +#include "cm_fx_min_dbio.h" + +long cmdb_fx_min(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT min(rate_bps) INTO :h_v FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("cmdb_fx_min FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/cm/dbio/cm_fx_onhol_dbio.h b/app/src/cm/dbio/cm_fx_onhol_dbio.h new file mode 100644 index 0000000..45176e1 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_onhol_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_onhol_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_onhol_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_ONHOL_DBIO_H +#define CM_FX_ONHOL_DBIO_H + +/* 휴일자 환율 합(조인). */ +long cmdb_fx_on_holiday(const char *bizdate); + +#endif /* CM_FX_ONHOL_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_onhol_dbio.pgc b/app/src/cm/dbio/cm_fx_onhol_dbio.pgc new file mode 100644 index 0000000..cab2acb --- /dev/null +++ b/app/src/cm/dbio/cm_fx_onhol_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_fx_onhol_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_onhol_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 휴일자 환율 합(조인). + */ +#include +#include +#include "cm_fx_onhol_dbio.h" + +long cmdb_fx_on_holiday(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(a.rate_bps),0) INTO :h_v + FROM cm_fx_rate a JOIN cm_holiday b ON a.base_date = b.holi_date + WHERE a.base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_fx_peak_dbio.h b/app/src/cm/dbio/cm_fx_peak_dbio.h new file mode 100644 index 0000000..7571196 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_peak_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_peak_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_peak_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_PEAK_DBIO_H +#define CM_FX_PEAK_DBIO_H + +/* 최고 환율 건수. */ +long cmdb_fx_peak_count(const char *bizdate); + +#endif /* CM_FX_PEAK_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_peak_dbio.pgc b/app/src/cm/dbio/cm_fx_peak_dbio.pgc new file mode 100644 index 0000000..c79ad32 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_peak_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cm_fx_peak_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_peak_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 최고 환율 건수. + */ +#include +#include +#include "cm_fx_peak_dbio.h" + +long cmdb_fx_peak_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_peak = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(rate_bps),0) INTO :h_peak FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_cnt FROM cm_fx_rate + WHERE base_date = :h_bizdate AND rate_bps = :h_peak; + if (sqlca.sqlcode < 0) return -1; + return h_cnt; +} diff --git a/app/src/cm/dbio/cm_fx_pos_dbio.h b/app/src/cm/dbio/cm_fx_pos_dbio.h new file mode 100644 index 0000000..d94fd00 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_pos_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_pos_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_pos_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_POS_DBIO_H +#define CM_FX_POS_DBIO_H + +/* 환율>0 건수(커서). */ +long cmdb_fx_positive_count(const char *bizdate); + +#endif /* CM_FX_POS_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_pos_dbio.pgc b/app/src/cm/dbio/cm_fx_pos_dbio.pgc new file mode 100644 index 0000000..3ca88d7 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_pos_dbio.pgc @@ -0,0 +1,29 @@ +/* + * cm_fx_pos_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_pos_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 환율>0 건수(커서). + */ +#include +#include +#include "cm_fx_pos_dbio.h" + +long cmdb_fx_positive_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_x = 0, k = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT rate_bps FROM cm_fx_rate WHERE base_date = :h_bizdate; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH qc INTO :h_x; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + if (h_x > 0) k++; + } + EXEC SQL CLOSE qc; + return k; +} diff --git a/app/src/cm/dbio/cm_fx_spread_dbio.h b/app/src/cm/dbio/cm_fx_spread_dbio.h new file mode 100644 index 0000000..c9f5755 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_spread_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_spread_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_spread_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_SPREAD_DBIO_H +#define CM_FX_SPREAD_DBIO_H + +/* 환율 최대-최소 스프레드. */ +long cmdb_fx_spread(const char *bizdate); + +#endif /* CM_FX_SPREAD_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_spread_dbio.pgc b/app/src/cm/dbio/cm_fx_spread_dbio.pgc new file mode 100644 index 0000000..d5acb92 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_spread_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_fx_spread_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_spread_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 환율 최대-최소 스프레드. + */ +#include +#include +#include "cm_fx_spread_dbio.h" + +long cmdb_fx_spread(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(rate_bps),0), coalesce(min(rate_bps),0) + INTO :h_max, :h_min FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_max - h_min; +} diff --git a/app/src/cm/dbio/cm_fx_sq_dbio.h b/app/src/cm/dbio/cm_fx_sq_dbio.h new file mode 100644 index 0000000..15b3c77 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_sq_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_sq_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_sq_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_SQ_DBIO_H +#define CM_FX_SQ_DBIO_H + +/* 환율 제곱 가중 합(커서). */ +long cmdb_fx_sq_sum(const char *bizdate); + +#endif /* CM_FX_SQ_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_sq_dbio.pgc b/app/src/cm/dbio/cm_fx_sq_dbio.pgc new file mode 100644 index 0000000..990d74c --- /dev/null +++ b/app/src/cm/dbio/cm_fx_sq_dbio.pgc @@ -0,0 +1,29 @@ +/* + * cm_fx_sq_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_sq_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 환율 제곱 가중 합(커서). + */ +#include +#include +#include "cm_fx_sq_dbio.h" + +long cmdb_fx_sq_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, acc = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT rate_bps, rate_bps FROM cm_fx_rate WHERE base_date = :h_bizdate; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH qc INTO :h_a, :h_b; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + acc += h_a * h_b; + } + EXEC SQL CLOSE qc; + return acc; +} diff --git a/app/src/cm/dbio/cm_fx_sum_dbio.h b/app/src/cm/dbio/cm_fx_sum_dbio.h new file mode 100644 index 0000000..e3ee4c1 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_sum_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_fx_sum_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_fx_sum_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FX_SUM_DBIO_H +#define CM_FX_SUM_DBIO_H + +/* 기준일 환율 합계. */ +long cmdb_fx_rate_sum(const char *bizdate); + +#endif /* CM_FX_SUM_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fx_sum_dbio.pgc b/app/src/cm/dbio/cm_fx_sum_dbio.pgc new file mode 100644 index 0000000..457b078 --- /dev/null +++ b/app/src/cm/dbio/cm_fx_sum_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_fx_sum_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fx_sum_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 기준일 환율 합계. + */ +#include +#include +#include "cm_fx_sum_dbio.h" + +long cmdb_fx_rate_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(rate_bps),0) INTO :h_v FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("cmdb_fx_rate_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/cm/dbio/cm_holiday_ex_dbio.h b/app/src/cm/dbio/cm_holiday_ex_dbio.h new file mode 100644 index 0000000..05b7179 --- /dev/null +++ b/app/src/cm/dbio/cm_holiday_ex_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_holiday_ex_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_holiday_ex_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_HOLIDAY_EX_DBIO_H +#define CM_HOLIDAY_EX_DBIO_H + +/* 휴일 등록 존재 여부. */ +int cmdb_holiday_present(const char *key); + +#endif /* CM_HOLIDAY_EX_DBIO_H */ diff --git a/app/src/cm/dbio/cm_holiday_ex_dbio.pgc b/app/src/cm/dbio/cm_holiday_ex_dbio.pgc new file mode 100644 index 0000000..ffafa5b --- /dev/null +++ b/app/src/cm/dbio/cm_holiday_ex_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_holiday_ex_dbio.pgc - cm 모듈 DB 접근 함수 (cm_holiday_ex_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 휴일 등록 존재 여부. + */ +#include +#include +#include "cm_holiday_ex_dbio.h" + +int cmdb_holiday_present(const char *key) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_key[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_key, key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_holiday WHERE holi_date = :h_key; + if (sqlca.sqlcode < 0) return -1; + return h_v > 0 ? 1 : 0; +} diff --git a/app/src/cm/dbio/cm_log_archive_dbio.h b/app/src/cm/dbio/cm_log_archive_dbio.h new file mode 100644 index 0000000..c6d272e --- /dev/null +++ b/app/src/cm/dbio/cm_log_archive_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_archive_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_archive_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_ARCHIVE_DBIO_H +#define CM_LOG_ARCHIVE_DBIO_H + +/* 정보 로그 보관(A) 전이. */ +long cmdb_log_archive(const char *bizdate); + +#endif /* CM_LOG_ARCHIVE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_archive_dbio.pgc b/app/src/cm/dbio/cm_log_archive_dbio.pgc new file mode 100644 index 0000000..562c35f --- /dev/null +++ b/app/src/cm/dbio/cm_log_archive_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_log_archive_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_archive_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 정보 로그 보관(A) 전이. + */ +#include +#include +#include "cm_log_archive_dbio.h" + +long cmdb_log_archive(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE cm_log SET log_level = 'A', updated_at = now() + WHERE biz_date = :h_bizdate AND log_level = 'I'; + if (sqlca.sqlcode < 0) { userlog("cmdb_log_archive FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/cm/dbio/cm_log_dsvc_dbio.h b/app/src/cm/dbio/cm_log_dsvc_dbio.h new file mode 100644 index 0000000..022c191 --- /dev/null +++ b/app/src/cm/dbio/cm_log_dsvc_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_dsvc_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_dsvc_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_DSVC_DBIO_H +#define CM_LOG_DSVC_DBIO_H + +/* 고유 서비스 수. */ +long cmdb_log_distinct_svc(const char *bizdate); + +#endif /* CM_LOG_DSVC_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_dsvc_dbio.pgc b/app/src/cm/dbio/cm_log_dsvc_dbio.pgc new file mode 100644 index 0000000..65c1962 --- /dev/null +++ b/app/src/cm/dbio/cm_log_dsvc_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_log_dsvc_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_dsvc_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 고유 서비스 수. + */ +#include +#include +#include "cm_log_dsvc_dbio.h" + +long cmdb_log_distinct_svc(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT svc_name) INTO :h_v FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_log_errsum_dbio.h b/app/src/cm/dbio/cm_log_errsum_dbio.h new file mode 100644 index 0000000..7f435c8 --- /dev/null +++ b/app/src/cm/dbio/cm_log_errsum_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_errsum_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_errsum_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_ERRSUM_DBIO_H +#define CM_LOG_ERRSUM_DBIO_H + +/* 오류 로그 ID 조건 합. */ +long cmdb_log_err_idsum(const char *bizdate); + +#endif /* CM_LOG_ERRSUM_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_errsum_dbio.pgc b/app/src/cm/dbio/cm_log_errsum_dbio.pgc new file mode 100644 index 0000000..cdf3900 --- /dev/null +++ b/app/src/cm/dbio/cm_log_errsum_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_log_errsum_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_errsum_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 오류 로그 ID 조건 합. + */ +#include +#include +#include "cm_log_errsum_dbio.h" + +long cmdb_log_err_idsum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE WHEN log_level = 'E' THEN log_id ELSE 0 END),0) + INTO :h_v FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_log_level_dbio.h b/app/src/cm/dbio/cm_log_level_dbio.h new file mode 100644 index 0000000..1785c9c --- /dev/null +++ b/app/src/cm/dbio/cm_log_level_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_level_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_level_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_LEVEL_DBIO_H +#define CM_LOG_LEVEL_DBIO_H + +/* 로그 레벨별 건수. */ +long cmdb_log_level_count(const char *status); + +#endif /* CM_LOG_LEVEL_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_level_dbio.pgc b/app/src/cm/dbio/cm_log_level_dbio.pgc new file mode 100644 index 0000000..01b4dc1 --- /dev/null +++ b/app/src/cm/dbio/cm_log_level_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_log_level_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_level_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 로그 레벨별 건수. + */ +#include +#include +#include "cm_log_level_dbio.h" + +long cmdb_log_level_count(const char *status) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_st[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_st, status, sizeof(h_st)-1); h_st[sizeof(h_st)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_log WHERE log_level = :h_st; + if (sqlca.sqlcode < 0) { userlog("cmdb_log_level_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/cm/dbio/cm_log_message_dbio.h b/app/src/cm/dbio/cm_log_message_dbio.h new file mode 100644 index 0000000..529f032 --- /dev/null +++ b/app/src/cm/dbio/cm_log_message_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_message_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_message_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_MESSAGE_DBIO_H +#define CM_LOG_MESSAGE_DBIO_H + +/* 로그 ID 메시지 조회. */ +int cmdb_log_message(long id, char *out); + +#endif /* CM_LOG_MESSAGE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_message_dbio.pgc b/app/src/cm/dbio/cm_log_message_dbio.pgc new file mode 100644 index 0000000..c81a51f --- /dev/null +++ b/app/src/cm/dbio/cm_log_message_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_log_message_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_message_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 로그 ID 메시지 조회. + */ +#include +#include +#include "cm_log_message_dbio.h" + +int cmdb_log_message(long id, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id = id; + char h_s[64]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT msg INTO :h_s FROM cm_log WHERE log_id = :h_id; + if (sqlca.sqlcode == 100) { out[0] = 0; return -1; } + if (sqlca.sqlcode < 0) { userlog("cmdb_log_message FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strncpy(out, h_s, 63); out[63] = 0; + return 0; +} diff --git a/app/src/cm/dbio/cm_log_purge_dbio.h b/app/src/cm/dbio/cm_log_purge_dbio.h new file mode 100644 index 0000000..00474f3 --- /dev/null +++ b/app/src/cm/dbio/cm_log_purge_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_purge_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_purge_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_PURGE_DBIO_H +#define CM_LOG_PURGE_DBIO_H + +/* 디버그 로그 삭제. */ +long cmdb_log_purge_debug(const char *bizdate); + +#endif /* CM_LOG_PURGE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_purge_dbio.pgc b/app/src/cm/dbio/cm_log_purge_dbio.pgc new file mode 100644 index 0000000..e484f40 --- /dev/null +++ b/app/src/cm/dbio/cm_log_purge_dbio.pgc @@ -0,0 +1,19 @@ +/* + * cm_log_purge_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_purge_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 디버그 로그 삭제. + */ +#include +#include +#include "cm_log_purge_dbio.h" + +long cmdb_log_purge_debug(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 DELETE FROM cm_log WHERE biz_date = :h_bizdate AND log_level = 'D'; + if (sqlca.sqlcode < 0) { userlog("cmdb_log_purge_debug FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/cm/dbio/cm_log_ratio_dbio.h b/app/src/cm/dbio/cm_log_ratio_dbio.h new file mode 100644 index 0000000..0f91c12 --- /dev/null +++ b/app/src/cm/dbio/cm_log_ratio_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_ratio_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_ratio_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_RATIO_DBIO_H +#define CM_LOG_RATIO_DBIO_H + +/* 오류 로그 비율(bps). */ +long cmdb_log_err_ratio(const char *bizdate); + +#endif /* CM_LOG_RATIO_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_ratio_dbio.pgc b/app/src/cm/dbio/cm_log_ratio_dbio.pgc new file mode 100644 index 0000000..48f0f71 --- /dev/null +++ b/app/src/cm/dbio/cm_log_ratio_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cm_log_ratio_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_ratio_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 오류 로그 비율(bps). + */ +#include +#include +#include "cm_log_ratio_dbio.h" + +long cmdb_log_err_ratio(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0; + 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_a FROM cm_log WHERE biz_date = :h_bizdate AND log_level = 'E'; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_b FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_b <= 0) return 0; + return h_a * 10000 / h_b; +} diff --git a/app/src/cm/dbio/cm_log_rollup_dbio.h b/app/src/cm/dbio/cm_log_rollup_dbio.h new file mode 100644 index 0000000..0ca0b84 --- /dev/null +++ b/app/src/cm/dbio/cm_log_rollup_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_log_rollup_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_log_rollup_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOG_ROLLUP_DBIO_H +#define CM_LOG_ROLLUP_DBIO_H + +/* 서비스별 로그 커서 누적. */ +long cmdb_log_svc_rollup(const char *bizdate); + +#endif /* CM_LOG_ROLLUP_DBIO_H */ diff --git a/app/src/cm/dbio/cm_log_rollup_dbio.pgc b/app/src/cm/dbio/cm_log_rollup_dbio.pgc new file mode 100644 index 0000000..dde7918 --- /dev/null +++ b/app/src/cm/dbio/cm_log_rollup_dbio.pgc @@ -0,0 +1,30 @@ +/* + * cm_log_rollup_dbio.pgc - cm 모듈 DB 접근 함수 (cm_log_rollup_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 서비스별 로그 커서 누적. + */ +#include +#include +#include "cm_log_rollup_dbio.h" + +long cmdb_log_svc_rollup(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT svc_name, coalesce(sum(log_id),0) FROM cm_log + WHERE biz_date = :h_bizdate GROUP BY svc_name; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) { userlog("cmdb_log_svc_rollup OPEN [%d]", sqlca.sqlcode); return -1; } + for (;;) { + EXEC SQL FETCH qc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + total += h_amt; + } + EXEC SQL CLOSE qc; + return total; +} diff --git a/app/src/cm/dbio/cm_netting_dbio.h b/app/src/cm/dbio/cm_netting_dbio.h new file mode 100644 index 0000000..afe560d --- /dev/null +++ b/app/src/cm/dbio/cm_netting_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_netting_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_netting_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_NETTING_DBIO_H +#define CM_NETTING_DBIO_H + +/* 차변+대변 총액. */ +long cmdb_netting_gross(const char *bizdate); + +#endif /* CM_NETTING_DBIO_H */ diff --git a/app/src/cm/dbio/cm_netting_dbio.pgc b/app/src/cm/dbio/cm_netting_dbio.pgc new file mode 100644 index 0000000..c442503 --- /dev/null +++ b/app/src/cm/dbio/cm_netting_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_netting_dbio.pgc - cm 모듈 DB 접근 함수 (cm_netting_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 차변+대변 총액. + */ +#include +#include +#include "cm_netting_dbio.h" + +long cmdb_netting_gross(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(debit),0) + coalesce(sum(credit),0) INTO :h_v + FROM cm_netting WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_param_yes_dbio.h b/app/src/cm/dbio/cm_param_yes_dbio.h new file mode 100644 index 0000000..55a1a74 --- /dev/null +++ b/app/src/cm/dbio/cm_param_yes_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_param_yes_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_param_yes_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_PARAM_YES_DBIO_H +#define CM_PARAM_YES_DBIO_H + +/* 파라미터 Y 여부. */ +int cmdb_param_is_yes(const char *key); + +#endif /* CM_PARAM_YES_DBIO_H */ diff --git a/app/src/cm/dbio/cm_param_yes_dbio.pgc b/app/src/cm/dbio/cm_param_yes_dbio.pgc new file mode 100644 index 0000000..589943d --- /dev/null +++ b/app/src/cm/dbio/cm_param_yes_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_param_yes_dbio.pgc - cm 모듈 DB 접근 함수 (cm_param_yes_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 파라미터 Y 여부. + */ +#include +#include +#include "cm_param_yes_dbio.h" + +int cmdb_param_is_yes(const char *key) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_key[32], h_st[16]; + 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_st FROM cm_param WHERE param_key = :h_key; + if (sqlca.sqlcode == 100) return -1; + if (sqlca.sqlcode < 0) return -1; + if (strcmp(h_st, "Y") == 0) return 1; + return 0; +} diff --git a/app/src/cm/dbio/cm_seq_bump_dbio.h b/app/src/cm/dbio/cm_seq_bump_dbio.h new file mode 100644 index 0000000..3fc105b --- /dev/null +++ b/app/src/cm/dbio/cm_seq_bump_dbio.h @@ -0,0 +1,12 @@ +/* + * cm_seq_bump_dbio.h - cm 모듈 DB 접근 계층 copybook (cm_seq_bump_dbio, part of libcmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_SEQ_BUMP_DBIO_H +#define CM_SEQ_BUMP_DBIO_H + +/* 활동 서비스 시퀀스 증가. */ +long cmdb_seq_bump_active(const char *bizdate); + +#endif /* CM_SEQ_BUMP_DBIO_H */ diff --git a/app/src/cm/dbio/cm_seq_bump_dbio.pgc b/app/src/cm/dbio/cm_seq_bump_dbio.pgc new file mode 100644 index 0000000..fe470da --- /dev/null +++ b/app/src/cm/dbio/cm_seq_bump_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cm_seq_bump_dbio.pgc - cm 모듈 DB 접근 함수 (cm_seq_bump_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 활동 서비스 시퀀스 증가. + */ +#include +#include +#include "cm_seq_bump_dbio.h" + +long cmdb_seq_bump_active(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE cm_seq SET seq_val = seq_val + 1 + WHERE seq_name IN (SELECT svc_name FROM cm_log WHERE biz_date = :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("cmdb_seq_bump_active FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/cm/run/run_archive.sh b/app/src/cm/run/run_archive.sh new file mode 100755 index 0000000..4faeaa8 --- /dev/null +++ b/app/src/cm/run/run_archive.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_archive.sh - 정보 로그 보관 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_archive.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_archive.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_archive_batch "$BIZDATE" diff --git a/app/src/cm/run/run_bizday.sh b/app/src/cm/run/run_bizday.sh new file mode 100755 index 0000000..ff7c2b7 --- /dev/null +++ b/app/src/cm/run/run_bizday.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_bizday.sh - 영업일 롤 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_bizday.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_bizday.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_bizday_roll_batch diff --git a/app/src/cm/run/run_dbgpurge.sh b/app/src/cm/run/run_dbgpurge.sh new file mode 100755 index 0000000..8c9782c --- /dev/null +++ b/app/src/cm/run/run_dbgpurge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_dbgpurge.sh - 디버그 로그 삭제 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_dbgpurge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_dbgpurge.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_dbgpurge_batch "$BIZDATE" diff --git a/app/src/cm/run/run_distinct.sh b/app/src/cm/run/run_distinct.sh new file mode 100755 index 0000000..9ff9023 --- /dev/null +++ b/app/src/cm/run/run_distinct.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_distinct.sh - 고유 서비스 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_distinct.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_distinct.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_distinct_batch "$BIZDATE" diff --git a/app/src/cm/run/run_eod.sh b/app/src/cm/run/run_eod.sh new file mode 100755 index 0000000..66a0754 --- /dev/null +++ b/app/src/cm/run/run_eod.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_eod.sh - cm 일마감 배치 일괄 실행 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_eod.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_eod.sh] BIZDATE=$BIZDATE" +for b in cm_logcnt_batch cm_logroll_batch cm_svcacc_batch cm_fxsum_batch cm_fxspread_batch \ + cm_errbps_batch cm_levelstat_batch cm_marker_batch cm_archive_batch cm_purge_batch; do + echo "--- $b $BIZDATE ---"; "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/cm/run/run_errbps.sh b/app/src/cm/run/run_errbps.sh new file mode 100755 index 0000000..82d53ae --- /dev/null +++ b/app/src/cm/run/run_errbps.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_errbps.sh - 오류 로그율 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_errbps.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_errbps.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_errbps_batch "$BIZDATE" diff --git a/app/src/cm/run/run_fxjoin.sh b/app/src/cm/run/run_fxjoin.sh new file mode 100755 index 0000000..317fd14 --- /dev/null +++ b/app/src/cm/run/run_fxjoin.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_fxjoin.sh - 휴일 환율 조인 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fxjoin.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_fxjoin.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_fxjoin_batch "$BIZDATE" diff --git a/app/src/cm/run/run_fxspread.sh b/app/src/cm/run/run_fxspread.sh new file mode 100755 index 0000000..d5c4088 --- /dev/null +++ b/app/src/cm/run/run_fxspread.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_fxspread.sh - 환율 스프레드 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fxspread.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_fxspread.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_fxspread_batch "$BIZDATE" diff --git a/app/src/cm/run/run_fxsum.sh b/app/src/cm/run/run_fxsum.sh new file mode 100755 index 0000000..e46e7c5 --- /dev/null +++ b/app/src/cm/run/run_fxsum.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_fxsum.sh - 환율 합계 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fxsum.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_fxsum.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_fxsum_batch "$BIZDATE" diff --git a/app/src/cm/run/run_levelstat.sh b/app/src/cm/run/run_levelstat.sh new file mode 100755 index 0000000..96d713e --- /dev/null +++ b/app/src/cm/run/run_levelstat.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_levelstat.sh - 레벨별 통계 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_levelstat.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_levelstat.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_levelstat_batch "$BIZDATE" diff --git a/app/src/cm/run/run_logcnt.sh b/app/src/cm/run/run_logcnt.sh new file mode 100755 index 0000000..06c8b60 --- /dev/null +++ b/app/src/cm/run/run_logcnt.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_logcnt.sh - 로그 건수 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_logcnt.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_logcnt.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_logcnt_batch "$BIZDATE" diff --git a/app/src/cm/run/run_logroll.sh b/app/src/cm/run/run_logroll.sh new file mode 100755 index 0000000..19f21b8 --- /dev/null +++ b/app/src/cm/run/run_logroll.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_logroll.sh - 서비스 로그 롤업 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_logroll.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_logroll.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_logroll_batch "$BIZDATE" diff --git a/app/src/cm/run/run_marker.sh b/app/src/cm/run/run_marker.sh new file mode 100755 index 0000000..238c789 --- /dev/null +++ b/app/src/cm/run/run_marker.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_marker.sh - EOD 마커 채번 적재 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_marker.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_marker.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_marker_batch "$BIZDATE" diff --git a/app/src/cm/run/run_purge.sh b/app/src/cm/run/run_purge.sh new file mode 100755 index 0000000..c827af7 --- /dev/null +++ b/app/src/cm/run/run_purge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_purge.sh - 로그 보존기간 정리 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_purge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_purge.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_purge_batch "$BIZDATE" diff --git a/app/src/cm/run/run_reconf.sh b/app/src/cm/run/run_reconf.sh new file mode 100755 index 0000000..c1e4f29 --- /dev/null +++ b/app/src/cm/run/run_reconf.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_reconf.sh - 경고 로그 재분류 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_reconf.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_reconf.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_reconf_batch "$BIZDATE" diff --git a/app/src/cm/run/run_svcacc.sh b/app/src/cm/run/run_svcacc.sh new file mode 100755 index 0000000..c175932 --- /dev/null +++ b/app/src/cm/run/run_svcacc.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_svcacc.sh - 서비스 누적 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_svcacc.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_svcacc.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_svcacc_batch "$BIZDATE" diff --git a/app/src/cm/run/run_svcsnap.sh b/app/src/cm/run/run_svcsnap.sh new file mode 100755 index 0000000..2ab7c1c --- /dev/null +++ b/app/src/cm/run/run_svcsnap.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_svcsnap.sh - 서비스 스냅샷 적재 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_svcsnap.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_svcsnap.sh] BIZDATE=$BIZDATE" +exec /app/bin/cm_svcsnap_batch "$BIZDATE" diff --git a/app/src/cm/svc/CM_ADDDAYS.h b/app/src/cm/svc/CM_ADDDAYS.h new file mode 100644 index 0000000..d9a6a64 --- /dev/null +++ b/app/src/cm/svc/CM_ADDDAYS.h @@ -0,0 +1,12 @@ +/* + * CM_ADDDAYS.h - cm (공통) 서비스 CM_ADDDAYS 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_ADDDAYS_H +#define CM_ADDDAYS_H + +#include "cm_svc.h" + +void CM_ADDDAYS(TPSVCINFO *p); + +#endif /* CM_ADDDAYS_H */ diff --git a/app/src/cm/svc/CM_ADDDAYS.pgc b/app/src/cm/svc/CM_ADDDAYS.pgc new file mode 100644 index 0000000..2ce0d98 --- /dev/null +++ b/app/src/cm/svc/CM_ADDDAYS.pgc @@ -0,0 +1,37 @@ +/* + * CM_ADDDAYS.pgc - cm 모듈 서비스 CM_ADDDAYS (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_ADDDAYS.h" + +/* ----- service-local pure helper(s) ----- */ +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]; +} + + +/* 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); +} diff --git a/app/src/cm/svc/CM_AMT_ROUND.h b/app/src/cm/svc/CM_AMT_ROUND.h new file mode 100644 index 0000000..b5bcc2a --- /dev/null +++ b/app/src/cm/svc/CM_AMT_ROUND.h @@ -0,0 +1,12 @@ +/* + * CM_AMT_ROUND.h - cm (공통) 서비스 CM_AMT_ROUND 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_AMT_ROUND_H +#define CM_AMT_ROUND_H + +#include "cm_svc.h" + +void CM_AMT_ROUND(TPSVCINFO *p); + +#endif /* CM_AMT_ROUND_H */ diff --git a/app/src/cm/svc/CM_AMT_ROUND.pgc b/app/src/cm/svc/CM_AMT_ROUND.pgc new file mode 100644 index 0000000..5415c42 --- /dev/null +++ b/app/src/cm/svc/CM_AMT_ROUND.pgc @@ -0,0 +1,17 @@ +/* + * CM_AMT_ROUND.pgc - cm 모듈 서비스 CM_AMT_ROUND (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_AMT_ROUND.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_AMT_SPLIT.h b/app/src/cm/svc/CM_AMT_SPLIT.h new file mode 100644 index 0000000..6ada2b9 --- /dev/null +++ b/app/src/cm/svc/CM_AMT_SPLIT.h @@ -0,0 +1,12 @@ +/* + * CM_AMT_SPLIT.h - cm (공통) 서비스 CM_AMT_SPLIT 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_AMT_SPLIT_H +#define CM_AMT_SPLIT_H + +#include "cm_svc.h" + +void CM_AMT_SPLIT(TPSVCINFO *p); + +#endif /* CM_AMT_SPLIT_H */ diff --git a/app/src/cm/svc/CM_AMT_SPLIT.pgc b/app/src/cm/svc/CM_AMT_SPLIT.pgc new file mode 100644 index 0000000..8330a16 --- /dev/null +++ b/app/src/cm/svc/CM_AMT_SPLIT.pgc @@ -0,0 +1,20 @@ +/* + * CM_AMT_SPLIT.pgc - cm 모듈 서비스 CM_AMT_SPLIT (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_AMT_SPLIT.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_BIZDAY.h b/app/src/cm/svc/CM_BIZDAY.h new file mode 100644 index 0000000..33f74d5 --- /dev/null +++ b/app/src/cm/svc/CM_BIZDAY.h @@ -0,0 +1,12 @@ +/* + * CM_BIZDAY.h - cm (공통) 서비스 CM_BIZDAY 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_BIZDAY_H +#define CM_BIZDAY_H + +#include "cm_svc.h" + +void CM_BIZDAY(TPSVCINFO *p); + +#endif /* CM_BIZDAY_H */ diff --git a/app/src/cm/svc/CM_BIZDAY.pgc b/app/src/cm/svc/CM_BIZDAY.pgc new file mode 100644 index 0000000..fb4992f --- /dev/null +++ b/app/src/cm/svc/CM_BIZDAY.pgc @@ -0,0 +1,16 @@ +/* + * CM_BIZDAY.pgc - cm 모듈 서비스 CM_BIZDAY (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_BIZDAY.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_CACHE_INV.h b/app/src/cm/svc/CM_CACHE_INV.h new file mode 100644 index 0000000..ab28cba --- /dev/null +++ b/app/src/cm/svc/CM_CACHE_INV.h @@ -0,0 +1,12 @@ +/* + * CM_CACHE_INV.h - cm (공통) 서비스 CM_CACHE_INV 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CACHE_INV_H +#define CM_CACHE_INV_H + +#include "cm_svc.h" + +void CM_CACHE_INV(TPSVCINFO *p); + +#endif /* CM_CACHE_INV_H */ diff --git a/app/src/cm/svc/CM_CACHE_INV.pgc b/app/src/cm/svc/CM_CACHE_INV.pgc new file mode 100644 index 0000000..a69c159 --- /dev/null +++ b/app/src/cm/svc/CM_CACHE_INV.pgc @@ -0,0 +1,18 @@ +/* + * CM_CACHE_INV.pgc - cm 모듈 서비스 CM_CACHE_INV (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CACHE_INV.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_CACHE_LOAD.h b/app/src/cm/svc/CM_CACHE_LOAD.h new file mode 100644 index 0000000..d87bfc5 --- /dev/null +++ b/app/src/cm/svc/CM_CACHE_LOAD.h @@ -0,0 +1,12 @@ +/* + * CM_CACHE_LOAD.h - cm (공통) 서비스 CM_CACHE_LOAD 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CACHE_LOAD_H +#define CM_CACHE_LOAD_H + +#include "cm_svc.h" + +void CM_CACHE_LOAD(TPSVCINFO *p); + +#endif /* CM_CACHE_LOAD_H */ diff --git a/app/src/cm/svc/CM_CACHE_LOAD.pgc b/app/src/cm/svc/CM_CACHE_LOAD.pgc new file mode 100644 index 0000000..35936f7 --- /dev/null +++ b/app/src/cm/svc/CM_CACHE_LOAD.pgc @@ -0,0 +1,20 @@ +/* + * CM_CACHE_LOAD.pgc - cm 모듈 서비스 CM_CACHE_LOAD (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CACHE_LOAD.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_CHECKDIGIT.h b/app/src/cm/svc/CM_CHECKDIGIT.h new file mode 100644 index 0000000..7608e91 --- /dev/null +++ b/app/src/cm/svc/CM_CHECKDIGIT.h @@ -0,0 +1,12 @@ +/* + * CM_CHECKDIGIT.h - cm (공통) 서비스 CM_CHECKDIGIT 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CHECKDIGIT_H +#define CM_CHECKDIGIT_H + +#include "cm_svc.h" + +void CM_CHECKDIGIT(TPSVCINFO *p); + +#endif /* CM_CHECKDIGIT_H */ diff --git a/app/src/cm/svc/CM_CHECKDIGIT.pgc b/app/src/cm/svc/CM_CHECKDIGIT.pgc new file mode 100644 index 0000000..b701906 --- /dev/null +++ b/app/src/cm/svc/CM_CHECKDIGIT.pgc @@ -0,0 +1,27 @@ +/* + * CM_CHECKDIGIT.pgc - cm 모듈 서비스 CM_CHECKDIGIT (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CHECKDIGIT.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_CODE.h b/app/src/cm/svc/CM_CODE.h new file mode 100644 index 0000000..f4b5af0 --- /dev/null +++ b/app/src/cm/svc/CM_CODE.h @@ -0,0 +1,12 @@ +/* + * CM_CODE.h - cm (공통) 서비스 CM_CODE 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CODE_H +#define CM_CODE_H + +#include "cm_svc.h" + +void CM_CODE(TPSVCINFO *p); + +#endif /* CM_CODE_H */ diff --git a/app/src/cm/svc/CM_CODE.pgc b/app/src/cm/svc/CM_CODE.pgc new file mode 100644 index 0000000..dd12a33 --- /dev/null +++ b/app/src/cm/svc/CM_CODE.pgc @@ -0,0 +1,20 @@ +/* + * CM_CODE.pgc - cm 모듈 서비스 CM_CODE (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODE.h" + +/* ========================================================================= */ +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); +} diff --git a/app/src/cm/svc/CM_CODENAME.h b/app/src/cm/svc/CM_CODENAME.h new file mode 100644 index 0000000..c51dd05 --- /dev/null +++ b/app/src/cm/svc/CM_CODENAME.h @@ -0,0 +1,12 @@ +/* + * CM_CODENAME.h - cm (공통) 서비스 CM_CODENAME 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CODENAME_H +#define CM_CODENAME_H + +#include "cm_svc.h" + +void CM_CODENAME(TPSVCINFO *p); + +#endif /* CM_CODENAME_H */ diff --git a/app/src/cm/svc/CM_CODENAME.pgc b/app/src/cm/svc/CM_CODENAME.pgc new file mode 100644 index 0000000..2a97d59 --- /dev/null +++ b/app/src/cm/svc/CM_CODENAME.pgc @@ -0,0 +1,21 @@ +/* + * CM_CODENAME.pgc - cm 모듈 서비스 CM_CODENAME (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODENAME.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_CRC.h b/app/src/cm/svc/CM_CRC.h new file mode 100644 index 0000000..1069bee --- /dev/null +++ b/app/src/cm/svc/CM_CRC.h @@ -0,0 +1,12 @@ +/* + * CM_CRC.h - cm (공통) 서비스 CM_CRC 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_CRC_H +#define CM_CRC_H + +#include "cm_svc.h" + +void CM_CRC(TPSVCINFO *p); + +#endif /* CM_CRC_H */ diff --git a/app/src/cm/svc/CM_CRC.pgc b/app/src/cm/svc/CM_CRC.pgc new file mode 100644 index 0000000..a6d4524 --- /dev/null +++ b/app/src/cm/svc/CM_CRC.pgc @@ -0,0 +1,34 @@ +/* + * CM_CRC.pgc - cm 모듈 서비스 CM_CRC (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_CRC.h" + +/* ----- service-local pure helper(s) ----- */ +/* 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; +} + + +/* 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); +} diff --git a/app/src/cm/svc/CM_ERRMSG.h b/app/src/cm/svc/CM_ERRMSG.h new file mode 100644 index 0000000..74d2be4 --- /dev/null +++ b/app/src/cm/svc/CM_ERRMSG.h @@ -0,0 +1,12 @@ +/* + * CM_ERRMSG.h - cm (공통) 서비스 CM_ERRMSG 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_ERRMSG_H +#define CM_ERRMSG_H + +#include "cm_svc.h" + +void CM_ERRMSG(TPSVCINFO *p); + +#endif /* CM_ERRMSG_H */ diff --git a/app/src/cm/svc/CM_ERRMSG.pgc b/app/src/cm/svc/CM_ERRMSG.pgc new file mode 100644 index 0000000..05f96fa --- /dev/null +++ b/app/src/cm/svc/CM_ERRMSG.pgc @@ -0,0 +1,19 @@ +/* + * CM_ERRMSG.pgc - cm 모듈 서비스 CM_ERRMSG (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_ERRMSG.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_FMT_AMT.h b/app/src/cm/svc/CM_FMT_AMT.h new file mode 100644 index 0000000..a07dcc8 --- /dev/null +++ b/app/src/cm/svc/CM_FMT_AMT.h @@ -0,0 +1,12 @@ +/* + * CM_FMT_AMT.h - cm (공통) 서비스 CM_FMT_AMT 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_FMT_AMT_H +#define CM_FMT_AMT_H + +#include "cm_svc.h" + +void CM_FMT_AMT(TPSVCINFO *p); + +#endif /* CM_FMT_AMT_H */ diff --git a/app/src/cm/svc/CM_FMT_AMT.pgc b/app/src/cm/svc/CM_FMT_AMT.pgc new file mode 100644 index 0000000..676ae1f --- /dev/null +++ b/app/src/cm/svc/CM_FMT_AMT.pgc @@ -0,0 +1,28 @@ +/* + * CM_FMT_AMT.pgc - cm 모듈 서비스 CM_FMT_AMT (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_FMT_AMT.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_FMT_DATE.h b/app/src/cm/svc/CM_FMT_DATE.h new file mode 100644 index 0000000..19ea68e --- /dev/null +++ b/app/src/cm/svc/CM_FMT_DATE.h @@ -0,0 +1,12 @@ +/* + * CM_FMT_DATE.h - cm (공통) 서비스 CM_FMT_DATE 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_FMT_DATE_H +#define CM_FMT_DATE_H + +#include "cm_svc.h" + +void CM_FMT_DATE(TPSVCINFO *p); + +#endif /* CM_FMT_DATE_H */ diff --git a/app/src/cm/svc/CM_FMT_DATE.pgc b/app/src/cm/svc/CM_FMT_DATE.pgc new file mode 100644 index 0000000..90e92b1 --- /dev/null +++ b/app/src/cm/svc/CM_FMT_DATE.pgc @@ -0,0 +1,22 @@ +/* + * CM_FMT_DATE.pgc - cm 모듈 서비스 CM_FMT_DATE (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_FMT_DATE.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_FX.h b/app/src/cm/svc/CM_FX.h new file mode 100644 index 0000000..71cf667 --- /dev/null +++ b/app/src/cm/svc/CM_FX.h @@ -0,0 +1,12 @@ +/* + * CM_FX.h - cm (공통) 서비스 CM_FX 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_FX_H +#define CM_FX_H + +#include "cm_svc.h" + +void CM_FX(TPSVCINFO *p); + +#endif /* CM_FX_H */ diff --git a/app/src/cm/svc/CM_FX.pgc b/app/src/cm/svc/CM_FX.pgc new file mode 100644 index 0000000..69c696d --- /dev/null +++ b/app/src/cm/svc/CM_FX.pgc @@ -0,0 +1,20 @@ +/* + * CM_FX.pgc - cm 모듈 서비스 CM_FX (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_FX.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_FX_CONV.h b/app/src/cm/svc/CM_FX_CONV.h new file mode 100644 index 0000000..db08bb0 --- /dev/null +++ b/app/src/cm/svc/CM_FX_CONV.h @@ -0,0 +1,12 @@ +/* + * CM_FX_CONV.h - cm (공통) 서비스 CM_FX_CONV 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_FX_CONV_H +#define CM_FX_CONV_H + +#include "cm_svc.h" + +void CM_FX_CONV(TPSVCINFO *p); + +#endif /* CM_FX_CONV_H */ diff --git a/app/src/cm/svc/CM_FX_CONV.pgc b/app/src/cm/svc/CM_FX_CONV.pgc new file mode 100644 index 0000000..83ca3d5 --- /dev/null +++ b/app/src/cm/svc/CM_FX_CONV.pgc @@ -0,0 +1,23 @@ +/* + * CM_FX_CONV.pgc - cm 모듈 서비스 CM_FX_CONV (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_FX_CONV.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_HOLIDAY_CHK.h b/app/src/cm/svc/CM_HOLIDAY_CHK.h new file mode 100644 index 0000000..0910390 --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_CHK.h @@ -0,0 +1,12 @@ +/* + * CM_HOLIDAY_CHK.h - cm (공통) 서비스 CM_HOLIDAY_CHK 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_HOLIDAY_CHK_H +#define CM_HOLIDAY_CHK_H + +#include "cm_svc.h" + +void CM_HOLIDAY_CHK(TPSVCINFO *p); + +#endif /* CM_HOLIDAY_CHK_H */ diff --git a/app/src/cm/svc/CM_HOLIDAY_CHK.pgc b/app/src/cm/svc/CM_HOLIDAY_CHK.pgc new file mode 100644 index 0000000..a76106a --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_CHK.pgc @@ -0,0 +1,20 @@ +/* + * CM_HOLIDAY_CHK.pgc - cm 모듈 서비스 CM_HOLIDAY_CHK (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_HOLIDAY_CHK.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_INTEREST.h b/app/src/cm/svc/CM_INTEREST.h new file mode 100644 index 0000000..61606d1 --- /dev/null +++ b/app/src/cm/svc/CM_INTEREST.h @@ -0,0 +1,12 @@ +/* + * CM_INTEREST.h - cm (공통) 서비스 CM_INTEREST 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_INTEREST_H +#define CM_INTEREST_H + +#include "cm_svc.h" + +void CM_INTEREST(TPSVCINFO *p); + +#endif /* CM_INTEREST_H */ diff --git a/app/src/cm/svc/CM_INTEREST.pgc b/app/src/cm/svc/CM_INTEREST.pgc new file mode 100644 index 0000000..d577c72 --- /dev/null +++ b/app/src/cm/svc/CM_INTEREST.pgc @@ -0,0 +1,17 @@ +/* + * CM_INTEREST.pgc - cm 모듈 서비스 CM_INTEREST (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_INTEREST.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_LOG.h b/app/src/cm/svc/CM_LOG.h new file mode 100644 index 0000000..340d7fb --- /dev/null +++ b/app/src/cm/svc/CM_LOG.h @@ -0,0 +1,12 @@ +/* + * CM_LOG.h - cm (공통) 서비스 CM_LOG 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_LOG_H +#define CM_LOG_H + +#include "cm_svc.h" + +void CM_LOG(TPSVCINFO *p); + +#endif /* CM_LOG_H */ diff --git a/app/src/cm/svc/CM_LOG.pgc b/app/src/cm/svc/CM_LOG.pgc new file mode 100644 index 0000000..dba4b82 --- /dev/null +++ b/app/src/cm/svc/CM_LOG.pgc @@ -0,0 +1,22 @@ +/* + * CM_LOG.pgc - cm 모듈 서비스 CM_LOG (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_LOG.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_LUHN.h b/app/src/cm/svc/CM_LUHN.h new file mode 100644 index 0000000..408b496 --- /dev/null +++ b/app/src/cm/svc/CM_LUHN.h @@ -0,0 +1,12 @@ +/* + * CM_LUHN.h - cm (공통) 서비스 CM_LUHN 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_LUHN_H +#define CM_LUHN_H + +#include "cm_svc.h" + +void CM_LUHN(TPSVCINFO *p); + +#endif /* CM_LUHN_H */ diff --git a/app/src/cm/svc/CM_LUHN.pgc b/app/src/cm/svc/CM_LUHN.pgc new file mode 100644 index 0000000..75759c4 --- /dev/null +++ b/app/src/cm/svc/CM_LUHN.pgc @@ -0,0 +1,16 @@ +/* + * CM_LUHN.pgc - cm 모듈 서비스 CM_LUHN (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_LUHN.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_MASK.h b/app/src/cm/svc/CM_MASK.h new file mode 100644 index 0000000..205b492 --- /dev/null +++ b/app/src/cm/svc/CM_MASK.h @@ -0,0 +1,12 @@ +/* + * CM_MASK.h - cm (공통) 서비스 CM_MASK 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_MASK_H +#define CM_MASK_H + +#include "cm_svc.h" + +void CM_MASK(TPSVCINFO *p); + +#endif /* CM_MASK_H */ diff --git a/app/src/cm/svc/CM_MASK.pgc b/app/src/cm/svc/CM_MASK.pgc new file mode 100644 index 0000000..326d2ad --- /dev/null +++ b/app/src/cm/svc/CM_MASK.pgc @@ -0,0 +1,21 @@ +/* + * CM_MASK.pgc - cm 모듈 서비스 CM_MASK (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_MASK.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_MONTHEND.h b/app/src/cm/svc/CM_MONTHEND.h new file mode 100644 index 0000000..57f1496 --- /dev/null +++ b/app/src/cm/svc/CM_MONTHEND.h @@ -0,0 +1,12 @@ +/* + * CM_MONTHEND.h - cm (공통) 서비스 CM_MONTHEND 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_MONTHEND_H +#define CM_MONTHEND_H + +#include "cm_svc.h" + +void CM_MONTHEND(TPSVCINFO *p); + +#endif /* CM_MONTHEND_H */ diff --git a/app/src/cm/svc/CM_MONTHEND.pgc b/app/src/cm/svc/CM_MONTHEND.pgc new file mode 100644 index 0000000..0a75494 --- /dev/null +++ b/app/src/cm/svc/CM_MONTHEND.pgc @@ -0,0 +1,33 @@ +/* + * CM_MONTHEND.pgc - cm 모듈 서비스 CM_MONTHEND (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_MONTHEND.h" + +/* ----- service-local pure helper(s) ----- */ +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]; +} + + +/* 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); +} diff --git a/app/src/cm/svc/CM_NETTING_CALC.h b/app/src/cm/svc/CM_NETTING_CALC.h new file mode 100644 index 0000000..d3406ab --- /dev/null +++ b/app/src/cm/svc/CM_NETTING_CALC.h @@ -0,0 +1,12 @@ +/* + * CM_NETTING_CALC.h - cm (공통) 서비스 CM_NETTING_CALC 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_NETTING_CALC_H +#define CM_NETTING_CALC_H + +#include "cm_svc.h" + +void CM_NETTING_CALC(TPSVCINFO *p); + +#endif /* CM_NETTING_CALC_H */ diff --git a/app/src/cm/svc/CM_NETTING_CALC.pgc b/app/src/cm/svc/CM_NETTING_CALC.pgc new file mode 100644 index 0000000..35abbbe --- /dev/null +++ b/app/src/cm/svc/CM_NETTING_CALC.pgc @@ -0,0 +1,17 @@ +/* + * CM_NETTING_CALC.pgc - cm 모듈 서비스 CM_NETTING_CALC (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_NETTING_CALC.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_NEXTBIZ.h b/app/src/cm/svc/CM_NEXTBIZ.h new file mode 100644 index 0000000..b96462b --- /dev/null +++ b/app/src/cm/svc/CM_NEXTBIZ.h @@ -0,0 +1,12 @@ +/* + * CM_NEXTBIZ.h - cm (공통) 서비스 CM_NEXTBIZ 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_NEXTBIZ_H +#define CM_NEXTBIZ_H + +#include "cm_svc.h" + +void CM_NEXTBIZ(TPSVCINFO *p); + +#endif /* CM_NEXTBIZ_H */ diff --git a/app/src/cm/svc/CM_NEXTBIZ.pgc b/app/src/cm/svc/CM_NEXTBIZ.pgc new file mode 100644 index 0000000..6865ded --- /dev/null +++ b/app/src/cm/svc/CM_NEXTBIZ.pgc @@ -0,0 +1,26 @@ +/* + * CM_NEXTBIZ.pgc - cm 모듈 서비스 CM_NEXTBIZ (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_NEXTBIZ.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_PARAM.h b/app/src/cm/svc/CM_PARAM.h new file mode 100644 index 0000000..c0f1062 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM.h @@ -0,0 +1,12 @@ +/* + * CM_PARAM.h - cm (공통) 서비스 CM_PARAM 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_PARAM_H +#define CM_PARAM_H + +#include "cm_svc.h" + +void CM_PARAM(TPSVCINFO *p); + +#endif /* CM_PARAM_H */ diff --git a/app/src/cm/svc/CM_PARAM.pgc b/app/src/cm/svc/CM_PARAM.pgc new file mode 100644 index 0000000..426b9db --- /dev/null +++ b/app/src/cm/svc/CM_PARAM.pgc @@ -0,0 +1,18 @@ +/* + * CM_PARAM.pgc - cm 모듈 서비스 CM_PARAM (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_PARAM.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_PARAM_SET.h b/app/src/cm/svc/CM_PARAM_SET.h new file mode 100644 index 0000000..499fc5f --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_SET.h @@ -0,0 +1,12 @@ +/* + * CM_PARAM_SET.h - cm (공통) 서비스 CM_PARAM_SET 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_PARAM_SET_H +#define CM_PARAM_SET_H + +#include "cm_svc.h" + +void CM_PARAM_SET(TPSVCINFO *p); + +#endif /* CM_PARAM_SET_H */ diff --git a/app/src/cm/svc/CM_PARAM_SET.pgc b/app/src/cm/svc/CM_PARAM_SET.pgc new file mode 100644 index 0000000..51a2e33 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_SET.pgc @@ -0,0 +1,19 @@ +/* + * CM_PARAM_SET.pgc - cm 모듈 서비스 CM_PARAM_SET (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_PARAM_SET.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_PING.h b/app/src/cm/svc/CM_PING.h new file mode 100644 index 0000000..f75bdc0 --- /dev/null +++ b/app/src/cm/svc/CM_PING.h @@ -0,0 +1,12 @@ +/* + * CM_PING.h - cm (공통) 서비스 CM_PING 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_PING_H +#define CM_PING_H + +#include "cm_svc.h" + +void CM_PING(TPSVCINFO *p); + +#endif /* CM_PING_H */ diff --git a/app/src/cm/svc/CM_PING.pgc b/app/src/cm/svc/CM_PING.pgc new file mode 100644 index 0000000..05ba779 --- /dev/null +++ b/app/src/cm/svc/CM_PING.pgc @@ -0,0 +1,15 @@ +/* + * CM_PING.pgc - cm 모듈 서비스 CM_PING (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_PING.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_PRORATE.h b/app/src/cm/svc/CM_PRORATE.h new file mode 100644 index 0000000..1447c04 --- /dev/null +++ b/app/src/cm/svc/CM_PRORATE.h @@ -0,0 +1,12 @@ +/* + * CM_PRORATE.h - cm (공통) 서비스 CM_PRORATE 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_PRORATE_H +#define CM_PRORATE_H + +#include "cm_svc.h" + +void CM_PRORATE(TPSVCINFO *p); + +#endif /* CM_PRORATE_H */ diff --git a/app/src/cm/svc/CM_PRORATE.pgc b/app/src/cm/svc/CM_PRORATE.pgc new file mode 100644 index 0000000..62d75bd --- /dev/null +++ b/app/src/cm/svc/CM_PRORATE.pgc @@ -0,0 +1,19 @@ +/* + * CM_PRORATE.pgc - cm 모듈 서비스 CM_PRORATE (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_PRORATE.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_SEQ.h b/app/src/cm/svc/CM_SEQ.h new file mode 100644 index 0000000..29c56f6 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ.h @@ -0,0 +1,12 @@ +/* + * CM_SEQ.h - cm (공통) 서비스 CM_SEQ 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_SEQ_H +#define CM_SEQ_H + +#include "cm_svc.h" + +void CM_SEQ(TPSVCINFO *p); + +#endif /* CM_SEQ_H */ diff --git a/app/src/cm/svc/CM_SEQ.pgc b/app/src/cm/svc/CM_SEQ.pgc new file mode 100644 index 0000000..9644463 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ.pgc @@ -0,0 +1,19 @@ +/* + * CM_SEQ.pgc - cm 모듈 서비스 CM_SEQ (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_SEQ.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_STATUS.h b/app/src/cm/svc/CM_STATUS.h new file mode 100644 index 0000000..a825de1 --- /dev/null +++ b/app/src/cm/svc/CM_STATUS.h @@ -0,0 +1,12 @@ +/* + * CM_STATUS.h - cm (공통) 서비스 CM_STATUS 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_STATUS_H +#define CM_STATUS_H + +#include "cm_svc.h" + +void CM_STATUS(TPSVCINFO *p); + +#endif /* CM_STATUS_H */ diff --git a/app/src/cm/svc/CM_STATUS.pgc b/app/src/cm/svc/CM_STATUS.pgc new file mode 100644 index 0000000..10e4238 --- /dev/null +++ b/app/src/cm/svc/CM_STATUS.pgc @@ -0,0 +1,21 @@ +/* + * CM_STATUS.pgc - cm 모듈 서비스 CM_STATUS (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_STATUS.h" + +/* 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); +} diff --git a/app/src/cm/svc/CM_WEEKDAY.h b/app/src/cm/svc/CM_WEEKDAY.h new file mode 100644 index 0000000..203b06a --- /dev/null +++ b/app/src/cm/svc/CM_WEEKDAY.h @@ -0,0 +1,12 @@ +/* + * CM_WEEKDAY.h - cm (공통) 서비스 CM_WEEKDAY 카피북 (copybook / record header). + * Split out of the former inline cm_svr.pgc; advertised by cm_svr. + */ +#ifndef CM_WEEKDAY_H +#define CM_WEEKDAY_H + +#include "cm_svc.h" + +void CM_WEEKDAY(TPSVCINFO *p); + +#endif /* CM_WEEKDAY_H */ diff --git a/app/src/cm/svc/CM_WEEKDAY.pgc b/app/src/cm/svc/CM_WEEKDAY.pgc new file mode 100644 index 0000000..80f75fc --- /dev/null +++ b/app/src/cm/svc/CM_WEEKDAY.pgc @@ -0,0 +1,32 @@ +/* + * CM_WEEKDAY.pgc - cm 모듈 서비스 CM_WEEKDAY (one service = one file). + * Split out of the former inline cm_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by cm_svr at tpsvrinit. + */ +#include "CM_WEEKDAY.h" + +/* ----- service-local pure helper(s) ----- */ +/* 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; +} + + +/* 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); +} diff --git a/app/src/cm/svc/cm_svc.h b/app/src/cm/svc/cm_svc.h new file mode 100644 index 0000000..52a5c97 --- /dev/null +++ b/app/src/cm/svc/cm_svc.h @@ -0,0 +1,34 @@ +/* + * cm_svc.h - cm (공통) 서비스 공통 카피북 (shared copybook for all cm svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the cm dbio layer (libcmdbio.a) + * and the common math (libacqcommon.a), and provides the tiny UBF helpers + + * tpreturn macros every service uses. No business logic lives here. + */ +#ifndef CM_SVC_H +#define CM_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "cm_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* CM_SVC_H */ diff --git a/app/src/lg/batch/lg_acctbal_batch.pgc b/app/src/lg/batch/lg_acctbal_batch.pgc new file mode 100644 index 0000000..320e6c6 --- /dev/null +++ b/app/src/lg/batch/lg_acctbal_batch.pgc @@ -0,0 +1,42 @@ +/* + * lg_acctbal_batch.pgc - lg 계정잔액 일괄 재집계 배치 (단일 upsert) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_acctbal_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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; } + + 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) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_cnt = sqlca.sqlerrd[2]; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_acctbal_batch COMMIT: bizdate=%s 계정=%ld건 재집계\n", bizdate, h_cnt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_apply_batch.pgc b/app/src/lg/batch/lg_apply_batch.pgc new file mode 100644 index 0000000..dab0fc2 --- /dev/null +++ b/app/src/lg/batch/lg_apply_batch.pgc @@ -0,0 +1,45 @@ +/* + * lg_apply_batch.pgc - lg 작성(D) 전표 일괄 전기확정 배치 (커서 D->P) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_apply_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_vid; + 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; } + + long rows = 0; + EXEC SQL DECLARE lab CURSOR FOR + SELECT voucher_id FROM lg_voucher + WHERE biz_date = :h_bizdate AND status = 'D' ORDER BY voucher_id; + EXEC SQL OPEN lab; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lab INTO :h_vid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lab; tpabort(0); return 1; } + if (lgdb_update_voucher_status(h_vid, "P") < 0) { EXEC SQL CLOSE lab; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE lab; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_apply_batch COMMIT: bizdate=%s 전기확정=%ld전표\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_carry_batch.pgc b/app/src/lg/batch/lg_carry_batch.pgc new file mode 100644 index 0000000..df86c37 --- /dev/null +++ b/app/src/lg/batch/lg_carry_batch.pgc @@ -0,0 +1,40 @@ +/* + * lg_carry_batch.pgc - lg 계정잔액 익일 이월 배치 (insert..select) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_carry_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_next[16]; + long 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; } + + char nextday[16]; + if (acq_next_bizday(bizdate, nextday) < 0) strcpy(nextday, bizdate); + 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) { fprintf(stderr, "CARRY FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_cnt = sqlca.sqlerrd[2]; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_carry_batch COMMIT: %s -> %s 계정 %ld건 이월\n", bizdate, nextday, h_cnt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_diffcheck_batch.pgc b/app/src/lg/batch/lg_diffcheck_batch.pgc new file mode 100644 index 0000000..fcd7098 --- /dev/null +++ b/app/src/lg/batch/lg_diffcheck_batch.pgc @@ -0,0 +1,36 @@ +/* + * lg_diffcheck_batch.pgc - lg 당일 대차 균형 점검 배치 (단건 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_diffcheck_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dr, h_cr; + 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 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) { fprintf(stderr, "DIFF FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_diffcheck_batch COMMIT: bizdate=%s 차변=%ld 대변=%ld 차액=%ld %s\n", + bizdate, h_dr, h_cr, h_dr - h_cr, (h_dr == h_cr) ? "(균형)" : "(불균형!)"); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_fee_batch.pgc b/app/src/lg/batch/lg_fee_batch.pgc new file mode 100644 index 0000000..c9a444e --- /dev/null +++ b/app/src/lg/batch/lg_fee_batch.pgc @@ -0,0 +1,52 @@ +/* + * lg_fee_batch.pgc - lg 수수료수익 전기 배치 (커서, 매입건별 FEE 전표) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_fee_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_merch[64]; + long h_pid, h_fee; + 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; } + + long rows = 0, vid; + EXEC SQL DECLARE lfb CURSOR FOR + SELECT purchase_id, merchant_id, fee FROM purchase + WHERE biz_date = :h_bizdate AND status = 'M' AND fee > 0 ORDER BY purchase_id; + EXEC SQL OPEN lfb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lfb INTO :h_pid, :h_merch, :h_fee; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lfb; tpabort(0); return 1; } + vid = lgdb_next_voucher_id(); + if (vid < 0) { EXEC SQL CLOSE lfb; tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "FEE", h_pid, "P", bizdate, "수수료전기(배치)") < 0 || + lgdb_insert_line(vid, "RECV", "D", h_fee, h_merch, bizdate, "posted") < 0 || + lgdb_insert_line(vid, "FEE_INC", "C", h_fee, h_merch, bizdate, "posted") < 0) { + EXEC SQL CLOSE lfb; tpabort(0); return 1; + } + rows++; + } + EXEC SQL CLOSE lfb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_fee_batch COMMIT: bizdate=%s 수수료전표=%ld\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_feeincome_batch.pgc b/app/src/lg/batch/lg_feeincome_batch.pgc new file mode 100644 index 0000000..7c85166 --- /dev/null +++ b/app/src/lg/batch/lg_feeincome_batch.pgc @@ -0,0 +1,34 @@ +/* + * lg_feeincome_batch.pgc - lg 수수료수익 집계 배치 (단건 집계) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_feeincome_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee, 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; } + + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_fee, :h_cnt + FROM lg_ledger_line WHERE biz_date = :h_bizdate AND acct = 'FEE_INC' AND dc = 'C'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FEE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_feeincome_batch COMMIT: bizdate=%s 수수료수익=%ld (분개 %ld줄)\n", bizdate, h_fee, h_cnt); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_journal_batch.pgc b/app/src/lg/batch/lg_journal_batch.pgc new file mode 100644 index 0000000..6762f4e --- /dev/null +++ b/app/src/lg/batch/lg_journal_batch.pgc @@ -0,0 +1,47 @@ +/* + * lg_journal_batch.pgc - lg 전표유형별 분개 통계 배치 (커서 GROUP BY) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_journal_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_type[16]; + long h_vc, h_lc; + 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; } + + long types = 0; + EXEC SQL DECLARE ljb CURSOR FOR + SELECT v.voucher_type, count(DISTINCT v.voucher_id), count(l.line_id) + FROM lg_voucher v LEFT JOIN lg_ledger_line l ON l.voucher_id = v.voucher_id + WHERE v.biz_date = :h_bizdate GROUP BY v.voucher_type ORDER BY v.voucher_type; + EXEC SQL OPEN ljb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH ljb INTO :h_type, :h_vc, :h_lc; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ljb; tpabort(0); return 1; } + printf(" %-10s 전표=%ld 분개=%ld\n", h_type, h_vc, h_lc); + types++; + } + EXEC SQL CLOSE ljb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_journal_batch COMMIT: bizdate=%s 유형=%ld종\n", bizdate, types); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_merchbal_batch.pgc b/app/src/lg/batch/lg_merchbal_batch.pgc new file mode 100644 index 0000000..bc6264d --- /dev/null +++ b/app/src/lg/batch/lg_merchbal_batch.pgc @@ -0,0 +1,45 @@ +/* + * lg_merchbal_batch.pgc - lg 가맹점 잔액 재집계 배치 (커서 + dbio upsert) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_merchbal_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_merch[64]; + 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; } + + long rows = 0; + EXEC SQL DECLARE lmb CURSOR FOR + SELECT DISTINCT merchant_id FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND merchant_id <> '' ORDER BY merchant_id; + EXEC SQL OPEN lmb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lmb INTO :h_merch; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lmb; tpabort(0); return 1; } + if (lgdb_upsert_merch_bal(h_merch, bizdate) < 0) { EXEC SQL CLOSE lmb; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE lmb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_merchbal_batch COMMIT: bizdate=%s 가맹점=%ld건\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_recalc_batch.pgc b/app/src/lg/batch/lg_recalc_batch.pgc new file mode 100644 index 0000000..e0531ee --- /dev/null +++ b/app/src/lg/batch/lg_recalc_batch.pgc @@ -0,0 +1,44 @@ +/* + * lg_recalc_batch.pgc - lg 계정잔액 라인기준 재계산 배치 (커서 + dbio) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_recalc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_acct[32]; + 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; } + + long rows = 0; + EXEC SQL DECLARE lrb CURSOR FOR + SELECT DISTINCT acct FROM lg_ledger_line WHERE biz_date = :h_bizdate ORDER BY acct; + EXEC SQL OPEN lrb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lrb INTO :h_acct; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lrb; tpabort(0); return 1; } + if (lgdb_recalc_acct_bal(h_acct, bizdate) < 0) { EXEC SQL CLOSE lrb; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE lrb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_recalc_batch COMMIT: bizdate=%s 계정 %ld건 재계산\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_report_batch.pgc b/app/src/lg/batch/lg_report_batch.pgc new file mode 100644 index 0000000..f8dbac2 --- /dev/null +++ b/app/src/lg/batch/lg_report_batch.pgc @@ -0,0 +1,46 @@ +/* + * lg_report_batch.pgc - lg 계정잔액 상위 리포트 배치 (커서 ORDER BY DESC) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_report_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_acct[32]; + long h_bal; + 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; } + + long rows = 0; + EXEC SQL DECLARE lrp CURSOR FOR + SELECT acct, balance FROM lg_account_bal + WHERE biz_date = :h_bizdate ORDER BY abs(balance) DESC LIMIT 10; + EXEC SQL OPEN lrp; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lrp INTO :h_acct, :h_bal; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lrp; tpabort(0); return 1; } + printf(" %-12s 잔액=%ld\n", h_acct, h_bal); + rows++; + } + EXEC SQL CLOSE lrp; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_report_batch COMMIT: bizdate=%s 상위계정 %ld건\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_reverse_batch.pgc b/app/src/lg/batch/lg_reverse_batch.pgc new file mode 100644 index 0000000..731b377 --- /dev/null +++ b/app/src/lg/batch/lg_reverse_batch.pgc @@ -0,0 +1,54 @@ +/* + * lg_reverse_batch.pgc - lg 조정(ADJ) 전표 역분개 배치 (커서 + insert..select) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_reverse_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_src, h_new; + 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; } + + long rows = 0; + EXEC SQL DECLARE lvb CURSOR FOR + SELECT voucher_id FROM lg_voucher + WHERE biz_date = :h_bizdate AND voucher_type = 'ADJ' AND status = 'P' + ORDER BY voucher_id; + EXEC SQL OPEN lvb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lvb INTO :h_src; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lvb; tpabort(0); return 1; } + h_new = lgdb_next_voucher_id(); + if (h_new < 0) { EXEC SQL CLOSE lvb; tpabort(0); return 1; } + if (lgdb_insert_voucher(h_new, "REVERSE", h_src, "P", bizdate, "조정역분개(배치)") < 0) { EXEC SQL CLOSE lvb; tpabort(0); return 1; } + 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) { EXEC SQL CLOSE lvb; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE lvb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_reverse_batch COMMIT: bizdate=%s 역분개=%ld전표\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_settle_batch.pgc b/app/src/lg/batch/lg_settle_batch.pgc new file mode 100644 index 0000000..e123831 --- /dev/null +++ b/app/src/lg/batch/lg_settle_batch.pgc @@ -0,0 +1,52 @@ +/* + * lg_settle_batch.pgc - lg 정산 전기 배치 (커서, 정산완료 S 건별 SETTLE 전표) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_settle_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + char h_merch[64]; + long h_pid, 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; } + + long rows = 0, vid; + EXEC SQL DECLARE lsb CURSOR FOR + SELECT purchase_id, merchant_id, net FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S' ORDER BY purchase_id; + EXEC SQL OPEN lsb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lsb INTO :h_pid, :h_merch, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lsb; tpabort(0); return 1; } + vid = lgdb_next_voucher_id(); + if (vid < 0) { EXEC SQL CLOSE lsb; tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "SETTLE", h_pid, "P", bizdate, "정산전기(배치)") < 0 || + lgdb_insert_line(vid, "MERCH_PAY", "D", h_net, h_merch, bizdate, "posted") < 0 || + lgdb_insert_line(vid, "CASH", "C", h_net, h_merch, bizdate, "posted") < 0) { + EXEC SQL CLOSE lsb; tpabort(0); return 1; + } + rows++; + } + EXEC SQL CLOSE lsb; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_settle_batch COMMIT: bizdate=%s 정산전표=%ld\n", bizdate, rows); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_transfer_batch.pgc b/app/src/lg/batch/lg_transfer_batch.pgc new file mode 100644 index 0000000..301b592 --- /dev/null +++ b/app/src/lg/batch/lg_transfer_batch.pgc @@ -0,0 +1,40 @@ +/* + * lg_transfer_batch.pgc - lg 미지급금 현금대체 배치 (단일 균형전표) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_transfer_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pay; + 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; } + + long vid; + EXEC SQL SELECT coalesce(sum(CASE WHEN dc='C' THEN amount ELSE 0 END),0) INTO :h_pay + FROM lg_ledger_line WHERE biz_date = :h_bizdate AND acct = 'MERCH_PAY'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SUM FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + vid = lgdb_next_voucher_id(); + if (vid < 0) { tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "TRANSFER", 0, "P", bizdate, "미지급금 현금대체(배치)") < 0 || + lgdb_insert_line(vid, "MERCH_PAY", "D", h_pay, "", bizdate, "posted") < 0 || + lgdb_insert_line(vid, "CASH", "C", h_pay, "", bizdate, "posted") < 0) { tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_transfer_batch COMMIT: bizdate=%s vid=%ld 대체액=%ld\n", bizdate, vid, h_pay); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_trialpost_batch.pgc b/app/src/lg/batch/lg_trialpost_batch.pgc new file mode 100644 index 0000000..b39c1f4 --- /dev/null +++ b/app/src/lg/batch/lg_trialpost_batch.pgc @@ -0,0 +1,44 @@ +/* + * lg_trialpost_batch.pgc - lg 시산표 대차 검증 후 마감연계 배치 (집계 + update) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_trialpost_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dr, h_cr; + 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; } + + long closed; + 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 AND status = 'posted'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SUM FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (h_dr != h_cr) { + fprintf(stderr, "lg_trialpost_batch 대차 불균형 차변=%ld 대변=%ld -> 마감연계 중단\n", h_dr, h_cr); + tpabort(0); tpclose(); tpterm(); return 1; + } + EXEC SQL UPDATE lg_ledger_line SET status = 'closed' + WHERE biz_date = :h_bizdate AND status = 'posted'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + closed = sqlca.sqlerrd[2]; + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_trialpost_batch COMMIT: bizdate=%s 균형확인, 마감연계 %ld줄\n", bizdate, closed); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_vacuum_batch.pgc b/app/src/lg/batch/lg_vacuum_batch.pgc new file mode 100644 index 0000000..973d2ca --- /dev/null +++ b/app/src/lg/batch/lg_vacuum_batch.pgc @@ -0,0 +1,35 @@ +/* + * lg_vacuum_batch.pgc - lg 취소(X) 전표 정리 배치 (dbio delete) (XA 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: lg_vacuum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_stale_purge_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + + EXEC SQL END DECLARE SECTION; + 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; } + + long purged; + purged = lgqdb_stale_purge(bizdate); + if (purged < 0) { fprintf(stderr, "lg_vacuum_batch FAIL\n"); tpabort(0); return 1; } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_vacuum_batch COMMIT: %s 이전 취소전표 라인 %ld건 삭제\n", bizdate, purged); + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/dbio/lg_acct_asof_dbio.h b/app/src/lg/dbio/lg_acct_asof_dbio.h new file mode 100644 index 0000000..90bc5e1 --- /dev/null +++ b/app/src/lg/dbio/lg_acct_asof_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_acct_asof_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_acct_asof_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 계정과목 잔액 조회 (as-of, account_bal). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_ACCT_ASOF_DBIO_H +#define LG_ACCT_ASOF_DBIO_H + +int lgqdb_acct_asof(const char *acct, const char *bizdate, long *bal, long *dr, long *cr); +#endif /* LG_ACCT_ASOF_DBIO_H */ diff --git a/app/src/lg/dbio/lg_acct_asof_dbio.pgc b/app/src/lg/dbio/lg_acct_asof_dbio.pgc new file mode 100644 index 0000000..cc69d6a --- /dev/null +++ b/app/src/lg/dbio/lg_acct_asof_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_acct_asof_dbio.pgc - lg 모듈 DB 접근 함수 (lg_acct_asof_dbio), archived into liblgdbio.a. + * 계정과목 잔액 조회 (as-of, account_bal). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_acct_asof_dbio.h" + +/* 계정 잔액/차변/대변 3개 반환 (account_bal). */ +int lgqdb_acct_asof(const char *acct, const char *bizdate, long *bal, long *dr, long *cr) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_bal, h_dr, h_cr; + 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, debit_total, credit_total INTO :h_bal, :h_dr, :h_cr + FROM lg_account_bal WHERE acct = :h_acct AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("lgqdb_acct_asof miss [%d]", sqlca.sqlcode); return -1; } + *bal = h_bal; *dr = h_dr; *cr = h_cr; + return 0; +} diff --git a/app/src/lg/dbio/lg_acct_walk_dbio.h b/app/src/lg/dbio/lg_acct_walk_dbio.h new file mode 100644 index 0000000..28aeaaf --- /dev/null +++ b/app/src/lg/dbio/lg_acct_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_acct_walk_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_acct_walk_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 당일 계정과목 커서 순회 - 불균형 계정 수 누적. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_ACCT_WALK_DBIO_H +#define LG_ACCT_WALK_DBIO_H + +long lgqdb_acct_walk(const char *bizdate); +#endif /* LG_ACCT_WALK_DBIO_H */ diff --git a/app/src/lg/dbio/lg_acct_walk_dbio.pgc b/app/src/lg/dbio/lg_acct_walk_dbio.pgc new file mode 100644 index 0000000..2d59c4c --- /dev/null +++ b/app/src/lg/dbio/lg_acct_walk_dbio.pgc @@ -0,0 +1,33 @@ +/* + * lg_acct_walk_dbio.pgc - lg 모듈 DB 접근 함수 (lg_acct_walk_dbio), archived into liblgdbio.a. + * 당일 계정과목 커서 순회 - 불균형 계정 수 누적. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_acct_walk_dbio.h" + +/* 커서로 계정별 차/대를 훑어 불균형 계정 수를 누적 반환. */ +long lgqdb_acct_walk(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + long bad = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE lgw 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; + EXEC SQL OPEN lgw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH lgw INTO :h_acct, :h_dr, :h_cr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lgw; return -1; } + if (h_dr != h_cr) bad++; + } + EXEC SQL CLOSE lgw; + return bad; +} diff --git a/app/src/lg/dbio/lg_carry_forward_dbio.h b/app/src/lg/dbio/lg_carry_forward_dbio.h new file mode 100644 index 0000000..634944a --- /dev/null +++ b/app/src/lg/dbio/lg_carry_forward_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_carry_forward_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_carry_forward_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 계정잔액 익일 이월 upsert (insert..select). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_CARRY_FORWARD_DBIO_H +#define LG_CARRY_FORWARD_DBIO_H + +long lgqdb_carry_forward(const char *bizdate, const char *nextday); +#endif /* LG_CARRY_FORWARD_DBIO_H */ diff --git a/app/src/lg/dbio/lg_carry_forward_dbio.pgc b/app/src/lg/dbio/lg_carry_forward_dbio.pgc new file mode 100644 index 0000000..002729a --- /dev/null +++ b/app/src/lg/dbio/lg_carry_forward_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_carry_forward_dbio.pgc - lg 모듈 DB 접근 함수 (lg_carry_forward_dbio), archived into liblgdbio.a. + * 계정잔액 익일 이월 upsert (insert..select). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_carry_forward_dbio.h" + +/* 당일 계정잔액을 익일 개시잔액으로 이월, 이월 계정 수 반환. */ +long lgqdb_carry_forward(const char *bizdate, const char *nextday) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_next[16]; + EXEC SQL END DECLARE SECTION; + 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) { userlog("lgqdb_carry_forward FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/lg/dbio/lg_credit_sum_dbio.h b/app/src/lg/dbio/lg_credit_sum_dbio.h new file mode 100644 index 0000000..6db78ef --- /dev/null +++ b/app/src/lg/dbio/lg_credit_sum_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_credit_sum_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_credit_sum_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 계정과목 x 영업일 대변(C) 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_CREDIT_SUM_DBIO_H +#define LG_CREDIT_SUM_DBIO_H + +long lgqdb_credit_sum(const char *acct, const char *bizdate); +#endif /* LG_CREDIT_SUM_DBIO_H */ diff --git a/app/src/lg/dbio/lg_credit_sum_dbio.pgc b/app/src/lg/dbio/lg_credit_sum_dbio.pgc new file mode 100644 index 0000000..72a7f36 --- /dev/null +++ b/app/src/lg/dbio/lg_credit_sum_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_credit_sum_dbio.pgc - lg 모듈 DB 접근 함수 (lg_credit_sum_dbio), archived into liblgdbio.a. + * 계정과목 x 영업일 대변(C) 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_credit_sum_dbio.h" + +/* 계정 x 일자 대변합 (FILTER 사용). */ +long lgqdb_credit_sum(const char *acct, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_cr = 0; + 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 coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0) INTO :h_cr + FROM lg_ledger_line WHERE acct = :h_acct AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_credit_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (h_cr < 0) return -1; + return h_cr; +} diff --git a/app/src/lg/dbio/lg_debit_sum_dbio.h b/app/src/lg/dbio/lg_debit_sum_dbio.h new file mode 100644 index 0000000..5f65a5d --- /dev/null +++ b/app/src/lg/dbio/lg_debit_sum_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_debit_sum_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_debit_sum_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 계정과목 x 영업일 차변(D) 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_DEBIT_SUM_DBIO_H +#define LG_DEBIT_SUM_DBIO_H + +long lgqdb_debit_sum(const char *acct, const char *bizdate); +#endif /* LG_DEBIT_SUM_DBIO_H */ diff --git a/app/src/lg/dbio/lg_debit_sum_dbio.pgc b/app/src/lg/dbio/lg_debit_sum_dbio.pgc new file mode 100644 index 0000000..d6f100c --- /dev/null +++ b/app/src/lg/dbio/lg_debit_sum_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_debit_sum_dbio.pgc - lg 모듈 DB 접근 함수 (lg_debit_sum_dbio), archived into liblgdbio.a. + * 계정과목 x 영업일 차변(D) 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_debit_sum_dbio.h" + +/* 계정 x 일자 차변합 (FILTER 사용). */ +long lgqdb_debit_sum(const char *acct, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_dr = 0; + 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 coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0) INTO :h_dr + FROM lg_ledger_line WHERE acct = :h_acct AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_debit_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_dr; +} diff --git a/app/src/lg/dbio/lg_draft_count_dbio.h b/app/src/lg/dbio/lg_draft_count_dbio.h new file mode 100644 index 0000000..02236bd --- /dev/null +++ b/app/src/lg/dbio/lg_draft_count_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_draft_count_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_draft_count_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 작성(D) 상태 미전기 전표 건수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_DRAFT_COUNT_DBIO_H +#define LG_DRAFT_COUNT_DBIO_H + +long lgqdb_draft_count(void); +#endif /* LG_DRAFT_COUNT_DBIO_H */ diff --git a/app/src/lg/dbio/lg_draft_count_dbio.pgc b/app/src/lg/dbio/lg_draft_count_dbio.pgc new file mode 100644 index 0000000..bdd4c3e --- /dev/null +++ b/app/src/lg/dbio/lg_draft_count_dbio.pgc @@ -0,0 +1,19 @@ +/* + * lg_draft_count_dbio.pgc - lg 모듈 DB 접근 함수 (lg_draft_count_dbio), archived into liblgdbio.a. + * 작성(D) 상태 미전기 전표 건수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_draft_count_dbio.h" + +/* 전기(P) 되지 않은 작성(D) 전표 수 (전 기간). */ +long lgqdb_draft_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_voucher WHERE status = 'D'; + if (sqlca.sqlcode < 0) { userlog("lgqdb_draft_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_fee_income_dbio.h b/app/src/lg/dbio/lg_fee_income_dbio.h new file mode 100644 index 0000000..3e99781 --- /dev/null +++ b/app/src/lg/dbio/lg_fee_income_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_fee_income_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_fee_income_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 수수료수익(FEE_INC) 계정 대변 누계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_FEE_INCOME_DBIO_H +#define LG_FEE_INCOME_DBIO_H + +long lgqdb_fee_income(const char *bizdate); +#endif /* LG_FEE_INCOME_DBIO_H */ diff --git a/app/src/lg/dbio/lg_fee_income_dbio.pgc b/app/src/lg/dbio/lg_fee_income_dbio.pgc new file mode 100644 index 0000000..18d8cce --- /dev/null +++ b/app/src/lg/dbio/lg_fee_income_dbio.pgc @@ -0,0 +1,22 @@ +/* + * lg_fee_income_dbio.pgc - lg 모듈 DB 접근 함수 (lg_fee_income_dbio), archived into liblgdbio.a. + * 수수료수익(FEE_INC) 계정 대변 누계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_fee_income_dbio.h" + +/* FEE_INC 계정의 당일 대변 누계 (수수료 수익). */ +long lgqdb_fee_income(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_fee FROM lg_ledger_line + WHERE acct = 'FEE_INC' AND dc = 'C' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_fee_income FAIL [%d]", sqlca.sqlcode); return -1; } + return h_fee; +} diff --git a/app/src/lg/dbio/lg_line_relabel_dbio.h b/app/src/lg/dbio/lg_line_relabel_dbio.h new file mode 100644 index 0000000..2ab7a33 --- /dev/null +++ b/app/src/lg/dbio/lg_line_relabel_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_line_relabel_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_line_relabel_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 특정 전표 라인 계정과목 재분류 (update count). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_LINE_RELABEL_DBIO_H +#define LG_LINE_RELABEL_DBIO_H + +long lgqdb_line_relabel(long vid, const char *from_acct, const char *to_acct); +#endif /* LG_LINE_RELABEL_DBIO_H */ diff --git a/app/src/lg/dbio/lg_line_relabel_dbio.pgc b/app/src/lg/dbio/lg_line_relabel_dbio.pgc new file mode 100644 index 0000000..396e88f --- /dev/null +++ b/app/src/lg/dbio/lg_line_relabel_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_line_relabel_dbio.pgc - lg 모듈 DB 접근 함수 (lg_line_relabel_dbio), archived into liblgdbio.a. + * 특정 전표 라인 계정과목 재분류 (update count). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_line_relabel_dbio.h" + +/* 한 전표 안에서 계정과목 재분류(from->to), 재분류 라인 수 반환. */ +long lgqdb_line_relabel(long vid, const char *from_acct, const char *to_acct) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_vid = vid; + char h_from[32], h_to[32]; + EXEC SQL END DECLARE SECTION; + strncpy(h_from, from_acct, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, to_acct, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + EXEC SQL UPDATE lg_ledger_line SET acct = :h_to + WHERE voucher_id = :h_vid AND acct = :h_from AND status = 'posted'; + if (sqlca.sqlcode < 0) { userlog("lgqdb_line_relabel FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/lg/dbio/lg_max_voucher_dbio.h b/app/src/lg/dbio/lg_max_voucher_dbio.h new file mode 100644 index 0000000..cd5d928 --- /dev/null +++ b/app/src/lg/dbio/lg_max_voucher_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_max_voucher_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_max_voucher_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 당일 최대 전표 ID. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MAX_VOUCHER_DBIO_H +#define LG_MAX_VOUCHER_DBIO_H + +long lgqdb_max_voucher(const char *bizdate); +#endif /* LG_MAX_VOUCHER_DBIO_H */ diff --git a/app/src/lg/dbio/lg_max_voucher_dbio.pgc b/app/src/lg/dbio/lg_max_voucher_dbio.pgc new file mode 100644 index 0000000..5580aa0 --- /dev/null +++ b/app/src/lg/dbio/lg_max_voucher_dbio.pgc @@ -0,0 +1,22 @@ +/* + * lg_max_voucher_dbio.pgc - lg 모듈 DB 접근 함수 (lg_max_voucher_dbio), archived into liblgdbio.a. + * 당일 최대 전표 ID. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_max_voucher_dbio.h" + +/* 당일 채번된 최대 전표 ID (없으면 0). */ +long lgqdb_max_voucher(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(voucher_id), 0) INTO :h_max + FROM lg_voucher WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_max_voucher FAIL [%d]", sqlca.sqlcode); return -1; } + return h_max; +} diff --git a/app/src/lg/dbio/lg_merch_balance_dbio.h b/app/src/lg/dbio/lg_merch_balance_dbio.h new file mode 100644 index 0000000..67b89d0 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_balance_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_merch_balance_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_merch_balance_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 가맹점 잔액 조회 (merch_bal). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MERCH_BALANCE_DBIO_H +#define LG_MERCH_BALANCE_DBIO_H + +long lgqdb_merch_balance(const char *merch, const char *bizdate); +#endif /* LG_MERCH_BALANCE_DBIO_H */ diff --git a/app/src/lg/dbio/lg_merch_balance_dbio.pgc b/app/src/lg/dbio/lg_merch_balance_dbio.pgc new file mode 100644 index 0000000..5beb734 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_balance_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_merch_balance_dbio.pgc - lg 모듈 DB 접근 함수 (lg_merch_balance_dbio), archived into liblgdbio.a. + * 가맹점 잔액 조회 (merch_bal). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_merch_balance_dbio.h" + +/* 가맹점 x 일자 잔액. */ +long lgqdb_merch_balance(const char *merch, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_bal = 0; + 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 SELECT coalesce(balance, 0) INTO :h_bal FROM lg_merch_bal + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("lgqdb_merch_balance FAIL [%d]", sqlca.sqlcode); return -1; } + return h_bal; +} diff --git a/app/src/lg/dbio/lg_merch_walk_dbio.h b/app/src/lg/dbio/lg_merch_walk_dbio.h new file mode 100644 index 0000000..022b9dc --- /dev/null +++ b/app/src/lg/dbio/lg_merch_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_merch_walk_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_merch_walk_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 가맹점 라인 커서 순회 - 순액(차-대) 누적. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MERCH_WALK_DBIO_H +#define LG_MERCH_WALK_DBIO_H + +long lgqdb_merch_walk(const char *merch, const char *bizdate); +#endif /* LG_MERCH_WALK_DBIO_H */ diff --git a/app/src/lg/dbio/lg_merch_walk_dbio.pgc b/app/src/lg/dbio/lg_merch_walk_dbio.pgc new file mode 100644 index 0000000..a0e1a95 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_walk_dbio.pgc @@ -0,0 +1,33 @@ +/* + * lg_merch_walk_dbio.pgc - lg 모듈 DB 접근 함수 (lg_merch_walk_dbio), archived into liblgdbio.a. + * 가맹점 라인 커서 순회 - 순액(차-대) 누적. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_merch_walk_dbio.h" + +/* 가맹점 라인을 커서로 훑어 순액(차변-대변) 누적. */ +long lgqdb_merch_walk(const char *merch, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16], h_dc[4]; + long h_amt; + EXEC SQL END DECLARE SECTION; + long net = 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 DECLARE lmw CURSOR FOR + SELECT dc, amount FROM lg_ledger_line + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + EXEC SQL OPEN lmw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH lmw INTO :h_dc, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lmw; return -1; } + net += (h_dc[0] == 'D') ? h_amt : -h_amt; + } + EXEC SQL CLOSE lmw; + return net; +} diff --git a/app/src/lg/dbio/lg_month_debit_dbio.h b/app/src/lg/dbio/lg_month_debit_dbio.h new file mode 100644 index 0000000..55294af --- /dev/null +++ b/app/src/lg/dbio/lg_month_debit_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_month_debit_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_month_debit_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 월(YYYY-MM) 계정 차변 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MONTH_DEBIT_DBIO_H +#define LG_MONTH_DEBIT_DBIO_H + +long lgqdb_month_debit(const char *acct, const char *month); +#endif /* LG_MONTH_DEBIT_DBIO_H */ diff --git a/app/src/lg/dbio/lg_month_debit_dbio.pgc b/app/src/lg/dbio/lg_month_debit_dbio.pgc new file mode 100644 index 0000000..687179d --- /dev/null +++ b/app/src/lg/dbio/lg_month_debit_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_month_debit_dbio.pgc - lg 모듈 DB 접근 함수 (lg_month_debit_dbio), archived into liblgdbio.a. + * 월(YYYY-MM) 계정 차변 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_month_debit_dbio.h" + +/* 월 프리픽스로 계정 차변 누계. */ +long lgqdb_month_debit(const char *acct, const char *month) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_month[16]; + long h_dr = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0; + strncpy(h_month, month, sizeof(h_month)-1); h_month[sizeof(h_month)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_dr FROM lg_ledger_line + WHERE acct = :h_acct AND dc = 'D' AND to_char(biz_date,'YYYY-MM') = :h_month; + if (sqlca.sqlcode < 0) { userlog("lgqdb_month_debit FAIL [%d]", sqlca.sqlcode); return -1; } + return h_dr; +} diff --git a/app/src/lg/dbio/lg_posted_total_dbio.h b/app/src/lg/dbio/lg_posted_total_dbio.h new file mode 100644 index 0000000..5687394 --- /dev/null +++ b/app/src/lg/dbio/lg_posted_total_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_posted_total_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_posted_total_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 전기(posted) 원장 라인 총 차변/대변. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_POSTED_TOTAL_DBIO_H +#define LG_POSTED_TOTAL_DBIO_H + +int lgqdb_posted_total(const char *bizdate, long *dr, long *cr); +#endif /* LG_POSTED_TOTAL_DBIO_H */ diff --git a/app/src/lg/dbio/lg_posted_total_dbio.pgc b/app/src/lg/dbio/lg_posted_total_dbio.pgc new file mode 100644 index 0000000..19b7976 --- /dev/null +++ b/app/src/lg/dbio/lg_posted_total_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_posted_total_dbio.pgc - lg 모듈 DB 접근 함수 (lg_posted_total_dbio), archived into liblgdbio.a. + * 전기(posted) 원장 라인 총 차변/대변. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_posted_total_dbio.h" + +/* posted 라인의 총 차변/대변 (마감연계 전 잔량). */ +int lgqdb_posted_total(const char *bizdate, long *dr, long *cr) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + 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 AND status = 'posted'; + if (sqlca.sqlcode < 0) { userlog("lgqdb_posted_total FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *dr = h_dr; *cr = h_cr; + return 0; +} diff --git a/app/src/lg/dbio/lg_recv_pay_gap_dbio.h b/app/src/lg/dbio/lg_recv_pay_gap_dbio.h new file mode 100644 index 0000000..b0e2a33 --- /dev/null +++ b/app/src/lg/dbio/lg_recv_pay_gap_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_recv_pay_gap_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_recv_pay_gap_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 미수(RECV) 차변 vs 미지급(MERCH_PAY) 대변 차액. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_RECV_PAY_GAP_DBIO_H +#define LG_RECV_PAY_GAP_DBIO_H + +int lgqdb_recv_pay_gap(const char *bizdate, long *recv, long *pay, long *gap); +#endif /* LG_RECV_PAY_GAP_DBIO_H */ diff --git a/app/src/lg/dbio/lg_recv_pay_gap_dbio.pgc b/app/src/lg/dbio/lg_recv_pay_gap_dbio.pgc new file mode 100644 index 0000000..c8a3269 --- /dev/null +++ b/app/src/lg/dbio/lg_recv_pay_gap_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_recv_pay_gap_dbio.pgc - lg 모듈 DB 접근 함수 (lg_recv_pay_gap_dbio), archived into liblgdbio.a. + * 미수(RECV) 차변 vs 미지급(MERCH_PAY) 대변 차액. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_recv_pay_gap_dbio.h" + +/* RECV 차변합과 MERCH_PAY 대변합의 차액 (미결제 잔량 점검). */ +int lgqdb_recv_pay_gap(const char *bizdate, long *recv, long *pay, long *gap) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_recv, h_pay; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(amount) FILTER (WHERE acct='RECV' AND dc='D'), 0), + coalesce(sum(amount) FILTER (WHERE acct='MERCH_PAY' AND dc='C'), 0) + INTO :h_recv, :h_pay FROM lg_ledger_line WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_recv_pay_gap FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *recv = h_recv; *pay = h_pay; *gap = h_recv - h_pay; + return 0; +} diff --git a/app/src/lg/dbio/lg_reverse_count_dbio.h b/app/src/lg/dbio/lg_reverse_count_dbio.h new file mode 100644 index 0000000..f503001 --- /dev/null +++ b/app/src/lg/dbio/lg_reverse_count_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_reverse_count_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_reverse_count_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 역분개(REVERSE) 전표 건수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_REVERSE_COUNT_DBIO_H +#define LG_REVERSE_COUNT_DBIO_H + +long lgqdb_reverse_count(const char *bizdate); +#endif /* LG_REVERSE_COUNT_DBIO_H */ diff --git a/app/src/lg/dbio/lg_reverse_count_dbio.pgc b/app/src/lg/dbio/lg_reverse_count_dbio.pgc new file mode 100644 index 0000000..401c8fd --- /dev/null +++ b/app/src/lg/dbio/lg_reverse_count_dbio.pgc @@ -0,0 +1,22 @@ +/* + * lg_reverse_count_dbio.pgc - lg 모듈 DB 접근 함수 (lg_reverse_count_dbio), archived into liblgdbio.a. + * 역분개(REVERSE) 전표 건수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_reverse_count_dbio.h" + +/* 당일 역분개(REVERSE) 전표 수. */ +long lgqdb_reverse_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + 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 lg_voucher + WHERE biz_date = :h_bizdate AND voucher_type = 'REVERSE'; + if (sqlca.sqlcode < 0) { userlog("lgqdb_reverse_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_settle_join_dbio.h b/app/src/lg/dbio/lg_settle_join_dbio.h new file mode 100644 index 0000000..5b5490f --- /dev/null +++ b/app/src/lg/dbio/lg_settle_join_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_settle_join_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_settle_join_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 매입(purchase) net 과 전기 원장 대변 정합 대사. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_SETTLE_JOIN_DBIO_H +#define LG_SETTLE_JOIN_DBIO_H + +int lgqdb_settle_join(const char *bizdate, long *purch_net, long *ledger_pay); +#endif /* LG_SETTLE_JOIN_DBIO_H */ diff --git a/app/src/lg/dbio/lg_settle_join_dbio.pgc b/app/src/lg/dbio/lg_settle_join_dbio.pgc new file mode 100644 index 0000000..dbc3cb8 --- /dev/null +++ b/app/src/lg/dbio/lg_settle_join_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_settle_join_dbio.pgc - lg 모듈 DB 접근 함수 (lg_settle_join_dbio), archived into liblgdbio.a. + * 매입(purchase) net 과 전기 원장 대변 정합 대사. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_settle_join_dbio.h" + +/* purchase.net 합과 lg_ledger_line MERCH_PAY 대변합을 조인 없이 각각 집계해 대사. */ +int lgqdb_settle_join(const char *bizdate, long *purch_net, long *ledger_pay) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pn, h_lp; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pn FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_lp FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND acct = 'MERCH_PAY' AND dc = 'C'; + if (sqlca.sqlcode < 0) return -1; + *purch_net = h_pn; *ledger_pay = h_lp; + return 0; +} diff --git a/app/src/lg/dbio/lg_stale_purge_dbio.h b/app/src/lg/dbio/lg_stale_purge_dbio.h new file mode 100644 index 0000000..1f9e134 --- /dev/null +++ b/app/src/lg/dbio/lg_stale_purge_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_stale_purge_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_stale_purge_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 오래된 취소(X) 전표 정리 (delete count). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_STALE_PURGE_DBIO_H +#define LG_STALE_PURGE_DBIO_H + +long lgqdb_stale_purge(const char *before); +#endif /* LG_STALE_PURGE_DBIO_H */ diff --git a/app/src/lg/dbio/lg_stale_purge_dbio.pgc b/app/src/lg/dbio/lg_stale_purge_dbio.pgc new file mode 100644 index 0000000..f91b370 --- /dev/null +++ b/app/src/lg/dbio/lg_stale_purge_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_stale_purge_dbio.pgc - lg 모듈 DB 접근 함수 (lg_stale_purge_dbio), archived into liblgdbio.a. + * 오래된 취소(X) 전표 정리 (delete count). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_stale_purge_dbio.h" + +/* before 이전 취소(X) 전표와 그 라인 삭제, 삭제 라인 수 반환. */ +long lgqdb_stale_purge(const char *before) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_before[16]; + long h_lines; + EXEC SQL END DECLARE SECTION; + strncpy(h_before, before, sizeof(h_before)-1); h_before[sizeof(h_before)-1] = 0; + EXEC SQL DELETE FROM lg_ledger_line WHERE voucher_id IN + (SELECT voucher_id FROM lg_voucher WHERE status = 'X' AND biz_date < :h_before); + if (sqlca.sqlcode < 0) { userlog("lgqdb_stale_purge line FAIL [%d]", sqlca.sqlcode); return -1; } + h_lines = sqlca.sqlerrd[2]; + EXEC SQL DELETE FROM lg_voucher WHERE status = 'X' AND biz_date < :h_before; + if (sqlca.sqlcode < 0) { userlog("lgqdb_stale_purge hdr FAIL [%d]", sqlca.sqlcode); return -1; } + return h_lines; +} diff --git a/app/src/lg/dbio/lg_status_flip_dbio.h b/app/src/lg/dbio/lg_status_flip_dbio.h new file mode 100644 index 0000000..03261ba --- /dev/null +++ b/app/src/lg/dbio/lg_status_flip_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_status_flip_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_status_flip_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 전표 상태 일괄 전이 (update count). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_STATUS_FLIP_DBIO_H +#define LG_STATUS_FLIP_DBIO_H + +long lgqdb_status_flip(const char *bizdate, const char *from_st, const char *to_st); +#endif /* LG_STATUS_FLIP_DBIO_H */ diff --git a/app/src/lg/dbio/lg_status_flip_dbio.pgc b/app/src/lg/dbio/lg_status_flip_dbio.pgc new file mode 100644 index 0000000..b41aaf0 --- /dev/null +++ b/app/src/lg/dbio/lg_status_flip_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_status_flip_dbio.pgc - lg 모듈 DB 접근 함수 (lg_status_flip_dbio), archived into liblgdbio.a. + * 전표 상태 일괄 전이 (update count). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_status_flip_dbio.h" + +/* 당일 from_st -> to_st 전표 일괄 전이, 반영 건수 반환. */ +long lgqdb_status_flip(const char *bizdate, const char *from_st, const char *to_st) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_from[8], h_to[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_from, from_st, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, to_st, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + EXEC SQL UPDATE lg_voucher SET status = :h_to, updated_at = now() + WHERE biz_date = :h_bizdate AND status = :h_from; + if (sqlca.sqlcode < 0) { userlog("lgqdb_status_flip FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/lg/dbio/lg_top_merch_dbio.h b/app/src/lg/dbio/lg_top_merch_dbio.h new file mode 100644 index 0000000..57295f1 --- /dev/null +++ b/app/src/lg/dbio/lg_top_merch_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_top_merch_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_top_merch_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 최대 잔액 가맹점 (order by limit 1). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_TOP_MERCH_DBIO_H +#define LG_TOP_MERCH_DBIO_H + +int lgqdb_top_merch(const char *bizdate, char *merch_out, long *bal_out); +#endif /* LG_TOP_MERCH_DBIO_H */ diff --git a/app/src/lg/dbio/lg_top_merch_dbio.pgc b/app/src/lg/dbio/lg_top_merch_dbio.pgc new file mode 100644 index 0000000..0c02a28 --- /dev/null +++ b/app/src/lg/dbio/lg_top_merch_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_top_merch_dbio.pgc - lg 모듈 DB 접근 함수 (lg_top_merch_dbio), archived into liblgdbio.a. + * 최대 잔액 가맹점 (order by limit 1). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_top_merch_dbio.h" + +/* 당일 잔액이 가장 큰 가맹점과 잔액. */ +int lgqdb_top_merch(const char *bizdate, char *merch_out, long *bal_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_bal; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT merchant_id, balance INTO :h_merch, :h_bal + FROM lg_merch_bal WHERE biz_date = :h_bizdate + ORDER BY balance DESC LIMIT 1; + if (sqlca.sqlcode == 100) { merch_out[0] = 0; *bal_out = 0; return 0; } + if (sqlca.sqlcode < 0) return -1; + strcpy(merch_out, h_merch); *bal_out = h_bal; + return 0; +} diff --git a/app/src/lg/dbio/lg_trial_imbalance_dbio.h b/app/src/lg/dbio/lg_trial_imbalance_dbio.h new file mode 100644 index 0000000..94f6034 --- /dev/null +++ b/app/src/lg/dbio/lg_trial_imbalance_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_trial_imbalance_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_trial_imbalance_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 시산표 불균형(diff<>0) 계정 건수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_TRIAL_IMBALANCE_DBIO_H +#define LG_TRIAL_IMBALANCE_DBIO_H + +long lgqdb_trial_imbalance(const char *bizdate); +#endif /* LG_TRIAL_IMBALANCE_DBIO_H */ diff --git a/app/src/lg/dbio/lg_trial_imbalance_dbio.pgc b/app/src/lg/dbio/lg_trial_imbalance_dbio.pgc new file mode 100644 index 0000000..94aff54 --- /dev/null +++ b/app/src/lg/dbio/lg_trial_imbalance_dbio.pgc @@ -0,0 +1,22 @@ +/* + * lg_trial_imbalance_dbio.pgc - lg 모듈 DB 접근 함수 (lg_trial_imbalance_dbio), archived into liblgdbio.a. + * 시산표 불균형(diff<>0) 계정 건수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_trial_imbalance_dbio.h" + +/* 시산표에서 대차 불일치 계정 수. */ +long lgqdb_trial_imbalance(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + 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 lg_trial_bal + WHERE biz_date = :h_bizdate AND diff <> 0; + if (sqlca.sqlcode < 0) { userlog("lgqdb_trial_imbalance FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_voucher_count_dbio.h b/app/src/lg/dbio/lg_voucher_count_dbio.h new file mode 100644 index 0000000..41673d0 --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_count_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_voucher_count_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_voucher_count_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 전표유형별 당일 전표 건수 집계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_VOUCHER_COUNT_DBIO_H +#define LG_VOUCHER_COUNT_DBIO_H + +long lgqdb_voucher_count(const char *bizdate, const char *vtype); +#endif /* LG_VOUCHER_COUNT_DBIO_H */ diff --git a/app/src/lg/dbio/lg_voucher_count_dbio.pgc b/app/src/lg/dbio/lg_voucher_count_dbio.pgc new file mode 100644 index 0000000..6dcfe57 --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_count_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_voucher_count_dbio.pgc - lg 모듈 DB 접근 함수 (lg_voucher_count_dbio), archived into liblgdbio.a. + * 전표유형별 당일 전표 건수 집계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_voucher_count_dbio.h" + +/* 특정 유형(vtype) 전표의 당일 건수. */ +long lgqdb_voucher_count(const char *bizdate, const char *vtype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_type, vtype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_voucher + WHERE biz_date = :h_bizdate AND voucher_type = :h_type; + if (sqlca.sqlcode < 0) { userlog("lgqdb_voucher_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_voucher_lines_dbio.h b/app/src/lg/dbio/lg_voucher_lines_dbio.h new file mode 100644 index 0000000..00309bd --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_lines_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_voucher_lines_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_voucher_lines_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 전표별 원장 라인 건수. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_VOUCHER_LINES_DBIO_H +#define LG_VOUCHER_LINES_DBIO_H + +long lgqdb_voucher_lines(long vid); +#endif /* LG_VOUCHER_LINES_DBIO_H */ diff --git a/app/src/lg/dbio/lg_voucher_lines_dbio.pgc b/app/src/lg/dbio/lg_voucher_lines_dbio.pgc new file mode 100644 index 0000000..a122fdd --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_lines_dbio.pgc @@ -0,0 +1,19 @@ +/* + * lg_voucher_lines_dbio.pgc - lg 모듈 DB 접근 함수 (lg_voucher_lines_dbio), archived into liblgdbio.a. + * 전표별 원장 라인 건수. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_voucher_lines_dbio.h" + +/* 특정 전표에 매달린 분개 라인 수. */ +long lgqdb_voucher_lines(long vid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_vid = vid, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_ledger_line WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) { userlog("lgqdb_voucher_lines FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_zero_bal_dbio.h b/app/src/lg/dbio/lg_zero_bal_dbio.h new file mode 100644 index 0000000..8a997bf --- /dev/null +++ b/app/src/lg/dbio/lg_zero_bal_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_zero_bal_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_zero_bal_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * 잔액 0 인 계정 수 (청산 계정). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_ZERO_BAL_DBIO_H +#define LG_ZERO_BAL_DBIO_H + +long lgqdb_zero_bal(const char *bizdate); +#endif /* LG_ZERO_BAL_DBIO_H */ diff --git a/app/src/lg/dbio/lg_zero_bal_dbio.pgc b/app/src/lg/dbio/lg_zero_bal_dbio.pgc new file mode 100644 index 0000000..dcfd39c --- /dev/null +++ b/app/src/lg/dbio/lg_zero_bal_dbio.pgc @@ -0,0 +1,22 @@ +/* + * lg_zero_bal_dbio.pgc - lg 모듈 DB 접근 함수 (lg_zero_bal_dbio), archived into liblgdbio.a. + * 잔액 0 인 계정 수 (청산 계정). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_zero_bal_dbio.h" + +/* 잔액이 정확히 0 인 계정 수. */ +long lgqdb_zero_bal(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + 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 lg_account_bal + WHERE biz_date = :h_bizdate AND balance = 0; + if (sqlca.sqlcode < 0) { userlog("lgqdb_zero_bal FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/lg/lg_svr.pgc b/app/src/lg/lg_svr.pgc index 982561f..03dae28 100644 --- a/app/src/lg/lg_svr.pgc +++ b/app/src/lg/lg_svr.pgc @@ -1,54 +1,59 @@ /* - * lg_svr.pgc - lg (원장/ledger) MODULE SERVER. + * lg_svr.pgc - lg (원장/ledger) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of lg's 30 online services now lives in its own file + * (app/src/lg/svc/.pgc) with its own copybook header + * (app/src/lg/svc/.h); those objects are archived into liblgsvc.a and + * linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all services from a {name, fn} table, + * - tpclose() at shutdown. * * 복식부기 원칙: 모든 전표는 차변(D) 합 = 대변(C) 합. 계정잔액 balance = 차변-대변. + * 모든 전기(posting)는 대변/차변 균형 분개로 이루어진다. * * 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 -#include -#include #include #include #include -#include -#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); } +/* every lg service's copybook header (declares its record struct + prototype) */ +#include "LG_POST.h" +#include "LG_CREDIT.h" +#include "LG_DEBIT.h" +#include "LG_DOUBLE.h" +#include "LG_BALCHK.h" +#include "LG_REVERSE.h" +#include "LG_CARRY.h" +#include "LG_CLOSE_LINK.h" +#include "LG_ACCTSUM.h" +#include "LG_RECALC.h" +#include "LG_UNSETTLED.h" +#include "LG_STATS.h" +#include "LG_DIFFCHK.h" +#include "LG_CORRECT.h" +#include "LG_INQUIRY.h" +#include "LG_TRIALBAL.h" +#include "LG_JOURNAL.h" +#include "LG_VOUCHER.h" +#include "LG_APPLY.h" +#include "LG_ROLLFWD.h" +#include "LG_MERCHBAL.h" +#include "LG_DAILYSUM.h" +#include "LG_MONTHLYSUM.h" +#include "LG_ADJUST.h" +#include "LG_TRANSFER.h" +#include "LG_SETTLEPOST.h" +#include "LG_ACQPOST.h" +#include "LG_FEEPOST.h" +#include "LG_STATUS.h" +#include "LG_CONFIRM.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, @@ -81,7 +86,7 @@ int tpsvrinit(int argc, char **argv) return -1; } } - userlog("lg_svr: %d lg 서비스 광고 완료, RM 개방", i); + userlog("lg_svr: %d lg 서비스 광고 완료, RM 개방 (복식부기 대변/차변)", i); return 0; } @@ -91,613 +96,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/lg/run/run_acctbal.sh b/app/src/lg/run/run_acctbal.sh new file mode 100755 index 0000000..154ae1b --- /dev/null +++ b/app/src/lg/run/run_acctbal.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_acctbal.sh - 계정잔액 재집계 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_acctbal.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_acctbal.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_acctbal_batch "$BIZDATE" diff --git a/app/src/lg/run/run_apply.sh b/app/src/lg/run/run_apply.sh new file mode 100755 index 0000000..a5ec345 --- /dev/null +++ b/app/src/lg/run/run_apply.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_apply.sh - 작성전표 전기확정 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_apply.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_apply.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_apply_batch "$BIZDATE" diff --git a/app/src/lg/run/run_carry.sh b/app/src/lg/run/run_carry.sh new file mode 100755 index 0000000..f08850d --- /dev/null +++ b/app/src/lg/run/run_carry.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_carry.sh - 계정잔액 익일 이월 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_carry.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_carry.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_carry_batch "$BIZDATE" diff --git a/app/src/lg/run/run_diffcheck.sh b/app/src/lg/run/run_diffcheck.sh new file mode 100755 index 0000000..a0fede9 --- /dev/null +++ b/app/src/lg/run/run_diffcheck.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_diffcheck.sh - 당일 대차 균형 점검 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_diffcheck.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_diffcheck.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_diffcheck_batch "$BIZDATE" diff --git a/app/src/lg/run/run_eom.sh b/app/src/lg/run/run_eom.sh new file mode 100755 index 0000000..fd8f06f --- /dev/null +++ b/app/src/lg/run/run_eom.sh @@ -0,0 +1,20 @@ +#!/bin/bash +## +## run_eom.sh - lg 월마감 파이프라인 (여러 배치 일괄 실행). +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치들을 순차 실행. +## Usage: run_eom.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_eom.sh] BIZDATE=$BIZDATE" +for b in lg_post_batch \ + lg_apply_batch \ + lg_acctbal_batch \ + lg_trialbal_batch \ + lg_trialpost_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/lg/run/run_fee.sh b/app/src/lg/run/run_fee.sh new file mode 100755 index 0000000..fd1487e --- /dev/null +++ b/app/src/lg/run/run_fee.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_fee.sh - 수수료수익 전기 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_fee.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_fee.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_fee_batch "$BIZDATE" diff --git a/app/src/lg/run/run_journal.sh b/app/src/lg/run/run_journal.sh new file mode 100755 index 0000000..b41ac12 --- /dev/null +++ b/app/src/lg/run/run_journal.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_journal.sh - 전표유형별 분개 통계 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_journal.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_journal.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_journal_batch "$BIZDATE" diff --git a/app/src/lg/run/run_ledger.sh b/app/src/lg/run/run_ledger.sh new file mode 100755 index 0000000..4dcb27e --- /dev/null +++ b/app/src/lg/run/run_ledger.sh @@ -0,0 +1,18 @@ +#!/bin/bash +## +## run_ledger.sh - lg 원장 점검 파이프라인 (여러 배치 일괄 실행). +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치들을 순차 실행. +## Usage: run_ledger.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_ledger.sh] BIZDATE=$BIZDATE" +for b in lg_feeincome_batch \ + lg_diffcheck_batch \ + lg_report_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/lg/run/run_merchbal.sh b/app/src/lg/run/run_merchbal.sh new file mode 100755 index 0000000..4b9c244 --- /dev/null +++ b/app/src/lg/run/run_merchbal.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_merchbal.sh - 가맹점 잔액 재집계 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_merchbal.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_merchbal.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_merchbal_batch "$BIZDATE" diff --git a/app/src/lg/run/run_post.sh b/app/src/lg/run/run_post.sh new file mode 100755 index 0000000..50c45fe --- /dev/null +++ b/app/src/lg/run/run_post.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_post.sh - 매입 원장 전기 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_post.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_post.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_post_batch "$BIZDATE" diff --git a/app/src/lg/run/run_recalc.sh b/app/src/lg/run/run_recalc.sh new file mode 100755 index 0000000..eee7918 --- /dev/null +++ b/app/src/lg/run/run_recalc.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_recalc.sh - 계정잔액 재계산 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_recalc.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_recalc.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_recalc_batch "$BIZDATE" diff --git a/app/src/lg/run/run_report.sh b/app/src/lg/run/run_report.sh new file mode 100755 index 0000000..2de2135 --- /dev/null +++ b/app/src/lg/run/run_report.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_report.sh - 계정잔액 상위 리포트 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_report.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_report.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_report_batch "$BIZDATE" diff --git a/app/src/lg/run/run_reverse.sh b/app/src/lg/run/run_reverse.sh new file mode 100755 index 0000000..64b796c --- /dev/null +++ b/app/src/lg/run/run_reverse.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_reverse.sh - 조정전표 역분개 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_reverse.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_reverse.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_reverse_batch "$BIZDATE" diff --git a/app/src/lg/run/run_settle.sh b/app/src/lg/run/run_settle.sh new file mode 100755 index 0000000..395704d --- /dev/null +++ b/app/src/lg/run/run_settle.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_settle.sh - 정산 전기 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_settle.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_settle.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_settle_batch "$BIZDATE" diff --git a/app/src/lg/run/run_transfer.sh b/app/src/lg/run/run_transfer.sh new file mode 100755 index 0000000..e813f7b --- /dev/null +++ b/app/src/lg/run/run_transfer.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_transfer.sh - 미지급금 현금대체 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_transfer.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_transfer.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_transfer_batch "$BIZDATE" diff --git a/app/src/lg/run/run_trialbal.sh b/app/src/lg/run/run_trialbal.sh new file mode 100755 index 0000000..7a64efa --- /dev/null +++ b/app/src/lg/run/run_trialbal.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_trialbal.sh - 시산표 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_trialbal.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_trialbal.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_trialbal_batch "$BIZDATE" diff --git a/app/src/lg/run/run_vacuum.sh b/app/src/lg/run/run_vacuum.sh new file mode 100755 index 0000000..2fdd0b0 --- /dev/null +++ b/app/src/lg/run/run_vacuum.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_vacuum.sh - 취소전표 정리 배치. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치를 실행. +## Usage: run_vacuum.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_vacuum.sh] BIZDATE=$BIZDATE" +exec /app/bin/lg_vacuum_batch "$BIZDATE" diff --git a/app/src/lg/svc/LG_ACCTSUM.h b/app/src/lg/svc/LG_ACCTSUM.h new file mode 100644 index 0000000..f4826e6 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTSUM.h @@ -0,0 +1,21 @@ +/* + * LG_ACCTSUM.h - lg 서비스 LG_ACCTSUM 카피북 (copybook / record header). + * 계정 잔액 재집계(dbio) 후 잔액 반환. + */ +#ifndef LG_ACCTSUM_H +#define LG_ACCTSUM_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_acctsum_rec_t; + +void LG_ACCTSUM(TPSVCINFO *p); + +#endif /* LG_ACCTSUM_H */ diff --git a/app/src/lg/svc/LG_ACCTSUM.pgc b/app/src/lg/svc/LG_ACCTSUM.pgc new file mode 100644 index 0000000..1c54fb2 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTSUM.pgc @@ -0,0 +1,22 @@ +/* + * LG_ACCTSUM.pgc - lg 모듈 서비스 LG_ACCTSUM (one service = one file). + * 계정 잔액 재집계(dbio) 후 잔액 반환. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_ACCTSUM.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ACQPOST.h b/app/src/lg/svc/LG_ACQPOST.h new file mode 100644 index 0000000..7cff67e --- /dev/null +++ b/app/src/lg/svc/LG_ACQPOST.h @@ -0,0 +1,21 @@ +/* + * LG_ACQPOST.h - lg 서비스 LG_ACQPOST 카피북 (copybook / record header). + * 매입 전기: 차변 RECV=amount, 대변 MERCH_PAY=net + FEE_INC=fee. + */ +#ifndef LG_ACQPOST_H +#define LG_ACQPOST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_acqpost_rec_t; + +void LG_ACQPOST(TPSVCINFO *p); + +#endif /* LG_ACQPOST_H */ diff --git a/app/src/lg/svc/LG_ACQPOST.pgc b/app/src/lg/svc/LG_ACQPOST.pgc new file mode 100644 index 0000000..14b730d --- /dev/null +++ b/app/src/lg/svc/LG_ACQPOST.pgc @@ -0,0 +1,31 @@ +/* + * LG_ACQPOST.pgc - lg 모듈 서비스 LG_ACQPOST (one service = one file). + * 매입 전기: 차변 RECV=amount, 대변 MERCH_PAY=net + FEE_INC=fee. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_ACQPOST.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ADJUST.h b/app/src/lg/svc/LG_ADJUST.h new file mode 100644 index 0000000..bd93728 --- /dev/null +++ b/app/src/lg/svc/LG_ADJUST.h @@ -0,0 +1,21 @@ +/* + * LG_ADJUST.h - lg 서비스 LG_ADJUST 카피북 (copybook / record header). + * 조정 전표: 부호에 따라 차변/대변 단일 분개. + */ +#ifndef LG_ADJUST_H +#define LG_ADJUST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_adjust_rec_t; + +void LG_ADJUST(TPSVCINFO *p); + +#endif /* LG_ADJUST_H */ diff --git a/app/src/lg/svc/LG_ADJUST.pgc b/app/src/lg/svc/LG_ADJUST.pgc new file mode 100644 index 0000000..eb06371 --- /dev/null +++ b/app/src/lg/svc/LG_ADJUST.pgc @@ -0,0 +1,29 @@ +/* + * LG_ADJUST.pgc - lg 모듈 서비스 LG_ADJUST (one service = one file). + * 조정 전표: 부호에 따라 차변/대변 단일 분개. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_ADJUST.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_APPLY.h b/app/src/lg/svc/LG_APPLY.h new file mode 100644 index 0000000..4ce7ca0 --- /dev/null +++ b/app/src/lg/svc/LG_APPLY.h @@ -0,0 +1,21 @@ +/* + * LG_APPLY.h - lg 서비스 LG_APPLY 카피북 (copybook / record header). + * 전표 전기확정(P): 라인들의 계정잔액을 잔액테이블에 반영. + */ +#ifndef LG_APPLY_H +#define LG_APPLY_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_apply_rec_t; + +void LG_APPLY(TPSVCINFO *p); + +#endif /* LG_APPLY_H */ diff --git a/app/src/lg/svc/LG_APPLY.pgc b/app/src/lg/svc/LG_APPLY.pgc new file mode 100644 index 0000000..a869801 --- /dev/null +++ b/app/src/lg/svc/LG_APPLY.pgc @@ -0,0 +1,39 @@ +/* + * LG_APPLY.pgc - lg 모듈 서비스 LG_APPLY (one service = one file). + * 전표 전기확정(P): 라인들의 계정잔액을 잔액테이블에 반영. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_APPLY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BALCHK.h b/app/src/lg/svc/LG_BALCHK.h new file mode 100644 index 0000000..1db486c --- /dev/null +++ b/app/src/lg/svc/LG_BALCHK.h @@ -0,0 +1,21 @@ +/* + * LG_BALCHK.h - lg 서비스 LG_BALCHK 카피북 (copybook / record header). + * 전표 단위 대차 균형 점검 (차변합 vs 대변합). + */ +#ifndef LG_BALCHK_H +#define LG_BALCHK_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_balchk_rec_t; + +void LG_BALCHK(TPSVCINFO *p); + +#endif /* LG_BALCHK_H */ diff --git a/app/src/lg/svc/LG_BALCHK.pgc b/app/src/lg/svc/LG_BALCHK.pgc new file mode 100644 index 0000000..835c06f --- /dev/null +++ b/app/src/lg/svc/LG_BALCHK.pgc @@ -0,0 +1,28 @@ +/* + * LG_BALCHK.pgc - lg 모듈 서비스 LG_BALCHK (one service = one file). + * 전표 단위 대차 균형 점검 (차변합 vs 대변합). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_BALCHK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_CARRY.h b/app/src/lg/svc/LG_CARRY.h new file mode 100644 index 0000000..4c72da7 --- /dev/null +++ b/app/src/lg/svc/LG_CARRY.h @@ -0,0 +1,21 @@ +/* + * LG_CARRY.h - lg 서비스 LG_CARRY 카피북 (copybook / record header). + * 이월: 당일 계정잔액을 익영업일 개시잔액으로 이월(upsert). + */ +#ifndef LG_CARRY_H +#define LG_CARRY_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_carry_rec_t; + +void LG_CARRY(TPSVCINFO *p); + +#endif /* LG_CARRY_H */ diff --git a/app/src/lg/svc/LG_CARRY.pgc b/app/src/lg/svc/LG_CARRY.pgc new file mode 100644 index 0000000..88cbd14 --- /dev/null +++ b/app/src/lg/svc/LG_CARRY.pgc @@ -0,0 +1,38 @@ +/* + * LG_CARRY.pgc - lg 모듈 서비스 LG_CARRY (one service = one file). + * 이월: 당일 계정잔액을 익영업일 개시잔액으로 이월(upsert). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_CARRY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_CLOSE_LINK.h b/app/src/lg/svc/LG_CLOSE_LINK.h new file mode 100644 index 0000000..4d19f70 --- /dev/null +++ b/app/src/lg/svc/LG_CLOSE_LINK.h @@ -0,0 +1,21 @@ +/* + * LG_CLOSE_LINK.h - lg 서비스 LG_CLOSE_LINK 카피북 (copybook / record header). + * 마감연계: 전표 라인 closed + 전표 상태 S(마감연계). + */ +#ifndef LG_CLOSE_LINK_H +#define LG_CLOSE_LINK_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_close_link_rec_t; + +void LG_CLOSE_LINK(TPSVCINFO *p); + +#endif /* LG_CLOSE_LINK_H */ diff --git a/app/src/lg/svc/LG_CLOSE_LINK.pgc b/app/src/lg/svc/LG_CLOSE_LINK.pgc new file mode 100644 index 0000000..557c72a --- /dev/null +++ b/app/src/lg/svc/LG_CLOSE_LINK.pgc @@ -0,0 +1,25 @@ +/* + * LG_CLOSE_LINK.pgc - lg 모듈 서비스 LG_CLOSE_LINK (one service = one file). + * 마감연계: 전표 라인 closed + 전표 상태 S(마감연계). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_CLOSE_LINK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_CONFIRM.h b/app/src/lg/svc/LG_CONFIRM.h new file mode 100644 index 0000000..ff6de61 --- /dev/null +++ b/app/src/lg/svc/LG_CONFIRM.h @@ -0,0 +1,21 @@ +/* + * LG_CONFIRM.h - lg 서비스 LG_CONFIRM 카피북 (copybook / record header). + * 전표 확정: 대차 균형이면 P로 확정, 아니면 실패. + */ +#ifndef LG_CONFIRM_H +#define LG_CONFIRM_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_confirm_rec_t; + +void LG_CONFIRM(TPSVCINFO *p); + +#endif /* LG_CONFIRM_H */ diff --git a/app/src/lg/svc/LG_CONFIRM.pgc b/app/src/lg/svc/LG_CONFIRM.pgc new file mode 100644 index 0000000..6f47052 --- /dev/null +++ b/app/src/lg/svc/LG_CONFIRM.pgc @@ -0,0 +1,31 @@ +/* + * LG_CONFIRM.pgc - lg 모듈 서비스 LG_CONFIRM (one service = one file). + * 전표 확정: 대차 균형이면 P로 확정, 아니면 실패. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_CONFIRM.h" + +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: */ diff --git a/app/src/lg/svc/LG_CORRECT.h b/app/src/lg/svc/LG_CORRECT.h new file mode 100644 index 0000000..5d0b556 --- /dev/null +++ b/app/src/lg/svc/LG_CORRECT.h @@ -0,0 +1,21 @@ +/* + * LG_CORRECT.h - lg 서비스 LG_CORRECT 카피북 (copybook / record header). + * 원장 라인 금액 정정. + */ +#ifndef LG_CORRECT_H +#define LG_CORRECT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_correct_rec_t; + +void LG_CORRECT(TPSVCINFO *p); + +#endif /* LG_CORRECT_H */ diff --git a/app/src/lg/svc/LG_CORRECT.pgc b/app/src/lg/svc/LG_CORRECT.pgc new file mode 100644 index 0000000..0f8023e --- /dev/null +++ b/app/src/lg/svc/LG_CORRECT.pgc @@ -0,0 +1,22 @@ +/* + * LG_CORRECT.pgc - lg 모듈 서비스 LG_CORRECT (one service = one file). + * 원장 라인 금액 정정. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_CORRECT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_CREDIT.h b/app/src/lg/svc/LG_CREDIT.h new file mode 100644 index 0000000..f98cef1 --- /dev/null +++ b/app/src/lg/svc/LG_CREDIT.h @@ -0,0 +1,21 @@ +/* + * LG_CREDIT.h - lg 서비스 LG_CREDIT 카피북 (copybook / record header). + * 기존 전표에 대변(C) 분개 1줄 추가. + */ +#ifndef LG_CREDIT_H +#define LG_CREDIT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_credit_rec_t; + +void LG_CREDIT(TPSVCINFO *p); + +#endif /* LG_CREDIT_H */ diff --git a/app/src/lg/svc/LG_CREDIT.pgc b/app/src/lg/svc/LG_CREDIT.pgc new file mode 100644 index 0000000..e6da59f --- /dev/null +++ b/app/src/lg/svc/LG_CREDIT.pgc @@ -0,0 +1,22 @@ +/* + * LG_CREDIT.pgc - lg 모듈 서비스 LG_CREDIT (one service = one file). + * 기존 전표에 대변(C) 분개 1줄 추가. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_CREDIT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DAILYSUM.h b/app/src/lg/svc/LG_DAILYSUM.h new file mode 100644 index 0000000..db97845 --- /dev/null +++ b/app/src/lg/svc/LG_DAILYSUM.h @@ -0,0 +1,21 @@ +/* + * LG_DAILYSUM.h - lg 서비스 LG_DAILYSUM 카피북 (copybook / record header). + * 당일 계정별 잔액 일괄 재집계(전 계정 upsert). + */ +#ifndef LG_DAILYSUM_H +#define LG_DAILYSUM_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_dailysum_rec_t; + +void LG_DAILYSUM(TPSVCINFO *p); + +#endif /* LG_DAILYSUM_H */ diff --git a/app/src/lg/svc/LG_DAILYSUM.pgc b/app/src/lg/svc/LG_DAILYSUM.pgc new file mode 100644 index 0000000..98a09a7 --- /dev/null +++ b/app/src/lg/svc/LG_DAILYSUM.pgc @@ -0,0 +1,39 @@ +/* + * LG_DAILYSUM.pgc - lg 모듈 서비스 LG_DAILYSUM (one service = one file). + * 당일 계정별 잔액 일괄 재집계(전 계정 upsert). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_DAILYSUM.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DEBIT.h b/app/src/lg/svc/LG_DEBIT.h new file mode 100644 index 0000000..01b2b4e --- /dev/null +++ b/app/src/lg/svc/LG_DEBIT.h @@ -0,0 +1,21 @@ +/* + * LG_DEBIT.h - lg 서비스 LG_DEBIT 카피북 (copybook / record header). + * 기존 전표에 차변(D) 분개 1줄 추가. + */ +#ifndef LG_DEBIT_H +#define LG_DEBIT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_debit_rec_t; + +void LG_DEBIT(TPSVCINFO *p); + +#endif /* LG_DEBIT_H */ diff --git a/app/src/lg/svc/LG_DEBIT.pgc b/app/src/lg/svc/LG_DEBIT.pgc new file mode 100644 index 0000000..5ce891a --- /dev/null +++ b/app/src/lg/svc/LG_DEBIT.pgc @@ -0,0 +1,22 @@ +/* + * LG_DEBIT.pgc - lg 모듈 서비스 LG_DEBIT (one service = one file). + * 기존 전표에 차변(D) 분개 1줄 추가. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_DEBIT.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DIFFCHK.h b/app/src/lg/svc/LG_DIFFCHK.h new file mode 100644 index 0000000..d232536 --- /dev/null +++ b/app/src/lg/svc/LG_DIFFCHK.h @@ -0,0 +1,21 @@ +/* + * LG_DIFFCHK.h - lg 서비스 LG_DIFFCHK 카피북 (copybook / record header). + * 당일 전체 대차 차액 점검(총차변-총대변). + */ +#ifndef LG_DIFFCHK_H +#define LG_DIFFCHK_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_diffchk_rec_t; + +void LG_DIFFCHK(TPSVCINFO *p); + +#endif /* LG_DIFFCHK_H */ diff --git a/app/src/lg/svc/LG_DIFFCHK.pgc b/app/src/lg/svc/LG_DIFFCHK.pgc new file mode 100644 index 0000000..64bc872 --- /dev/null +++ b/app/src/lg/svc/LG_DIFFCHK.pgc @@ -0,0 +1,30 @@ +/* + * LG_DIFFCHK.pgc - lg 모듈 서비스 LG_DIFFCHK (one service = one file). + * 당일 전체 대차 차액 점검(총차변-총대변). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_DIFFCHK.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DOUBLE.h b/app/src/lg/svc/LG_DOUBLE.h new file mode 100644 index 0000000..aa78e43 --- /dev/null +++ b/app/src/lg/svc/LG_DOUBLE.h @@ -0,0 +1,21 @@ +/* + * LG_DOUBLE.h - lg 서비스 LG_DOUBLE 카피북 (copybook / record header). + * 신규 전표 즉시 전기(posted): 차변/대변 + 양 계정잔액 반영. + */ +#ifndef LG_DOUBLE_H +#define LG_DOUBLE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_double_rec_t; + +void LG_DOUBLE(TPSVCINFO *p); + +#endif /* LG_DOUBLE_H */ diff --git a/app/src/lg/svc/LG_DOUBLE.pgc b/app/src/lg/svc/LG_DOUBLE.pgc new file mode 100644 index 0000000..fc95a80 --- /dev/null +++ b/app/src/lg/svc/LG_DOUBLE.pgc @@ -0,0 +1,34 @@ +/* + * LG_DOUBLE.pgc - lg 모듈 서비스 LG_DOUBLE (one service = one file). + * 신규 전표 즉시 전기(posted): 차변/대변 + 양 계정잔액 반영. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_DOUBLE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_FEEPOST.h b/app/src/lg/svc/LG_FEEPOST.h new file mode 100644 index 0000000..585da23 --- /dev/null +++ b/app/src/lg/svc/LG_FEEPOST.h @@ -0,0 +1,21 @@ +/* + * LG_FEEPOST.h - lg 서비스 LG_FEEPOST 카피북 (copybook / record header). + * 수수료수익 전기: 차변 RECV=fee, 대변 FEE_INC=fee. + */ +#ifndef LG_FEEPOST_H +#define LG_FEEPOST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_feepost_rec_t; + +void LG_FEEPOST(TPSVCINFO *p); + +#endif /* LG_FEEPOST_H */ diff --git a/app/src/lg/svc/LG_FEEPOST.pgc b/app/src/lg/svc/LG_FEEPOST.pgc new file mode 100644 index 0000000..caf39ce --- /dev/null +++ b/app/src/lg/svc/LG_FEEPOST.pgc @@ -0,0 +1,26 @@ +/* + * LG_FEEPOST.pgc - lg 모듈 서비스 LG_FEEPOST (one service = one file). + * 수수료수익 전기: 차변 RECV=fee, 대변 FEE_INC=fee. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_FEEPOST.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_INQUIRY.h b/app/src/lg/svc/LG_INQUIRY.h new file mode 100644 index 0000000..5964a6a --- /dev/null +++ b/app/src/lg/svc/LG_INQUIRY.h @@ -0,0 +1,21 @@ +/* + * LG_INQUIRY.h - lg 서비스 LG_INQUIRY 카피북 (copybook / record header). + * 전표 상태 조회(dbio). + */ +#ifndef LG_INQUIRY_H +#define LG_INQUIRY_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_inquiry_rec_t; + +void LG_INQUIRY(TPSVCINFO *p); + +#endif /* LG_INQUIRY_H */ diff --git a/app/src/lg/svc/LG_INQUIRY.pgc b/app/src/lg/svc/LG_INQUIRY.pgc new file mode 100644 index 0000000..b460a50 --- /dev/null +++ b/app/src/lg/svc/LG_INQUIRY.pgc @@ -0,0 +1,19 @@ +/* + * LG_INQUIRY.pgc - lg 모듈 서비스 LG_INQUIRY (one service = one file). + * 전표 상태 조회(dbio). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_INQUIRY.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_JOURNAL.h b/app/src/lg/svc/LG_JOURNAL.h new file mode 100644 index 0000000..31781ae --- /dev/null +++ b/app/src/lg/svc/LG_JOURNAL.h @@ -0,0 +1,21 @@ +/* + * LG_JOURNAL.h - lg 서비스 LG_JOURNAL 카피북 (copybook / record header). + * 당일 분개(원장 라인) 건수 조회. + */ +#ifndef LG_JOURNAL_H +#define LG_JOURNAL_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_journal_rec_t; + +void LG_JOURNAL(TPSVCINFO *p); + +#endif /* LG_JOURNAL_H */ diff --git a/app/src/lg/svc/LG_JOURNAL.pgc b/app/src/lg/svc/LG_JOURNAL.pgc new file mode 100644 index 0000000..43b6b7b --- /dev/null +++ b/app/src/lg/svc/LG_JOURNAL.pgc @@ -0,0 +1,25 @@ +/* + * LG_JOURNAL.pgc - lg 모듈 서비스 LG_JOURNAL (one service = one file). + * 당일 분개(원장 라인) 건수 조회. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_JOURNAL.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MERCHBAL.h b/app/src/lg/svc/LG_MERCHBAL.h new file mode 100644 index 0000000..9d8aab9 --- /dev/null +++ b/app/src/lg/svc/LG_MERCHBAL.h @@ -0,0 +1,21 @@ +/* + * LG_MERCHBAL.h - lg 서비스 LG_MERCHBAL 카피북 (copybook / record header). + * 가맹점 잔액 재집계(dbio). + */ +#ifndef LG_MERCHBAL_H +#define LG_MERCHBAL_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_merchbal_rec_t; + +void LG_MERCHBAL(TPSVCINFO *p); + +#endif /* LG_MERCHBAL_H */ diff --git a/app/src/lg/svc/LG_MERCHBAL.pgc b/app/src/lg/svc/LG_MERCHBAL.pgc new file mode 100644 index 0000000..1f1acf8 --- /dev/null +++ b/app/src/lg/svc/LG_MERCHBAL.pgc @@ -0,0 +1,20 @@ +/* + * LG_MERCHBAL.pgc - lg 모듈 서비스 LG_MERCHBAL (one service = one file). + * 가맹점 잔액 재집계(dbio). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_MERCHBAL.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MONTHLYSUM.h b/app/src/lg/svc/LG_MONTHLYSUM.h new file mode 100644 index 0000000..4c53240 --- /dev/null +++ b/app/src/lg/svc/LG_MONTHLYSUM.h @@ -0,0 +1,21 @@ +/* + * LG_MONTHLYSUM.h - lg 서비스 LG_MONTHLYSUM 카피북 (copybook / record header). + * 월 계정과목별 차/대 집계 건수 조회(YYYY-MM). + */ +#ifndef LG_MONTHLYSUM_H +#define LG_MONTHLYSUM_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_monthlysum_rec_t; + +void LG_MONTHLYSUM(TPSVCINFO *p); + +#endif /* LG_MONTHLYSUM_H */ diff --git a/app/src/lg/svc/LG_MONTHLYSUM.pgc b/app/src/lg/svc/LG_MONTHLYSUM.pgc new file mode 100644 index 0000000..d654f66 --- /dev/null +++ b/app/src/lg/svc/LG_MONTHLYSUM.pgc @@ -0,0 +1,30 @@ +/* + * LG_MONTHLYSUM.pgc - lg 모듈 서비스 LG_MONTHLYSUM (one service = one file). + * 월 계정과목별 차/대 집계 건수 조회(YYYY-MM). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_MONTHLYSUM.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_POST.h b/app/src/lg/svc/LG_POST.h new file mode 100644 index 0000000..a4a4b21 --- /dev/null +++ b/app/src/lg/svc/LG_POST.h @@ -0,0 +1,21 @@ +/* + * LG_POST.h - lg 서비스 LG_POST 카피북 (copybook / record header). + * 전표 작성(draft) + 차변/대변 균형 분개 2줄. + */ +#ifndef LG_POST_H +#define LG_POST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_post_rec_t; + +void LG_POST(TPSVCINFO *p); + +#endif /* LG_POST_H */ diff --git a/app/src/lg/svc/LG_POST.pgc b/app/src/lg/svc/LG_POST.pgc new file mode 100644 index 0000000..6721b65 --- /dev/null +++ b/app/src/lg/svc/LG_POST.pgc @@ -0,0 +1,34 @@ +/* + * LG_POST.pgc - lg 모듈 서비스 LG_POST (one service = one file). + * 전표 작성(draft) + 차변/대변 균형 분개 2줄. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_POST.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_RECALC.h b/app/src/lg/svc/LG_RECALC.h new file mode 100644 index 0000000..ad96a0d --- /dev/null +++ b/app/src/lg/svc/LG_RECALC.h @@ -0,0 +1,21 @@ +/* + * LG_RECALC.h - lg 서비스 LG_RECALC 카피북 (copybook / record header). + * 계정 잔액 원장 라인 기준 재계산(dbio). + */ +#ifndef LG_RECALC_H +#define LG_RECALC_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_recalc_rec_t; + +void LG_RECALC(TPSVCINFO *p); + +#endif /* LG_RECALC_H */ diff --git a/app/src/lg/svc/LG_RECALC.pgc b/app/src/lg/svc/LG_RECALC.pgc new file mode 100644 index 0000000..7bc0102 --- /dev/null +++ b/app/src/lg/svc/LG_RECALC.pgc @@ -0,0 +1,19 @@ +/* + * LG_RECALC.pgc - lg 모듈 서비스 LG_RECALC (one service = one file). + * 계정 잔액 원장 라인 기준 재계산(dbio). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_RECALC.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_REVERSE.h b/app/src/lg/svc/LG_REVERSE.h new file mode 100644 index 0000000..41658b7 --- /dev/null +++ b/app/src/lg/svc/LG_REVERSE.h @@ -0,0 +1,21 @@ +/* + * LG_REVERSE.h - lg 서비스 LG_REVERSE 카피북 (copybook / record header). + * 역분개 전표: 원전표 라인의 차/대를 뒤집어 신규 전표에 복사. + */ +#ifndef LG_REVERSE_H +#define LG_REVERSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_reverse_rec_t; + +void LG_REVERSE(TPSVCINFO *p); + +#endif /* LG_REVERSE_H */ diff --git a/app/src/lg/svc/LG_REVERSE.pgc b/app/src/lg/svc/LG_REVERSE.pgc new file mode 100644 index 0000000..0a15da0 --- /dev/null +++ b/app/src/lg/svc/LG_REVERSE.pgc @@ -0,0 +1,38 @@ +/* + * LG_REVERSE.pgc - lg 모듈 서비스 LG_REVERSE (one service = one file). + * 역분개 전표: 원전표 라인의 차/대를 뒤집어 신규 전표에 복사. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_REVERSE.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ROLLFWD.h b/app/src/lg/svc/LG_ROLLFWD.h new file mode 100644 index 0000000..ab51bc6 --- /dev/null +++ b/app/src/lg/svc/LG_ROLLFWD.h @@ -0,0 +1,21 @@ +/* + * LG_ROLLFWD.h - lg 서비스 LG_ROLLFWD 카피북 (copybook / record header). + * 전 계정 잔액 익일 이월(개시잔액 롤포워드). + */ +#ifndef LG_ROLLFWD_H +#define LG_ROLLFWD_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_rollfwd_rec_t; + +void LG_ROLLFWD(TPSVCINFO *p); + +#endif /* LG_ROLLFWD_H */ diff --git a/app/src/lg/svc/LG_ROLLFWD.pgc b/app/src/lg/svc/LG_ROLLFWD.pgc new file mode 100644 index 0000000..2a806b2 --- /dev/null +++ b/app/src/lg/svc/LG_ROLLFWD.pgc @@ -0,0 +1,33 @@ +/* + * LG_ROLLFWD.pgc - lg 모듈 서비스 LG_ROLLFWD (one service = one file). + * 전 계정 잔액 익일 이월(개시잔액 롤포워드). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_ROLLFWD.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_SETTLEPOST.h b/app/src/lg/svc/LG_SETTLEPOST.h new file mode 100644 index 0000000..377a378 --- /dev/null +++ b/app/src/lg/svc/LG_SETTLEPOST.h @@ -0,0 +1,21 @@ +/* + * LG_SETTLEPOST.h - lg 서비스 LG_SETTLEPOST 카피북 (copybook / record header). + * 정산 전기: 차변 MERCH_PAY, 대변 CASH (net). + */ +#ifndef LG_SETTLEPOST_H +#define LG_SETTLEPOST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_settlepost_rec_t; + +void LG_SETTLEPOST(TPSVCINFO *p); + +#endif /* LG_SETTLEPOST_H */ diff --git a/app/src/lg/svc/LG_SETTLEPOST.pgc b/app/src/lg/svc/LG_SETTLEPOST.pgc new file mode 100644 index 0000000..8b39929 --- /dev/null +++ b/app/src/lg/svc/LG_SETTLEPOST.pgc @@ -0,0 +1,28 @@ +/* + * LG_SETTLEPOST.pgc - lg 모듈 서비스 LG_SETTLEPOST (one service = one file). + * 정산 전기: 차변 MERCH_PAY, 대변 CASH (net). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_SETTLEPOST.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATS.h b/app/src/lg/svc/LG_STATS.h new file mode 100644 index 0000000..c2d62bb --- /dev/null +++ b/app/src/lg/svc/LG_STATS.h @@ -0,0 +1,21 @@ +/* + * LG_STATS.h - lg 서비스 LG_STATS 카피북 (copybook / record header). + * 당일 전표/원장 통계(전표수, 총차변). + */ +#ifndef LG_STATS_H +#define LG_STATS_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_stats_rec_t; + +void LG_STATS(TPSVCINFO *p); + +#endif /* LG_STATS_H */ diff --git a/app/src/lg/svc/LG_STATS.pgc b/app/src/lg/svc/LG_STATS.pgc new file mode 100644 index 0000000..96b4048 --- /dev/null +++ b/app/src/lg/svc/LG_STATS.pgc @@ -0,0 +1,28 @@ +/* + * LG_STATS.pgc - lg 모듈 서비스 LG_STATS (one service = one file). + * 당일 전표/원장 통계(전표수, 총차변). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_STATS.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATUS.h b/app/src/lg/svc/LG_STATUS.h new file mode 100644 index 0000000..468cd2d --- /dev/null +++ b/app/src/lg/svc/LG_STATUS.h @@ -0,0 +1,21 @@ +/* + * LG_STATUS.h - lg 서비스 LG_STATUS 카피북 (copybook / record header). + * 전표 상태 직접 변경(setter). + */ +#ifndef LG_STATUS_H +#define LG_STATUS_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_status_rec_t; + +void LG_STATUS(TPSVCINFO *p); + +#endif /* LG_STATUS_H */ diff --git a/app/src/lg/svc/LG_STATUS.pgc b/app/src/lg/svc/LG_STATUS.pgc new file mode 100644 index 0000000..03484f5 --- /dev/null +++ b/app/src/lg/svc/LG_STATUS.pgc @@ -0,0 +1,19 @@ +/* + * LG_STATUS.pgc - lg 모듈 서비스 LG_STATUS (one service = one file). + * 전표 상태 직접 변경(setter). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_STATUS.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_TRANSFER.h b/app/src/lg/svc/LG_TRANSFER.h new file mode 100644 index 0000000..a96468e --- /dev/null +++ b/app/src/lg/svc/LG_TRANSFER.h @@ -0,0 +1,21 @@ +/* + * LG_TRANSFER.h - lg 서비스 LG_TRANSFER 카피북 (copybook / record header). + * 계정간 대체: 출금계정 대변, 입금계정 차변(균형). + */ +#ifndef LG_TRANSFER_H +#define LG_TRANSFER_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_transfer_rec_t; + +void LG_TRANSFER(TPSVCINFO *p); + +#endif /* LG_TRANSFER_H */ diff --git a/app/src/lg/svc/LG_TRANSFER.pgc b/app/src/lg/svc/LG_TRANSFER.pgc new file mode 100644 index 0000000..304031d --- /dev/null +++ b/app/src/lg/svc/LG_TRANSFER.pgc @@ -0,0 +1,28 @@ +/* + * LG_TRANSFER.pgc - lg 모듈 서비스 LG_TRANSFER (one service = one file). + * 계정간 대체: 출금계정 대변, 입금계정 차변(균형). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_TRANSFER.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_TRIALBAL.h b/app/src/lg/svc/LG_TRIALBAL.h new file mode 100644 index 0000000..4064588 --- /dev/null +++ b/app/src/lg/svc/LG_TRIALBAL.h @@ -0,0 +1,21 @@ +/* + * LG_TRIALBAL.h - lg 서비스 LG_TRIALBAL 카피북 (copybook / record header). + * 계정별 시산표 산출 후 lg_trial_bal upsert. + */ +#ifndef LG_TRIALBAL_H +#define LG_TRIALBAL_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_trialbal_rec_t; + +void LG_TRIALBAL(TPSVCINFO *p); + +#endif /* LG_TRIALBAL_H */ diff --git a/app/src/lg/svc/LG_TRIALBAL.pgc b/app/src/lg/svc/LG_TRIALBAL.pgc new file mode 100644 index 0000000..149e564 --- /dev/null +++ b/app/src/lg/svc/LG_TRIALBAL.pgc @@ -0,0 +1,37 @@ +/* + * LG_TRIALBAL.pgc - lg 모듈 서비스 LG_TRIALBAL (one service = one file). + * 계정별 시산표 산출 후 lg_trial_bal upsert. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_TRIALBAL.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_UNSETTLED.h b/app/src/lg/svc/LG_UNSETTLED.h new file mode 100644 index 0000000..519ca06 --- /dev/null +++ b/app/src/lg/svc/LG_UNSETTLED.h @@ -0,0 +1,21 @@ +/* + * LG_UNSETTLED.h - lg 서비스 LG_UNSETTLED 카피북 (copybook / record header). + * 미마감(status=posted) 원장 라인 건수. + */ +#ifndef LG_UNSETTLED_H +#define LG_UNSETTLED_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_unsettled_rec_t; + +void LG_UNSETTLED(TPSVCINFO *p); + +#endif /* LG_UNSETTLED_H */ diff --git a/app/src/lg/svc/LG_UNSETTLED.pgc b/app/src/lg/svc/LG_UNSETTLED.pgc new file mode 100644 index 0000000..78b93ef --- /dev/null +++ b/app/src/lg/svc/LG_UNSETTLED.pgc @@ -0,0 +1,26 @@ +/* + * LG_UNSETTLED.pgc - lg 모듈 서비스 LG_UNSETTLED (one service = one file). + * 미마감(status=posted) 원장 라인 건수. + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_UNSETTLED.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_VOUCHER.h b/app/src/lg/svc/LG_VOUCHER.h new file mode 100644 index 0000000..941406d --- /dev/null +++ b/app/src/lg/svc/LG_VOUCHER.h @@ -0,0 +1,21 @@ +/* + * LG_VOUCHER.h - lg 서비스 LG_VOUCHER 카피북 (copybook / record header). + * 빈 전표 헤더 생성(작성 상태). + */ +#ifndef LG_VOUCHER_H +#define LG_VOUCHER_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_voucher_rec_t; + +void LG_VOUCHER(TPSVCINFO *p); + +#endif /* LG_VOUCHER_H */ diff --git a/app/src/lg/svc/LG_VOUCHER.pgc b/app/src/lg/svc/LG_VOUCHER.pgc new file mode 100644 index 0000000..54cf789 --- /dev/null +++ b/app/src/lg/svc/LG_VOUCHER.pgc @@ -0,0 +1,25 @@ +/* + * LG_VOUCHER.pgc - lg 모듈 서비스 LG_VOUCHER (one service = one file). + * 빈 전표 헤더 생성(작성 상태). + * 이전 인라인 lg_svr.pgc 에서 분리; 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). + * lg_svr 가 tpsvrinit 에서 tpadvertise 로 광고한다. + */ +#include "LG_VOUCHER.h" + +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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/lg_svc.h b/app/src/lg/svc/lg_svc.h new file mode 100644 index 0000000..fa86739 --- /dev/null +++ b/app/src/lg/svc/lg_svc.h @@ -0,0 +1,35 @@ +/* + * lg_svc.h - lg (원장/복식부기) 서비스 공통 카피북 (shared copybook for all lg svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the lg dbio layer (liblgdbio.a) and the + * common math (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros every service uses. No business logic and no service function lives here. + * + * 복식부기 원칙: 모든 전표는 차변(D) 합 = 대변(C) 합. 계정잔액 balance = 차변-대변. + */ +#ifndef LG_SVC_H +#define LG_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "lg_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* LG_SVC_H */ diff --git a/app/src/mg/batch/mg_amount_batch.pgc b/app/src/mg/batch/mg_amount_batch.pgc new file mode 100644 index 0000000..7a6af40 --- /dev/null +++ b/app/src/mg/batch/mg_amount_batch.pgc @@ -0,0 +1,37 @@ +/* + * mg_amount_batch.pgc - mg 모듈 건수+금액 단건 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 건수+금액 단건 집계; then tpcommit drives XA 2PC. + * Usage: mg_amount_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0, h_sum = 0; + 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 SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum + FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_amount_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_amount_batch COMMIT: bizdate=%s rows=%ld sum=%ld\n", bizdate, h_cnt, h_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_chanacc_batch.pgc b/app/src/mg/batch/mg_chanacc_batch.pgc new file mode 100644 index 0000000..ad355da --- /dev/null +++ b/app/src/mg/batch/mg_chanacc_batch.pgc @@ -0,0 +1,46 @@ +/* + * mg_chanacc_batch.pgc - mg 모듈 커서 누적 합계 (행별 DML 없음) 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서 누적 합계 (행별 DML 없음); then tpcommit drives XA 2PC. + * Usage: mg_chanacc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, total = 0, rows = 0; + 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 bc CURSOR FOR + SELECT channel, coalesce(sum(amount),0) FROM mg_msg_log + WHERE biz_date = :h_bizdate GROUP BY channel; + EXEC SQL OPEN bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + total += h_amt; rows++; + } + EXEC SQL CLOSE bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_chanacc_batch COMMIT: bizdate=%s groups=%ld total=%ld\n", bizdate, rows, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_chansnap_batch.pgc b/app/src/mg/batch/mg_chansnap_batch.pgc new file mode 100644 index 0000000..558f36f --- /dev/null +++ b/app/src/mg/batch/mg_chansnap_batch.pgc @@ -0,0 +1,49 @@ +/* + * mg_chansnap_batch.pgc - mg 모듈 커서 순회 후 대상 테이블 행별 INSERT 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서 순회 후 대상 테이블 행별 INSERT; then tpcommit drives XA 2PC. + * Usage: mg_chansnap_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, rows = 0; + 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 bc CURSOR FOR + SELECT channel, coalesce(sum(amount),0) FROM mg_msg_log + WHERE biz_date = :h_bizdate GROUP BY channel; + EXEC SQL OPEN bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + EXEC SQL INSERT INTO mg_chan_snap (channel, biz_date, amt) + VALUES (:h_key, :h_bizdate, :h_amt); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_chansnap_batch COMMIT: bizdate=%s inserted=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_confirm_batch.pgc b/app/src/mg/batch/mg_confirm_batch.pgc new file mode 100644 index 0000000..9412d5b --- /dev/null +++ b/app/src/mg/batch/mg_confirm_batch.pgc @@ -0,0 +1,43 @@ +/* + * mg_confirm_batch.pgc - mg 모듈 건수 확인 후 조건부 업데이트 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 건수 확인 후 조건부 업데이트; then tpcommit drives XA 2PC. + * Usage: mg_confirm_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0, h_upd = 0; + 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 SELECT count(*) INTO :h_cnt FROM mg_queue + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (h_cnt > 0) { + EXEC SQL UPDATE mg_queue SET status = 'C', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_confirm_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_upd = sqlca.sqlerrd[2]; + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_confirm_batch COMMIT: bizdate=%s pending=%ld updated=%ld\n", bizdate, h_cnt, h_upd); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_dirstat_batch.pgc b/app/src/mg/batch/mg_dirstat_batch.pgc new file mode 100644 index 0000000..b1733d9 --- /dev/null +++ b/app/src/mg/batch/mg_dirstat_batch.pgc @@ -0,0 +1,40 @@ +/* + * mg_dirstat_batch.pgc - mg 모듈 3분류 CASE 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 3분류 CASE 집계; then tpcommit drives XA 2PC. + * Usage: mg_dirstat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, h_c = 0; + 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 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 direction = 'IN' THEN 1 ELSE 0 END),0) + INTO :h_a, :h_b, :h_c FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_dirstat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_dirstat_batch COMMIT: bizdate=%s a=%ld b=%ld c=%ld\n", bizdate, h_a, h_b, h_c); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_distinct_batch.pgc b/app/src/mg/batch/mg_distinct_batch.pgc new file mode 100644 index 0000000..8ed07e6 --- /dev/null +++ b/app/src/mg/batch/mg_distinct_batch.pgc @@ -0,0 +1,36 @@ +/* + * mg_distinct_batch.pgc - mg 모듈 고유값(distinct) 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 고유값(distinct) 집계; then tpcommit drives XA 2PC. + * Usage: mg_distinct_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_d = 0; + 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 SELECT count(DISTINCT channel) INTO :h_d FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_distinct_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_distinct_batch COMMIT: bizdate=%s distinct=%ld\n", bizdate, h_d); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_errbps_batch.pgc b/app/src/mg/batch/mg_errbps_batch.pgc new file mode 100644 index 0000000..a22062b --- /dev/null +++ b/app/src/mg/batch/mg_errbps_batch.pgc @@ -0,0 +1,40 @@ +/* + * mg_errbps_batch.pgc - mg 모듈 두 SELECT 비율(bps) 산출 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 두 SELECT 비율(bps) 산출; then tpcommit drives XA 2PC. + * Usage: mg_errbps_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, h_bps = 0; + 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 SELECT count(*) INTO :h_a FROM mg_msg_log + WHERE biz_date = :h_bizdate AND status = 'N'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_b FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + h_bps = (h_b > 0) ? h_a * 10000 / h_b : 0; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_errbps_batch COMMIT: bizdate=%s hit=%ld total=%ld bps=%ld\n", bizdate, h_a, h_b, h_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_join_batch.pgc b/app/src/mg/batch/mg_join_batch.pgc new file mode 100644 index 0000000..a1bae46 --- /dev/null +++ b/app/src/mg/batch/mg_join_batch.pgc @@ -0,0 +1,38 @@ +/* + * mg_join_batch.pgc - mg 모듈 조인 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 조인 집계; then tpcommit drives XA 2PC. + * Usage: mg_join_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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 SELECT coalesce(sum(a.amount),0) INTO :h_v + FROM mg_msg_log a JOIN mg_route b ON a.channel = b.channel + WHERE a.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_join_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_join_batch COMMIT: bizdate=%s joined_sum=%ld\n", bizdate, h_v); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_marker_batch.pgc b/app/src/mg/batch/mg_marker_batch.pgc new file mode 100644 index 0000000..2a479db --- /dev/null +++ b/app/src/mg/batch/mg_marker_batch.pgc @@ -0,0 +1,40 @@ +/* + * mg_marker_batch.pgc - mg 모듈 시퀀스 채번 + 마커 행 INSERT 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 시퀀스 채번 + 마커 행 INSERT; then tpcommit drives XA 2PC. + * Usage: mg_marker_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_id = 0, h_cnt = 0; + 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 SELECT count(*) INTO :h_cnt FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT nextval('mg_msg_seq') INTO :h_id; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL INSERT INTO mg_eod_mark (mark_id, biz_date, cnt) VALUES (:h_id, :h_bizdate, :h_cnt); + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_marker_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_marker_batch COMMIT: bizdate=%s mark_id=%ld cnt=%ld\n", bizdate, h_id, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_promote_batch.pgc b/app/src/mg/batch/mg_promote_batch.pgc new file mode 100644 index 0000000..76730ce --- /dev/null +++ b/app/src/mg/batch/mg_promote_batch.pgc @@ -0,0 +1,38 @@ +/* + * mg_promote_batch.pgc - mg 모듈 상태 일괄 전이 업데이트 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 상태 일괄 전이 업데이트; then tpcommit drives XA 2PC. + * Usage: mg_promote_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_upd = 0; + 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 UPDATE mg_queue SET status = 'Q', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'F'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_promote_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_upd = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_promote_batch COMMIT: bizdate=%s updated=%ld\n", bizdate, h_upd); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_prunequeue_batch.pgc b/app/src/mg/batch/mg_prunequeue_batch.pgc new file mode 100644 index 0000000..7fc7b1f --- /dev/null +++ b/app/src/mg/batch/mg_prunequeue_batch.pgc @@ -0,0 +1,38 @@ +/* + * mg_prunequeue_batch.pgc - mg 모듈 서브셀렉트 IN 조건 삭제 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 서브셀렉트 IN 조건 삭제; then tpcommit drives XA 2PC. + * Usage: mg_prunequeue_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_del = 0; + 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 DELETE FROM mg_queue + WHERE stan IN (SELECT stan FROM mg_msg_log WHERE biz_date = :h_bizdate AND status = 'N'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_prunequeue_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_del = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_prunequeue_batch COMMIT: bizdate=%s deleted=%ld\n", bizdate, h_del); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_purge_batch.pgc b/app/src/mg/batch/mg_purge_batch.pgc new file mode 100644 index 0000000..c0a13c9 --- /dev/null +++ b/app/src/mg/batch/mg_purge_batch.pgc @@ -0,0 +1,37 @@ +/* + * mg_purge_batch.pgc - mg 모듈 상태 조건 삭제 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 상태 조건 삭제; then tpcommit drives XA 2PC. + * Usage: mg_purge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_del = 0; + 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 DELETE FROM mg_queue WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_purge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_del = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_purge_batch COMMIT: bizdate=%s deleted=%ld\n", bizdate, h_del); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_rollup_batch.pgc b/app/src/mg/batch/mg_rollup_batch.pgc new file mode 100644 index 0000000..7b591ea --- /dev/null +++ b/app/src/mg/batch/mg_rollup_batch.pgc @@ -0,0 +1,39 @@ +/* + * mg_rollup_batch.pgc - mg 모듈 INSERT ... SELECT 그룹 롤업 적재 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; INSERT ... SELECT 그룹 롤업 적재; then tpcommit drives XA 2PC. + * Usage: mg_rollup_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rows = 0; + 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 INSERT INTO mg_chan_rollup (channel, biz_date, cnt, amt) + SELECT channel, biz_date, count(*), coalesce(sum(amount),0) + FROM mg_msg_log WHERE biz_date = :h_bizdate GROUP BY channel, biz_date; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_rollup_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_rollup_batch COMMIT: bizdate=%s rolled=%ld\n", bizdate, h_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_scalar_batch.pgc b/app/src/mg/batch/mg_scalar_batch.pgc new file mode 100644 index 0000000..4f591a0 --- /dev/null +++ b/app/src/mg/batch/mg_scalar_batch.pgc @@ -0,0 +1,36 @@ +/* + * mg_scalar_batch.pgc - mg 모듈 스칼라 합계 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 스칼라 합계 집계; then tpcommit drives XA 2PC. + * Usage: mg_scalar_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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 SELECT coalesce(sum(amount),0) INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_scalar_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_scalar_batch COMMIT: bizdate=%s total=%ld\n", bizdate, h_v); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_spread_batch.pgc b/app/src/mg/batch/mg_spread_batch.pgc new file mode 100644 index 0000000..ff027ea --- /dev/null +++ b/app/src/mg/batch/mg_spread_batch.pgc @@ -0,0 +1,38 @@ +/* + * mg_spread_batch.pgc - mg 모듈 최대-최소 스프레드 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 최대-최소 스프레드 집계; then tpcommit drives XA 2PC. + * Usage: mg_spread_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0, h_spread = 0; + 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 SELECT coalesce(max(amount),0), coalesce(min(amount),0) + INTO :h_max, :h_min FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_spread_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_spread = h_max - h_min; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_spread_batch COMMIT: bizdate=%s max=%ld min=%ld spread=%ld\n", bizdate, h_max, h_min, h_spread); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/dbio/mg_amount_spread_dbio.h b/app/src/mg/dbio/mg_amount_spread_dbio.h new file mode 100644 index 0000000..14c9358 --- /dev/null +++ b/app/src/mg/dbio/mg_amount_spread_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_amount_spread_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_amount_spread_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_AMOUNT_SPREAD_DBIO_H +#define MG_AMOUNT_SPREAD_DBIO_H + +/* 전문 금액 최대-최소 스프레드. */ +long mgdb_msg_amount_spread(const char *bizdate); + +#endif /* MG_AMOUNT_SPREAD_DBIO_H */ diff --git a/app/src/mg/dbio/mg_amount_spread_dbio.pgc b/app/src/mg/dbio/mg_amount_spread_dbio.pgc new file mode 100644 index 0000000..a9324fc --- /dev/null +++ b/app/src/mg/dbio/mg_amount_spread_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_amount_spread_dbio.pgc - mg 모듈 DB 접근 함수 (mg_amount_spread_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 전문 금액 최대-최소 스프레드. + */ +#include +#include +#include "mg_amount_spread_dbio.h" + +long mgdb_msg_amount_spread(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(amount),0), coalesce(min(amount),0) + INTO :h_max, :h_min FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_max - h_min; +} diff --git a/app/src/mg/dbio/mg_avg_amount_dbio.h b/app/src/mg/dbio/mg_avg_amount_dbio.h new file mode 100644 index 0000000..b3c3d3f --- /dev/null +++ b/app/src/mg/dbio/mg_avg_amount_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_avg_amount_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_avg_amount_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_AVG_AMOUNT_DBIO_H +#define MG_AVG_AMOUNT_DBIO_H + +/* 전문 건당 평균 금액. */ +long mgdb_msg_avg_amount(const char *bizdate); + +#endif /* MG_AVG_AMOUNT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_avg_amount_dbio.pgc b/app/src/mg/dbio/mg_avg_amount_dbio.pgc new file mode 100644 index 0000000..b8503e1 --- /dev/null +++ b/app/src/mg/dbio/mg_avg_amount_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mg_avg_amount_dbio.pgc - mg 모듈 DB 접근 함수 (mg_avg_amount_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 전문 건당 평균 금액. + */ +#include +#include +#include "mg_avg_amount_dbio.h" + +long mgdb_msg_avg_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_sum, :h_cnt + FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_cnt <= 0) return 0; + return h_sum / h_cnt; +} diff --git a/app/src/mg/dbio/mg_big_pct_dbio.h b/app/src/mg/dbio/mg_big_pct_dbio.h new file mode 100644 index 0000000..80dcec4 --- /dev/null +++ b/app/src/mg/dbio/mg_big_pct_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_big_pct_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_big_pct_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_BIG_PCT_DBIO_H +#define MG_BIG_PCT_DBIO_H + +/* 고액 전문 비율(%). */ +long mgdb_msg_big_pct(const char *bizdate); + +#endif /* MG_BIG_PCT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_big_pct_dbio.pgc b/app/src/mg/dbio/mg_big_pct_dbio.pgc new file mode 100644 index 0000000..b606bc4 --- /dev/null +++ b/app/src/mg/dbio/mg_big_pct_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mg_big_pct_dbio.pgc - mg 모듈 DB 접근 함수 (mg_big_pct_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 고액 전문 비율(%). + */ +#include +#include +#include "mg_big_pct_dbio.h" + +long mgdb_msg_big_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_hi = 0, h_all = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE WHEN amount > 1000000 THEN 1 ELSE 0 END),0), count(*) + INTO :h_hi, :h_all FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_all <= 0) return 0; + return h_hi * 100 / h_all; +} diff --git a/app/src/mg/dbio/mg_bump_stan_dbio.h b/app/src/mg/dbio/mg_bump_stan_dbio.h new file mode 100644 index 0000000..8c715a3 --- /dev/null +++ b/app/src/mg/dbio/mg_bump_stan_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_bump_stan_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_bump_stan_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_BUMP_STAN_DBIO_H +#define MG_BUMP_STAN_DBIO_H + +/* 활동 채널 last_stan 증가. */ +long mgdb_channel_bump_stan(const char *bizdate); + +#endif /* MG_BUMP_STAN_DBIO_H */ diff --git a/app/src/mg/dbio/mg_bump_stan_dbio.pgc b/app/src/mg/dbio/mg_bump_stan_dbio.pgc new file mode 100644 index 0000000..3e61d9f --- /dev/null +++ b/app/src/mg/dbio/mg_bump_stan_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_bump_stan_dbio.pgc - mg 모듈 DB 접근 함수 (mg_bump_stan_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 활동 채널 last_stan 증가. + */ +#include +#include +#include "mg_bump_stan_dbio.h" + +long mgdb_channel_bump_stan(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE mg_channel SET last_stan = last_stan + 1 + WHERE channel IN (SELECT channel FROM mg_msg_log WHERE biz_date = :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("mgdb_channel_bump_stan FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/mg/dbio/mg_chan_sum_dbio.h b/app/src/mg/dbio/mg_chan_sum_dbio.h new file mode 100644 index 0000000..1e06eb3 --- /dev/null +++ b/app/src/mg/dbio/mg_chan_sum_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_chan_sum_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_chan_sum_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_CHAN_SUM_DBIO_H +#define MG_CHAN_SUM_DBIO_H + +/* 채널 그룹 금액 커서 누적. */ +long mgdb_msg_chan_sum(const char *bizdate); + +#endif /* MG_CHAN_SUM_DBIO_H */ diff --git a/app/src/mg/dbio/mg_chan_sum_dbio.pgc b/app/src/mg/dbio/mg_chan_sum_dbio.pgc new file mode 100644 index 0000000..849d494 --- /dev/null +++ b/app/src/mg/dbio/mg_chan_sum_dbio.pgc @@ -0,0 +1,30 @@ +/* + * mg_chan_sum_dbio.pgc - mg 모듈 DB 접근 함수 (mg_chan_sum_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 채널 그룹 금액 커서 누적. + */ +#include +#include +#include "mg_chan_sum_dbio.h" + +long mgdb_msg_chan_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[32]; + long h_amt = 0, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT channel, coalesce(sum(amount),0) FROM mg_msg_log + WHERE biz_date = :h_bizdate GROUP BY channel; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) { userlog("mgdb_msg_chan_sum OPEN [%d]", sqlca.sqlcode); return -1; } + for (;;) { + EXEC SQL FETCH qc INTO :h_key, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + total += h_amt; + } + EXEC SQL CLOSE qc; + return total; +} diff --git a/app/src/mg/dbio/mg_channel_up_dbio.h b/app/src/mg/dbio/mg_channel_up_dbio.h new file mode 100644 index 0000000..2b02def --- /dev/null +++ b/app/src/mg/dbio/mg_channel_up_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_channel_up_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_channel_up_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_CHANNEL_UP_DBIO_H +#define MG_CHANNEL_UP_DBIO_H + +/* 채널 UP 상태 여부. */ +int mgdb_channel_is_up(const char *key); + +#endif /* MG_CHANNEL_UP_DBIO_H */ diff --git a/app/src/mg/dbio/mg_channel_up_dbio.pgc b/app/src/mg/dbio/mg_channel_up_dbio.pgc new file mode 100644 index 0000000..a1c182c --- /dev/null +++ b/app/src/mg/dbio/mg_channel_up_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_channel_up_dbio.pgc - mg 모듈 DB 접근 함수 (mg_channel_up_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 채널 UP 상태 여부. + */ +#include +#include +#include "mg_channel_up_dbio.h" + +int mgdb_channel_is_up(const char *key) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_key[32], h_st[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_key, key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0; + EXEC SQL SELECT status INTO :h_st FROM mg_channel WHERE channel = :h_key; + if (sqlca.sqlcode == 100) return -1; + if (sqlca.sqlcode < 0) return -1; + if (strcmp(h_st, "UP") == 0) return 1; + return 0; +} diff --git a/app/src/mg/dbio/mg_dbio.h b/app/src/mg/dbio/mg_dbio.h index 8be9a1d..543d50a 100644 --- a/app/src/mg/dbio/mg_dbio.h +++ b/app/src/mg/dbio/mg_dbio.h @@ -38,4 +38,30 @@ 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); + +/* ----- expanded query/analytics dbio (each in its own mg_*_dbio.pgc) ----- */ +long mgdb_msg_amount_sum(const char *bizdate); /* 당일 전문 금액 합계. */ +long mgdb_queue_backlog(const char *status); /* 상태별 큐 적체 건수. */ +long mgdb_msg_amount_spread(const char *bizdate); /* 전문 금액 최대-최소 스프레드. */ +long mgdb_msg_err_ratio(const char *bizdate); /* 오류 전문 비율(bps). */ +long mgdb_msg_chan_sum(const char *bizdate); /* 채널 그룹 금액 커서 누적. */ +long mgdb_msg_avg_amount(const char *bizdate); /* 전문 건당 평균 금액. */ +long mgdb_msg_inbound_amount(const char *bizdate); /* 수신(IN) 전문 금액 합계. */ +long mgdb_queue_purge_sent(const char *bizdate); /* 송신완료(S) 큐 정리 삭제. */ +long mgdb_queue_mark_stale(const char *bizdate); /* 대기(Q) 오래된 큐 실패(F) 전이. */ +int mgdb_route_channel_exists(const char *key); /* 채널 라우팅 존재 여부. */ +int mgdb_queue_payload(long id, char *out); /* 큐 ID 페이로드 조회. */ +long mgdb_queue_weighted(const char *bizdate); /* STAN x 재시도수 가중 합. */ +long mgdb_msg_peak_count(const char *bizdate); /* 최고 금액 전문 건수. */ +long mgdb_msg_pan_like(const char *pat); /* PAN 접두 패턴 전문 건수. */ +long mgdb_msg_distinct_chan(const char *bizdate); /* 당일 고유 채널 수. */ +long mgdb_msg_routed_amount(const char *bizdate); /* 라우팅 매칭 전문 금액(조인). */ +long mgdb_msg_min_amount(const char *bizdate); /* 당일 최소 전문 금액. */ +long mgdb_msg_big_pct(const char *bizdate); /* 고액 전문 비율(%). */ +int mgdb_channel_is_up(const char *key); /* 채널 UP 상태 여부. */ +long mgdb_channel_bump_stan(const char *bizdate); /* 활동 채널 last_stan 증가. */ +long mgdb_msg_insert_marker(const char *bizdate, long amount); /* 마커 전문 채번 후 적재. */ +long mgdb_queue_load(const char *bizdate); /* 큐 STAN+재시도 합산 부하지표. */ +long mgdb_msg_positive_count(const char *bizdate); /* 금액>0 전문 건수(커서). */ + #endif /* MG_DBIO_H */ diff --git a/app/src/mg/dbio/mg_distinct_chan_dbio.h b/app/src/mg/dbio/mg_distinct_chan_dbio.h new file mode 100644 index 0000000..ded9915 --- /dev/null +++ b/app/src/mg/dbio/mg_distinct_chan_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_distinct_chan_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_distinct_chan_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_DISTINCT_CHAN_DBIO_H +#define MG_DISTINCT_CHAN_DBIO_H + +/* 당일 고유 채널 수. */ +long mgdb_msg_distinct_chan(const char *bizdate); + +#endif /* MG_DISTINCT_CHAN_DBIO_H */ diff --git a/app/src/mg/dbio/mg_distinct_chan_dbio.pgc b/app/src/mg/dbio/mg_distinct_chan_dbio.pgc new file mode 100644 index 0000000..4a47c50 --- /dev/null +++ b/app/src/mg/dbio/mg_distinct_chan_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_distinct_chan_dbio.pgc - mg 모듈 DB 접근 함수 (mg_distinct_chan_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 고유 채널 수. + */ +#include +#include +#include "mg_distinct_chan_dbio.h" + +long mgdb_msg_distinct_chan(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT channel) INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_err_ratio_dbio.h b/app/src/mg/dbio/mg_err_ratio_dbio.h new file mode 100644 index 0000000..1575737 --- /dev/null +++ b/app/src/mg/dbio/mg_err_ratio_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_err_ratio_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_err_ratio_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ERR_RATIO_DBIO_H +#define MG_ERR_RATIO_DBIO_H + +/* 오류 전문 비율(bps). */ +long mgdb_msg_err_ratio(const char *bizdate); + +#endif /* MG_ERR_RATIO_DBIO_H */ diff --git a/app/src/mg/dbio/mg_err_ratio_dbio.pgc b/app/src/mg/dbio/mg_err_ratio_dbio.pgc new file mode 100644 index 0000000..ff736a6 --- /dev/null +++ b/app/src/mg/dbio/mg_err_ratio_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_err_ratio_dbio.pgc - mg 모듈 DB 접근 함수 (mg_err_ratio_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 오류 전문 비율(bps). + */ +#include +#include +#include "mg_err_ratio_dbio.h" + +long mgdb_msg_err_ratio(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0; + 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_a FROM mg_msg_log WHERE biz_date = :h_bizdate AND status = 'N'; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_b FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_b <= 0) return 0; + return h_a * 10000 / h_b; +} diff --git a/app/src/mg/dbio/mg_inbound_amt_dbio.h b/app/src/mg/dbio/mg_inbound_amt_dbio.h new file mode 100644 index 0000000..a7431ab --- /dev/null +++ b/app/src/mg/dbio/mg_inbound_amt_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_inbound_amt_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_inbound_amt_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_INBOUND_AMT_DBIO_H +#define MG_INBOUND_AMT_DBIO_H + +/* 수신(IN) 전문 금액 합계. */ +long mgdb_msg_inbound_amount(const char *bizdate); + +#endif /* MG_INBOUND_AMT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_inbound_amt_dbio.pgc b/app/src/mg/dbio/mg_inbound_amt_dbio.pgc new file mode 100644 index 0000000..d8e3b78 --- /dev/null +++ b/app/src/mg/dbio/mg_inbound_amt_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_inbound_amt_dbio.pgc - mg 모듈 DB 접근 함수 (mg_inbound_amt_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 수신(IN) 전문 금액 합계. + */ +#include +#include +#include "mg_inbound_amt_dbio.h" + +long mgdb_msg_inbound_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + 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 amount ELSE 0 END),0) + INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_insert_mark_dbio.h b/app/src/mg/dbio/mg_insert_mark_dbio.h new file mode 100644 index 0000000..d6c2f6b --- /dev/null +++ b/app/src/mg/dbio/mg_insert_mark_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_insert_mark_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_insert_mark_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_INSERT_MARK_DBIO_H +#define MG_INSERT_MARK_DBIO_H + +/* 마커 전문 채번 후 적재. */ +long mgdb_msg_insert_marker(const char *bizdate, long amount); + +#endif /* MG_INSERT_MARK_DBIO_H */ diff --git a/app/src/mg/dbio/mg_insert_mark_dbio.pgc b/app/src/mg/dbio/mg_insert_mark_dbio.pgc new file mode 100644 index 0000000..76d5576 --- /dev/null +++ b/app/src/mg/dbio/mg_insert_mark_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_insert_mark_dbio.pgc - mg 모듈 DB 접근 함수 (mg_insert_mark_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 마커 전문 채번 후 적재. + */ +#include +#include +#include "mg_insert_mark_dbio.h" + +long mgdb_msg_insert_marker(const char *bizdate, long amount) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id = 0, h_amt = amount; + 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 nextval('mg_msg_seq') INTO :h_id; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL INSERT INTO mg_msg_log (msg_id, amount, biz_date) + VALUES (:h_id, :h_amt, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("mgdb_msg_insert_marker FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_id; +} diff --git a/app/src/mg/dbio/mg_min_amount_dbio.h b/app/src/mg/dbio/mg_min_amount_dbio.h new file mode 100644 index 0000000..3b3c049 --- /dev/null +++ b/app/src/mg/dbio/mg_min_amount_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_min_amount_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_min_amount_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_MIN_AMOUNT_DBIO_H +#define MG_MIN_AMOUNT_DBIO_H + +/* 당일 최소 전문 금액. */ +long mgdb_msg_min_amount(const char *bizdate); + +#endif /* MG_MIN_AMOUNT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_min_amount_dbio.pgc b/app/src/mg/dbio/mg_min_amount_dbio.pgc new file mode 100644 index 0000000..6fab9a1 --- /dev/null +++ b/app/src/mg/dbio/mg_min_amount_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_min_amount_dbio.pgc - mg 모듈 DB 접근 함수 (mg_min_amount_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 최소 전문 금액. + */ +#include +#include +#include "mg_min_amount_dbio.h" + +long mgdb_msg_min_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT min(amount) INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("mgdb_msg_min_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/mg/dbio/mg_msg_amount_dbio.h b/app/src/mg/dbio/mg_msg_amount_dbio.h new file mode 100644 index 0000000..a00348c --- /dev/null +++ b/app/src/mg/dbio/mg_msg_amount_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_msg_amount_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_msg_amount_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_MSG_AMOUNT_DBIO_H +#define MG_MSG_AMOUNT_DBIO_H + +/* 당일 전문 금액 합계. */ +long mgdb_msg_amount_sum(const char *bizdate); + +#endif /* MG_MSG_AMOUNT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_msg_amount_dbio.pgc b/app/src/mg/dbio/mg_msg_amount_dbio.pgc new file mode 100644 index 0000000..6080f49 --- /dev/null +++ b/app/src/mg/dbio/mg_msg_amount_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_msg_amount_dbio.pgc - mg 모듈 DB 접근 함수 (mg_msg_amount_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 전문 금액 합계. + */ +#include +#include +#include "mg_msg_amount_dbio.h" + +long mgdb_msg_amount_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("mgdb_msg_amount_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/mg/dbio/mg_pan_like_dbio.h b/app/src/mg/dbio/mg_pan_like_dbio.h new file mode 100644 index 0000000..6a4201f --- /dev/null +++ b/app/src/mg/dbio/mg_pan_like_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_pan_like_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_pan_like_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_PAN_LIKE_DBIO_H +#define MG_PAN_LIKE_DBIO_H + +/* PAN 접두 패턴 전문 건수. */ +long mgdb_msg_pan_like(const char *pat); + +#endif /* MG_PAN_LIKE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_pan_like_dbio.pgc b/app/src/mg/dbio/mg_pan_like_dbio.pgc new file mode 100644 index 0000000..5dbad27 --- /dev/null +++ b/app/src/mg/dbio/mg_pan_like_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_pan_like_dbio.pgc - mg 모듈 DB 접근 함수 (mg_pan_like_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * PAN 접두 패턴 전문 건수. + */ +#include +#include +#include "mg_pan_like_dbio.h" + +long mgdb_msg_pan_like(const char *pat) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_pat[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_pat, pat, sizeof(h_pat)-1); h_pat[sizeof(h_pat)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_msg_log WHERE pan LIKE :h_pat; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_peak_count_dbio.h b/app/src/mg/dbio/mg_peak_count_dbio.h new file mode 100644 index 0000000..53f87a1 --- /dev/null +++ b/app/src/mg/dbio/mg_peak_count_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_peak_count_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_peak_count_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_PEAK_COUNT_DBIO_H +#define MG_PEAK_COUNT_DBIO_H + +/* 최고 금액 전문 건수. */ +long mgdb_msg_peak_count(const char *bizdate); + +#endif /* MG_PEAK_COUNT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_peak_count_dbio.pgc b/app/src/mg/dbio/mg_peak_count_dbio.pgc new file mode 100644 index 0000000..d500579 --- /dev/null +++ b/app/src/mg/dbio/mg_peak_count_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_peak_count_dbio.pgc - mg 모듈 DB 접근 함수 (mg_peak_count_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 최고 금액 전문 건수. + */ +#include +#include +#include "mg_peak_count_dbio.h" + +long mgdb_msg_peak_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_peak = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(amount),0) INTO :h_peak FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mg_msg_log + WHERE biz_date = :h_bizdate AND amount = :h_peak; + if (sqlca.sqlcode < 0) return -1; + return h_cnt; +} diff --git a/app/src/mg/dbio/mg_pos_count_dbio.h b/app/src/mg/dbio/mg_pos_count_dbio.h new file mode 100644 index 0000000..a661158 --- /dev/null +++ b/app/src/mg/dbio/mg_pos_count_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_pos_count_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_pos_count_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_POS_COUNT_DBIO_H +#define MG_POS_COUNT_DBIO_H + +/* 금액>0 전문 건수(커서). */ +long mgdb_msg_positive_count(const char *bizdate); + +#endif /* MG_POS_COUNT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_pos_count_dbio.pgc b/app/src/mg/dbio/mg_pos_count_dbio.pgc new file mode 100644 index 0000000..e998263 --- /dev/null +++ b/app/src/mg/dbio/mg_pos_count_dbio.pgc @@ -0,0 +1,29 @@ +/* + * mg_pos_count_dbio.pgc - mg 모듈 DB 접근 함수 (mg_pos_count_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 금액>0 전문 건수(커서). + */ +#include +#include +#include "mg_pos_count_dbio.h" + +long mgdb_msg_positive_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_x = 0, k = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT amount FROM mg_msg_log WHERE biz_date = :h_bizdate; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH qc INTO :h_x; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + if (h_x > 0) k++; + } + EXEC SQL CLOSE qc; + return k; +} diff --git a/app/src/mg/dbio/mg_queue_backlog_dbio.h b/app/src/mg/dbio/mg_queue_backlog_dbio.h new file mode 100644 index 0000000..dd1cda2 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_backlog_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_backlog_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_backlog_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_BACKLOG_DBIO_H +#define MG_QUEUE_BACKLOG_DBIO_H + +/* 상태별 큐 적체 건수. */ +long mgdb_queue_backlog(const char *status); + +#endif /* MG_QUEUE_BACKLOG_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_backlog_dbio.pgc b/app/src/mg/dbio/mg_queue_backlog_dbio.pgc new file mode 100644 index 0000000..4f99058 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_backlog_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_queue_backlog_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_backlog_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 상태별 큐 적체 건수. + */ +#include +#include +#include "mg_queue_backlog_dbio.h" + +long mgdb_queue_backlog(const char *status) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_st[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_st, status, sizeof(h_st)-1); h_st[sizeof(h_st)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_queue WHERE status = :h_st; + if (sqlca.sqlcode < 0) { userlog("mgdb_queue_backlog FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/mg/dbio/mg_queue_load_dbio.h b/app/src/mg/dbio/mg_queue_load_dbio.h new file mode 100644 index 0000000..1766d6b --- /dev/null +++ b/app/src/mg/dbio/mg_queue_load_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_load_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_load_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_LOAD_DBIO_H +#define MG_QUEUE_LOAD_DBIO_H + +/* 큐 STAN+재시도 합산 부하지표. */ +long mgdb_queue_load(const char *bizdate); + +#endif /* MG_QUEUE_LOAD_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_load_dbio.pgc b/app/src/mg/dbio/mg_queue_load_dbio.pgc new file mode 100644 index 0000000..d9a755b --- /dev/null +++ b/app/src/mg/dbio/mg_queue_load_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_queue_load_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_load_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 큐 STAN+재시도 합산 부하지표. + */ +#include +#include +#include "mg_queue_load_dbio.h" + +long mgdb_queue_load(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(stan),0) + coalesce(sum(retry_cnt),0) INTO :h_v + FROM mg_queue WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_queue_payload_dbio.h b/app/src/mg/dbio/mg_queue_payload_dbio.h new file mode 100644 index 0000000..a572351 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_payload_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_payload_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_payload_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_PAYLOAD_DBIO_H +#define MG_QUEUE_PAYLOAD_DBIO_H + +/* 큐 ID 페이로드 조회. */ +int mgdb_queue_payload(long id, char *out); + +#endif /* MG_QUEUE_PAYLOAD_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_payload_dbio.pgc b/app/src/mg/dbio/mg_queue_payload_dbio.pgc new file mode 100644 index 0000000..741a661 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_payload_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_queue_payload_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_payload_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 큐 ID 페이로드 조회. + */ +#include +#include +#include "mg_queue_payload_dbio.h" + +int mgdb_queue_payload(long id, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id = id; + char h_s[64]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT payload INTO :h_s FROM mg_queue WHERE queue_id = :h_id; + if (sqlca.sqlcode == 100) { out[0] = 0; return -1; } + if (sqlca.sqlcode < 0) { userlog("mgdb_queue_payload FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strncpy(out, h_s, 63); out[63] = 0; + return 0; +} diff --git a/app/src/mg/dbio/mg_queue_purge_dbio.h b/app/src/mg/dbio/mg_queue_purge_dbio.h new file mode 100644 index 0000000..04a64c9 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_purge_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_purge_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_purge_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_PURGE_DBIO_H +#define MG_QUEUE_PURGE_DBIO_H + +/* 송신완료(S) 큐 정리 삭제. */ +long mgdb_queue_purge_sent(const char *bizdate); + +#endif /* MG_QUEUE_PURGE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_purge_dbio.pgc b/app/src/mg/dbio/mg_queue_purge_dbio.pgc new file mode 100644 index 0000000..c4afa56 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_purge_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mg_queue_purge_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_purge_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 송신완료(S) 큐 정리 삭제. + */ +#include +#include +#include "mg_queue_purge_dbio.h" + +long mgdb_queue_purge_sent(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 DELETE FROM mg_queue WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { userlog("mgdb_queue_purge_sent FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/mg/dbio/mg_queue_stale_dbio.h b/app/src/mg/dbio/mg_queue_stale_dbio.h new file mode 100644 index 0000000..28f9de3 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_stale_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_stale_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_stale_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_STALE_DBIO_H +#define MG_QUEUE_STALE_DBIO_H + +/* 대기(Q) 오래된 큐 실패(F) 전이. */ +long mgdb_queue_mark_stale(const char *bizdate); + +#endif /* MG_QUEUE_STALE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_stale_dbio.pgc b/app/src/mg/dbio/mg_queue_stale_dbio.pgc new file mode 100644 index 0000000..24e3ddc --- /dev/null +++ b/app/src/mg/dbio/mg_queue_stale_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_queue_stale_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_stale_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 대기(Q) 오래된 큐 실패(F) 전이. + */ +#include +#include +#include "mg_queue_stale_dbio.h" + +long mgdb_queue_mark_stale(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE mg_queue SET status = 'F', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'Q'; + if (sqlca.sqlcode < 0) { userlog("mgdb_queue_mark_stale FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/mg/dbio/mg_queue_weight_dbio.h b/app/src/mg/dbio/mg_queue_weight_dbio.h new file mode 100644 index 0000000..aec6da1 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_weight_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_queue_weight_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_queue_weight_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QUEUE_WEIGHT_DBIO_H +#define MG_QUEUE_WEIGHT_DBIO_H + +/* STAN x 재시도수 가중 합. */ +long mgdb_queue_weighted(const char *bizdate); + +#endif /* MG_QUEUE_WEIGHT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_queue_weight_dbio.pgc b/app/src/mg/dbio/mg_queue_weight_dbio.pgc new file mode 100644 index 0000000..171bfa1 --- /dev/null +++ b/app/src/mg/dbio/mg_queue_weight_dbio.pgc @@ -0,0 +1,29 @@ +/* + * mg_queue_weight_dbio.pgc - mg 모듈 DB 접근 함수 (mg_queue_weight_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * STAN x 재시도수 가중 합. + */ +#include +#include +#include "mg_queue_weight_dbio.h" + +long mgdb_queue_weighted(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_a = 0, h_b = 0, acc = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE qc CURSOR FOR + SELECT stan, retry_cnt FROM mg_queue WHERE biz_date = :h_bizdate; + EXEC SQL OPEN qc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH qc INTO :h_a, :h_b; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE qc; return -1; } + acc += h_a * h_b; + } + EXEC SQL CLOSE qc; + return acc; +} diff --git a/app/src/mg/dbio/mg_route_exists_dbio.h b/app/src/mg/dbio/mg_route_exists_dbio.h new file mode 100644 index 0000000..d01e92f --- /dev/null +++ b/app/src/mg/dbio/mg_route_exists_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_route_exists_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_route_exists_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ROUTE_EXISTS_DBIO_H +#define MG_ROUTE_EXISTS_DBIO_H + +/* 채널 라우팅 존재 여부. */ +int mgdb_route_channel_exists(const char *key); + +#endif /* MG_ROUTE_EXISTS_DBIO_H */ diff --git a/app/src/mg/dbio/mg_route_exists_dbio.pgc b/app/src/mg/dbio/mg_route_exists_dbio.pgc new file mode 100644 index 0000000..a358902 --- /dev/null +++ b/app/src/mg/dbio/mg_route_exists_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_route_exists_dbio.pgc - mg 모듈 DB 접근 함수 (mg_route_exists_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 채널 라우팅 존재 여부. + */ +#include +#include +#include "mg_route_exists_dbio.h" + +int mgdb_route_channel_exists(const char *key) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_key[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_key, key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_route WHERE channel = :h_key; + if (sqlca.sqlcode < 0) return -1; + return h_v > 0 ? 1 : 0; +} diff --git a/app/src/mg/dbio/mg_routed_amt_dbio.h b/app/src/mg/dbio/mg_routed_amt_dbio.h new file mode 100644 index 0000000..8644724 --- /dev/null +++ b/app/src/mg/dbio/mg_routed_amt_dbio.h @@ -0,0 +1,12 @@ +/* + * mg_routed_amt_dbio.h - mg 모듈 DB 접근 계층 copybook (mg_routed_amt_dbio, part of libmgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ROUTED_AMT_DBIO_H +#define MG_ROUTED_AMT_DBIO_H + +/* 라우팅 매칭 전문 금액(조인). */ +long mgdb_msg_routed_amount(const char *bizdate); + +#endif /* MG_ROUTED_AMT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_routed_amt_dbio.pgc b/app/src/mg/dbio/mg_routed_amt_dbio.pgc new file mode 100644 index 0000000..95053f0 --- /dev/null +++ b/app/src/mg/dbio/mg_routed_amt_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mg_routed_amt_dbio.pgc - mg 모듈 DB 접근 함수 (mg_routed_amt_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 라우팅 매칭 전문 금액(조인). + */ +#include +#include +#include "mg_routed_amt_dbio.h" + +long mgdb_msg_routed_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(a.amount),0) INTO :h_v + FROM mg_msg_log a JOIN mg_route b ON a.channel = b.channel + WHERE a.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/mg_svr.pgc b/app/src/mg/mg_svr.pgc index 624b959..63ba839 100644 --- a/app/src/mg/mg_svr.pgc +++ b/app/src/mg/mg_svr.pgc @@ -1,70 +1,72 @@ /* - * mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER. + * mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of mg's 30 online services now lives in its own file + * (app/src/mg/svc/.pgc) with its own copybook header + * (app/src/mg/svc/.h); those objects are archived into libmgsvc.a + * and linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all 30 services from a {name, fn} table, + * - tpclose() at shutdown. * * 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 -#include -#include #include #include #include -#include -#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); } +/* every mg service's copybook header (declares its prototype) */ +#include "MG_RECV.h" +#include "MG_SEND.h" +#include "MG_ISO8583.h" +#include "MG_BITMAP.h" +#include "MG_STAN.h" +#include "MG_ROUTE.h" +#include "MG_RESEND.h" +#include "MG_CONVERT.h" +#include "MG_ERRMSG.h" +#include "MG_LOG.h" +#include "MG_CHANAUTH.h" +#include "MG_SESSION.h" +#include "MG_ACK.h" +#include "MG_NACK.h" +#include "MG_ECHO.h" +#include "MG_HEALTH.h" +#include "MG_SIGNON.h" +#include "MG_SIGNOFF.h" +#include "MG_KEYEXCH.h" +#include "MG_MAC.h" +#include "MG_PARSE.h" +#include "MG_PACK.h" +#include "MG_UNPACK.h" +#include "MG_QUEUE.h" +#include "MG_DEQUEUE.h" +#include "MG_RETRY.h" +#include "MG_STATS.h" +#include "MG_CHANSTAT.h" +#include "MG_STATUS.h" +#include "MG_CONFIRM.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, + {"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} }; @@ -91,559 +93,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/mg/run/run_chanacc.sh b/app/src/mg/run/run_chanacc.sh new file mode 100755 index 0000000..7e04863 --- /dev/null +++ b/app/src/mg/run/run_chanacc.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_chanacc.sh - 채널 누적 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_chanacc.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_chanacc.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_chanacc_batch "$BIZDATE" diff --git a/app/src/mg/run/run_chansnap.sh b/app/src/mg/run/run_chansnap.sh new file mode 100755 index 0000000..d767bfc --- /dev/null +++ b/app/src/mg/run/run_chansnap.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_chansnap.sh - 채널 스냅샷 적재 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_chansnap.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_chansnap.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_chansnap_batch "$BIZDATE" diff --git a/app/src/mg/run/run_confirm.sh b/app/src/mg/run/run_confirm.sh new file mode 100755 index 0000000..589de98 --- /dev/null +++ b/app/src/mg/run/run_confirm.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_confirm.sh - 송신 큐 확정 처리 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_confirm.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_confirm.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_confirm_batch "$BIZDATE" diff --git a/app/src/mg/run/run_dirstat.sh b/app/src/mg/run/run_dirstat.sh new file mode 100755 index 0000000..f8b0768 --- /dev/null +++ b/app/src/mg/run/run_dirstat.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_dirstat.sh - 방향별 통계 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_dirstat.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_dirstat.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_dirstat_batch "$BIZDATE" diff --git a/app/src/mg/run/run_distinct.sh b/app/src/mg/run/run_distinct.sh new file mode 100755 index 0000000..09c1789 --- /dev/null +++ b/app/src/mg/run/run_distinct.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_distinct.sh - 고유 채널 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_distinct.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_distinct.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_distinct_batch "$BIZDATE" diff --git a/app/src/mg/run/run_eod.sh b/app/src/mg/run/run_eod.sh new file mode 100755 index 0000000..d19e02d --- /dev/null +++ b/app/src/mg/run/run_eod.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_eod.sh - mg 일마감 배치 일괄 실행 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_eod.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_eod.sh] BIZDATE=$BIZDATE" +for b in mg_amount_batch mg_rollup_batch mg_chanacc_batch mg_spread_batch mg_stat_batch \ + mg_errbps_batch mg_dirstat_batch mg_marker_batch mg_purge_batch; do + echo "--- $b $BIZDATE ---"; "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/mg/run/run_errbps.sh b/app/src/mg/run/run_errbps.sh new file mode 100755 index 0000000..c4892ab --- /dev/null +++ b/app/src/mg/run/run_errbps.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_errbps.sh - 오류율(bps) 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_errbps.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_errbps.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_errbps_batch "$BIZDATE" diff --git a/app/src/mg/run/run_join.sh b/app/src/mg/run/run_join.sh new file mode 100755 index 0000000..3edad45 --- /dev/null +++ b/app/src/mg/run/run_join.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_join.sh - 라우팅 조인 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_join.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_join.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_join_batch "$BIZDATE" diff --git a/app/src/mg/run/run_marker.sh b/app/src/mg/run/run_marker.sh new file mode 100755 index 0000000..efdb8fa --- /dev/null +++ b/app/src/mg/run/run_marker.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_marker.sh - EOD 마커 채번 적재 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_marker.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_marker.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_marker_batch "$BIZDATE" diff --git a/app/src/mg/run/run_promote.sh b/app/src/mg/run/run_promote.sh new file mode 100755 index 0000000..5f943f8 --- /dev/null +++ b/app/src/mg/run/run_promote.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_promote.sh - 실패 큐 재대기 승격 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_promote.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_promote.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_promote_batch "$BIZDATE" diff --git a/app/src/mg/run/run_prune.sh b/app/src/mg/run/run_prune.sh new file mode 100755 index 0000000..3951ec5 --- /dev/null +++ b/app/src/mg/run/run_prune.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_prune.sh - 오류 전문 큐 삭제 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_prune.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_prune.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_prunequeue_batch "$BIZDATE" diff --git a/app/src/mg/run/run_purge.sh b/app/src/mg/run/run_purge.sh new file mode 100755 index 0000000..888768a --- /dev/null +++ b/app/src/mg/run/run_purge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_purge.sh - 송신완료 큐 정리 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_purge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_purge.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_purge_batch "$BIZDATE" diff --git a/app/src/mg/run/run_recv.sh b/app/src/mg/run/run_recv.sh new file mode 100755 index 0000000..de792ce --- /dev/null +++ b/app/src/mg/run/run_recv.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_recv.sh - 전문 수신 처리 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_recv.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_recv.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_amount_batch "$BIZDATE" diff --git a/app/src/mg/run/run_resend.sh b/app/src/mg/run/run_resend.sh new file mode 100755 index 0000000..5226fe4 --- /dev/null +++ b/app/src/mg/run/run_resend.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_resend.sh - 실패 전문 재전송 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_resend.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_resend.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_resend_batch "$BIZDATE" diff --git a/app/src/mg/run/run_rollup.sh b/app/src/mg/run/run_rollup.sh new file mode 100755 index 0000000..f233051 --- /dev/null +++ b/app/src/mg/run/run_rollup.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_rollup.sh - 채널 롤업 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_rollup.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_rollup.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_rollup_batch "$BIZDATE" diff --git a/app/src/mg/run/run_send.sh b/app/src/mg/run/run_send.sh new file mode 100755 index 0000000..0c6cedd --- /dev/null +++ b/app/src/mg/run/run_send.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_send.sh - 전문 송신 처리 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_send.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_send.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_scalar_batch "$BIZDATE" diff --git a/app/src/mg/run/run_spread.sh b/app/src/mg/run/run_spread.sh new file mode 100755 index 0000000..f05ecc7 --- /dev/null +++ b/app/src/mg/run/run_spread.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_spread.sh - 금액 스프레드 집계 +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_spread.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_spread.sh] BIZDATE=$BIZDATE" +exec /app/bin/mg_spread_batch "$BIZDATE" diff --git a/app/src/mg/svc/MG_ACK.h b/app/src/mg/svc/MG_ACK.h new file mode 100644 index 0000000..9a3b6df --- /dev/null +++ b/app/src/mg/svc/MG_ACK.h @@ -0,0 +1,12 @@ +/* + * MG_ACK.h - mg (전문게이트웨이) 서비스 MG_ACK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ACK_H +#define MG_ACK_H + +#include "mg_svc.h" + +void MG_ACK(TPSVCINFO *p); + +#endif /* MG_ACK_H */ diff --git a/app/src/mg/svc/MG_ACK.pgc b/app/src/mg/svc/MG_ACK.pgc new file mode 100644 index 0000000..c0a28e1 --- /dev/null +++ b/app/src/mg/svc/MG_ACK.pgc @@ -0,0 +1,16 @@ +/* + * MG_ACK.pgc - mg 모듈 서비스 MG_ACK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ACK.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_BITMAP.h b/app/src/mg/svc/MG_BITMAP.h new file mode 100644 index 0000000..e408af5 --- /dev/null +++ b/app/src/mg/svc/MG_BITMAP.h @@ -0,0 +1,12 @@ +/* + * MG_BITMAP.h - mg (전문게이트웨이) 서비스 MG_BITMAP 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_BITMAP_H +#define MG_BITMAP_H + +#include "mg_svc.h" + +void MG_BITMAP(TPSVCINFO *p); + +#endif /* MG_BITMAP_H */ diff --git a/app/src/mg/svc/MG_BITMAP.pgc b/app/src/mg/svc/MG_BITMAP.pgc new file mode 100644 index 0000000..6b9bc03 --- /dev/null +++ b/app/src/mg/svc/MG_BITMAP.pgc @@ -0,0 +1,37 @@ +/* + * MG_BITMAP.pgc - mg 모듈 서비스 MG_BITMAP (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_BITMAP.h" + +/* ----- service-local pure helper(s) ----- */ +/* 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); +} + + +/* 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); +} diff --git a/app/src/mg/svc/MG_CHANAUTH.h b/app/src/mg/svc/MG_CHANAUTH.h new file mode 100644 index 0000000..3337957 --- /dev/null +++ b/app/src/mg/svc/MG_CHANAUTH.h @@ -0,0 +1,12 @@ +/* + * MG_CHANAUTH.h - mg (전문게이트웨이) 서비스 MG_CHANAUTH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANAUTH_H +#define MG_CHANAUTH_H + +#include "mg_svc.h" + +void MG_CHANAUTH(TPSVCINFO *p); + +#endif /* MG_CHANAUTH_H */ diff --git a/app/src/mg/svc/MG_CHANAUTH.pgc b/app/src/mg/svc/MG_CHANAUTH.pgc new file mode 100644 index 0000000..494e00e --- /dev/null +++ b/app/src/mg/svc/MG_CHANAUTH.pgc @@ -0,0 +1,24 @@ +/* + * MG_CHANAUTH.pgc - mg 모듈 서비스 MG_CHANAUTH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANAUTH.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_CHANSTAT.h b/app/src/mg/svc/MG_CHANSTAT.h new file mode 100644 index 0000000..9c20cd3 --- /dev/null +++ b/app/src/mg/svc/MG_CHANSTAT.h @@ -0,0 +1,12 @@ +/* + * MG_CHANSTAT.h - mg (전문게이트웨이) 서비스 MG_CHANSTAT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANSTAT_H +#define MG_CHANSTAT_H + +#include "mg_svc.h" + +void MG_CHANSTAT(TPSVCINFO *p); + +#endif /* MG_CHANSTAT_H */ diff --git a/app/src/mg/svc/MG_CHANSTAT.pgc b/app/src/mg/svc/MG_CHANSTAT.pgc new file mode 100644 index 0000000..e246dc9 --- /dev/null +++ b/app/src/mg/svc/MG_CHANSTAT.pgc @@ -0,0 +1,17 @@ +/* + * MG_CHANSTAT.pgc - mg 모듈 서비스 MG_CHANSTAT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANSTAT.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_CONFIRM.h b/app/src/mg/svc/MG_CONFIRM.h new file mode 100644 index 0000000..1347778 --- /dev/null +++ b/app/src/mg/svc/MG_CONFIRM.h @@ -0,0 +1,12 @@ +/* + * MG_CONFIRM.h - mg (전문게이트웨이) 서비스 MG_CONFIRM 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CONFIRM_H +#define MG_CONFIRM_H + +#include "mg_svc.h" + +void MG_CONFIRM(TPSVCINFO *p); + +#endif /* MG_CONFIRM_H */ diff --git a/app/src/mg/svc/MG_CONFIRM.pgc b/app/src/mg/svc/MG_CONFIRM.pgc new file mode 100644 index 0000000..e1d482d --- /dev/null +++ b/app/src/mg/svc/MG_CONFIRM.pgc @@ -0,0 +1,16 @@ +/* + * MG_CONFIRM.pgc - mg 모듈 서비스 MG_CONFIRM (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CONFIRM.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_CONVERT.h b/app/src/mg/svc/MG_CONVERT.h new file mode 100644 index 0000000..3b8e3b5 --- /dev/null +++ b/app/src/mg/svc/MG_CONVERT.h @@ -0,0 +1,12 @@ +/* + * MG_CONVERT.h - mg (전문게이트웨이) 서비스 MG_CONVERT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CONVERT_H +#define MG_CONVERT_H + +#include "mg_svc.h" + +void MG_CONVERT(TPSVCINFO *p); + +#endif /* MG_CONVERT_H */ diff --git a/app/src/mg/svc/MG_CONVERT.pgc b/app/src/mg/svc/MG_CONVERT.pgc new file mode 100644 index 0000000..59ca6a4 --- /dev/null +++ b/app/src/mg/svc/MG_CONVERT.pgc @@ -0,0 +1,31 @@ +/* + * MG_CONVERT.pgc - mg 모듈 서비스 MG_CONVERT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CONVERT.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_DEQUEUE.h b/app/src/mg/svc/MG_DEQUEUE.h new file mode 100644 index 0000000..5dcff48 --- /dev/null +++ b/app/src/mg/svc/MG_DEQUEUE.h @@ -0,0 +1,12 @@ +/* + * MG_DEQUEUE.h - mg (전문게이트웨이) 서비스 MG_DEQUEUE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_DEQUEUE_H +#define MG_DEQUEUE_H + +#include "mg_svc.h" + +void MG_DEQUEUE(TPSVCINFO *p); + +#endif /* MG_DEQUEUE_H */ diff --git a/app/src/mg/svc/MG_DEQUEUE.pgc b/app/src/mg/svc/MG_DEQUEUE.pgc new file mode 100644 index 0000000..354f69d --- /dev/null +++ b/app/src/mg/svc/MG_DEQUEUE.pgc @@ -0,0 +1,31 @@ +/* + * MG_DEQUEUE.pgc - mg 모듈 서비스 MG_DEQUEUE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_DEQUEUE.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_ECHO.h b/app/src/mg/svc/MG_ECHO.h new file mode 100644 index 0000000..3d6a0d0 --- /dev/null +++ b/app/src/mg/svc/MG_ECHO.h @@ -0,0 +1,12 @@ +/* + * MG_ECHO.h - mg (전문게이트웨이) 서비스 MG_ECHO 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ECHO_H +#define MG_ECHO_H + +#include "mg_svc.h" + +void MG_ECHO(TPSVCINFO *p); + +#endif /* MG_ECHO_H */ diff --git a/app/src/mg/svc/MG_ECHO.pgc b/app/src/mg/svc/MG_ECHO.pgc new file mode 100644 index 0000000..35e6f38 --- /dev/null +++ b/app/src/mg/svc/MG_ECHO.pgc @@ -0,0 +1,25 @@ +/* + * MG_ECHO.pgc - mg 모듈 서비스 MG_ECHO (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ECHO.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_ERRMSG.h b/app/src/mg/svc/MG_ERRMSG.h new file mode 100644 index 0000000..5e61484 --- /dev/null +++ b/app/src/mg/svc/MG_ERRMSG.h @@ -0,0 +1,12 @@ +/* + * MG_ERRMSG.h - mg (전문게이트웨이) 서비스 MG_ERRMSG 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ERRMSG_H +#define MG_ERRMSG_H + +#include "mg_svc.h" + +void MG_ERRMSG(TPSVCINFO *p); + +#endif /* MG_ERRMSG_H */ diff --git a/app/src/mg/svc/MG_ERRMSG.pgc b/app/src/mg/svc/MG_ERRMSG.pgc new file mode 100644 index 0000000..2a42bf1 --- /dev/null +++ b/app/src/mg/svc/MG_ERRMSG.pgc @@ -0,0 +1,28 @@ +/* + * MG_ERRMSG.pgc - mg 모듈 서비스 MG_ERRMSG (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ERRMSG.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_HEALTH.h b/app/src/mg/svc/MG_HEALTH.h new file mode 100644 index 0000000..bc107d8 --- /dev/null +++ b/app/src/mg/svc/MG_HEALTH.h @@ -0,0 +1,12 @@ +/* + * MG_HEALTH.h - mg (전문게이트웨이) 서비스 MG_HEALTH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_HEALTH_H +#define MG_HEALTH_H + +#include "mg_svc.h" + +void MG_HEALTH(TPSVCINFO *p); + +#endif /* MG_HEALTH_H */ diff --git a/app/src/mg/svc/MG_HEALTH.pgc b/app/src/mg/svc/MG_HEALTH.pgc new file mode 100644 index 0000000..eee5323 --- /dev/null +++ b/app/src/mg/svc/MG_HEALTH.pgc @@ -0,0 +1,20 @@ +/* + * MG_HEALTH.pgc - mg 모듈 서비스 MG_HEALTH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_HEALTH.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_ISO8583.h b/app/src/mg/svc/MG_ISO8583.h new file mode 100644 index 0000000..676b5fe --- /dev/null +++ b/app/src/mg/svc/MG_ISO8583.h @@ -0,0 +1,12 @@ +/* + * MG_ISO8583.h - mg (전문게이트웨이) 서비스 MG_ISO8583 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ISO8583_H +#define MG_ISO8583_H + +#include "mg_svc.h" + +void MG_ISO8583(TPSVCINFO *p); + +#endif /* MG_ISO8583_H */ diff --git a/app/src/mg/svc/MG_ISO8583.pgc b/app/src/mg/svc/MG_ISO8583.pgc new file mode 100644 index 0000000..e2fe8f7 --- /dev/null +++ b/app/src/mg/svc/MG_ISO8583.pgc @@ -0,0 +1,36 @@ +/* + * MG_ISO8583.pgc - mg 모듈 서비스 MG_ISO8583 (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ISO8583.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_KEYEXCH.h b/app/src/mg/svc/MG_KEYEXCH.h new file mode 100644 index 0000000..68f1464 --- /dev/null +++ b/app/src/mg/svc/MG_KEYEXCH.h @@ -0,0 +1,12 @@ +/* + * MG_KEYEXCH.h - mg (전문게이트웨이) 서비스 MG_KEYEXCH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_KEYEXCH_H +#define MG_KEYEXCH_H + +#include "mg_svc.h" + +void MG_KEYEXCH(TPSVCINFO *p); + +#endif /* MG_KEYEXCH_H */ diff --git a/app/src/mg/svc/MG_KEYEXCH.pgc b/app/src/mg/svc/MG_KEYEXCH.pgc new file mode 100644 index 0000000..948ebf9 --- /dev/null +++ b/app/src/mg/svc/MG_KEYEXCH.pgc @@ -0,0 +1,22 @@ +/* + * MG_KEYEXCH.pgc - mg 모듈 서비스 MG_KEYEXCH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_KEYEXCH.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_LOG.h b/app/src/mg/svc/MG_LOG.h new file mode 100644 index 0000000..edf3763 --- /dev/null +++ b/app/src/mg/svc/MG_LOG.h @@ -0,0 +1,12 @@ +/* + * MG_LOG.h - mg (전문게이트웨이) 서비스 MG_LOG 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_LOG_H +#define MG_LOG_H + +#include "mg_svc.h" + +void MG_LOG(TPSVCINFO *p); + +#endif /* MG_LOG_H */ diff --git a/app/src/mg/svc/MG_LOG.pgc b/app/src/mg/svc/MG_LOG.pgc new file mode 100644 index 0000000..da0e906 --- /dev/null +++ b/app/src/mg/svc/MG_LOG.pgc @@ -0,0 +1,27 @@ +/* + * MG_LOG.pgc - mg 모듈 서비스 MG_LOG (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_LOG.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_MAC.h b/app/src/mg/svc/MG_MAC.h new file mode 100644 index 0000000..464190c --- /dev/null +++ b/app/src/mg/svc/MG_MAC.h @@ -0,0 +1,12 @@ +/* + * MG_MAC.h - mg (전문게이트웨이) 서비스 MG_MAC 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_MAC_H +#define MG_MAC_H + +#include "mg_svc.h" + +void MG_MAC(TPSVCINFO *p); + +#endif /* MG_MAC_H */ diff --git a/app/src/mg/svc/MG_MAC.pgc b/app/src/mg/svc/MG_MAC.pgc new file mode 100644 index 0000000..1409878 --- /dev/null +++ b/app/src/mg/svc/MG_MAC.pgc @@ -0,0 +1,39 @@ +/* + * MG_MAC.pgc - mg 모듈 서비스 MG_MAC (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_MAC.h" + +/* ----- service-local pure helper(s) ----- */ +/* 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); +} + + +/* 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); +} diff --git a/app/src/mg/svc/MG_NACK.h b/app/src/mg/svc/MG_NACK.h new file mode 100644 index 0000000..1926ae1 --- /dev/null +++ b/app/src/mg/svc/MG_NACK.h @@ -0,0 +1,12 @@ +/* + * MG_NACK.h - mg (전문게이트웨이) 서비스 MG_NACK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_NACK_H +#define MG_NACK_H + +#include "mg_svc.h" + +void MG_NACK(TPSVCINFO *p); + +#endif /* MG_NACK_H */ diff --git a/app/src/mg/svc/MG_NACK.pgc b/app/src/mg/svc/MG_NACK.pgc new file mode 100644 index 0000000..895b824 --- /dev/null +++ b/app/src/mg/svc/MG_NACK.pgc @@ -0,0 +1,19 @@ +/* + * MG_NACK.pgc - mg 모듈 서비스 MG_NACK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_NACK.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_PACK.h b/app/src/mg/svc/MG_PACK.h new file mode 100644 index 0000000..c42eb27 --- /dev/null +++ b/app/src/mg/svc/MG_PACK.h @@ -0,0 +1,12 @@ +/* + * MG_PACK.h - mg (전문게이트웨이) 서비스 MG_PACK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_PACK_H +#define MG_PACK_H + +#include "mg_svc.h" + +void MG_PACK(TPSVCINFO *p); + +#endif /* MG_PACK_H */ diff --git a/app/src/mg/svc/MG_PACK.pgc b/app/src/mg/svc/MG_PACK.pgc new file mode 100644 index 0000000..0bb44bc --- /dev/null +++ b/app/src/mg/svc/MG_PACK.pgc @@ -0,0 +1,21 @@ +/* + * MG_PACK.pgc - mg 모듈 서비스 MG_PACK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_PACK.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_PARSE.h b/app/src/mg/svc/MG_PARSE.h new file mode 100644 index 0000000..46dc50f --- /dev/null +++ b/app/src/mg/svc/MG_PARSE.h @@ -0,0 +1,12 @@ +/* + * MG_PARSE.h - mg (전문게이트웨이) 서비스 MG_PARSE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_PARSE_H +#define MG_PARSE_H + +#include "mg_svc.h" + +void MG_PARSE(TPSVCINFO *p); + +#endif /* MG_PARSE_H */ diff --git a/app/src/mg/svc/MG_PARSE.pgc b/app/src/mg/svc/MG_PARSE.pgc new file mode 100644 index 0000000..8ec0620 --- /dev/null +++ b/app/src/mg/svc/MG_PARSE.pgc @@ -0,0 +1,23 @@ +/* + * MG_PARSE.pgc - mg 모듈 서비스 MG_PARSE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_PARSE.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_QUEUE.h b/app/src/mg/svc/MG_QUEUE.h new file mode 100644 index 0000000..72efb83 --- /dev/null +++ b/app/src/mg/svc/MG_QUEUE.h @@ -0,0 +1,12 @@ +/* + * MG_QUEUE.h - mg (전문게이트웨이) 서비스 MG_QUEUE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_QUEUE_H +#define MG_QUEUE_H + +#include "mg_svc.h" + +void MG_QUEUE(TPSVCINFO *p); + +#endif /* MG_QUEUE_H */ diff --git a/app/src/mg/svc/MG_QUEUE.pgc b/app/src/mg/svc/MG_QUEUE.pgc new file mode 100644 index 0000000..76e5cfd --- /dev/null +++ b/app/src/mg/svc/MG_QUEUE.pgc @@ -0,0 +1,25 @@ +/* + * MG_QUEUE.pgc - mg 모듈 서비스 MG_QUEUE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_QUEUE.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_RECV.h b/app/src/mg/svc/MG_RECV.h new file mode 100644 index 0000000..e2900e1 --- /dev/null +++ b/app/src/mg/svc/MG_RECV.h @@ -0,0 +1,12 @@ +/* + * MG_RECV.h - mg (전문게이트웨이) 서비스 MG_RECV 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RECV_H +#define MG_RECV_H + +#include "mg_svc.h" + +void MG_RECV(TPSVCINFO *p); + +#endif /* MG_RECV_H */ diff --git a/app/src/mg/svc/MG_RECV.pgc b/app/src/mg/svc/MG_RECV.pgc new file mode 100644 index 0000000..3aa6f77 --- /dev/null +++ b/app/src/mg/svc/MG_RECV.pgc @@ -0,0 +1,31 @@ +/* + * MG_RECV.pgc - mg 모듈 서비스 MG_RECV (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RECV.h" + +/* ========================================================================= */ +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); +} diff --git a/app/src/mg/svc/MG_RESEND.h b/app/src/mg/svc/MG_RESEND.h new file mode 100644 index 0000000..d0b7e93 --- /dev/null +++ b/app/src/mg/svc/MG_RESEND.h @@ -0,0 +1,12 @@ +/* + * MG_RESEND.h - mg (전문게이트웨이) 서비스 MG_RESEND 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RESEND_H +#define MG_RESEND_H + +#include "mg_svc.h" + +void MG_RESEND(TPSVCINFO *p); + +#endif /* MG_RESEND_H */ diff --git a/app/src/mg/svc/MG_RESEND.pgc b/app/src/mg/svc/MG_RESEND.pgc new file mode 100644 index 0000000..cf20a2a --- /dev/null +++ b/app/src/mg/svc/MG_RESEND.pgc @@ -0,0 +1,29 @@ +/* + * MG_RESEND.pgc - mg 모듈 서비스 MG_RESEND (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RESEND.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_RETRY.h b/app/src/mg/svc/MG_RETRY.h new file mode 100644 index 0000000..1696067 --- /dev/null +++ b/app/src/mg/svc/MG_RETRY.h @@ -0,0 +1,12 @@ +/* + * MG_RETRY.h - mg (전문게이트웨이) 서비스 MG_RETRY 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RETRY_H +#define MG_RETRY_H + +#include "mg_svc.h" + +void MG_RETRY(TPSVCINFO *p); + +#endif /* MG_RETRY_H */ diff --git a/app/src/mg/svc/MG_RETRY.pgc b/app/src/mg/svc/MG_RETRY.pgc new file mode 100644 index 0000000..e473f7e --- /dev/null +++ b/app/src/mg/svc/MG_RETRY.pgc @@ -0,0 +1,23 @@ +/* + * MG_RETRY.pgc - mg 모듈 서비스 MG_RETRY (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RETRY.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_ROUTE.h b/app/src/mg/svc/MG_ROUTE.h new file mode 100644 index 0000000..b2ad294 --- /dev/null +++ b/app/src/mg/svc/MG_ROUTE.h @@ -0,0 +1,12 @@ +/* + * MG_ROUTE.h - mg (전문게이트웨이) 서비스 MG_ROUTE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ROUTE_H +#define MG_ROUTE_H + +#include "mg_svc.h" + +void MG_ROUTE(TPSVCINFO *p); + +#endif /* MG_ROUTE_H */ diff --git a/app/src/mg/svc/MG_ROUTE.pgc b/app/src/mg/svc/MG_ROUTE.pgc new file mode 100644 index 0000000..1589465 --- /dev/null +++ b/app/src/mg/svc/MG_ROUTE.pgc @@ -0,0 +1,23 @@ +/* + * MG_ROUTE.pgc - mg 모듈 서비스 MG_ROUTE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ROUTE.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_SEND.h b/app/src/mg/svc/MG_SEND.h new file mode 100644 index 0000000..660af76 --- /dev/null +++ b/app/src/mg/svc/MG_SEND.h @@ -0,0 +1,12 @@ +/* + * MG_SEND.h - mg (전문게이트웨이) 서비스 MG_SEND 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SEND_H +#define MG_SEND_H + +#include "mg_svc.h" + +void MG_SEND(TPSVCINFO *p); + +#endif /* MG_SEND_H */ diff --git a/app/src/mg/svc/MG_SEND.pgc b/app/src/mg/svc/MG_SEND.pgc new file mode 100644 index 0000000..4d4875f --- /dev/null +++ b/app/src/mg/svc/MG_SEND.pgc @@ -0,0 +1,28 @@ +/* + * MG_SEND.pgc - mg 모듈 서비스 MG_SEND (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SEND.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_SESSION.h b/app/src/mg/svc/MG_SESSION.h new file mode 100644 index 0000000..7245052 --- /dev/null +++ b/app/src/mg/svc/MG_SESSION.h @@ -0,0 +1,12 @@ +/* + * MG_SESSION.h - mg (전문게이트웨이) 서비스 MG_SESSION 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SESSION_H +#define MG_SESSION_H + +#include "mg_svc.h" + +void MG_SESSION(TPSVCINFO *p); + +#endif /* MG_SESSION_H */ diff --git a/app/src/mg/svc/MG_SESSION.pgc b/app/src/mg/svc/MG_SESSION.pgc new file mode 100644 index 0000000..eab9374 --- /dev/null +++ b/app/src/mg/svc/MG_SESSION.pgc @@ -0,0 +1,16 @@ +/* + * MG_SESSION.pgc - mg 모듈 서비스 MG_SESSION (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SESSION.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_SIGNOFF.h b/app/src/mg/svc/MG_SIGNOFF.h new file mode 100644 index 0000000..132c415 --- /dev/null +++ b/app/src/mg/svc/MG_SIGNOFF.h @@ -0,0 +1,12 @@ +/* + * MG_SIGNOFF.h - mg (전문게이트웨이) 서비스 MG_SIGNOFF 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SIGNOFF_H +#define MG_SIGNOFF_H + +#include "mg_svc.h" + +void MG_SIGNOFF(TPSVCINFO *p); + +#endif /* MG_SIGNOFF_H */ diff --git a/app/src/mg/svc/MG_SIGNOFF.pgc b/app/src/mg/svc/MG_SIGNOFF.pgc new file mode 100644 index 0000000..d4af2ea --- /dev/null +++ b/app/src/mg/svc/MG_SIGNOFF.pgc @@ -0,0 +1,17 @@ +/* + * MG_SIGNOFF.pgc - mg 모듈 서비스 MG_SIGNOFF (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SIGNOFF.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_SIGNON.h b/app/src/mg/svc/MG_SIGNON.h new file mode 100644 index 0000000..4693a05 --- /dev/null +++ b/app/src/mg/svc/MG_SIGNON.h @@ -0,0 +1,12 @@ +/* + * MG_SIGNON.h - mg (전문게이트웨이) 서비스 MG_SIGNON 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SIGNON_H +#define MG_SIGNON_H + +#include "mg_svc.h" + +void MG_SIGNON(TPSVCINFO *p); + +#endif /* MG_SIGNON_H */ diff --git a/app/src/mg/svc/MG_SIGNON.pgc b/app/src/mg/svc/MG_SIGNON.pgc new file mode 100644 index 0000000..172a9f7 --- /dev/null +++ b/app/src/mg/svc/MG_SIGNON.pgc @@ -0,0 +1,21 @@ +/* + * MG_SIGNON.pgc - mg 모듈 서비스 MG_SIGNON (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SIGNON.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_STAN.h b/app/src/mg/svc/MG_STAN.h new file mode 100644 index 0000000..0dc2c4e --- /dev/null +++ b/app/src/mg/svc/MG_STAN.h @@ -0,0 +1,12 @@ +/* + * MG_STAN.h - mg (전문게이트웨이) 서비스 MG_STAN 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STAN_H +#define MG_STAN_H + +#include "mg_svc.h" + +void MG_STAN(TPSVCINFO *p); + +#endif /* MG_STAN_H */ diff --git a/app/src/mg/svc/MG_STAN.pgc b/app/src/mg/svc/MG_STAN.pgc new file mode 100644 index 0000000..f20486e --- /dev/null +++ b/app/src/mg/svc/MG_STAN.pgc @@ -0,0 +1,16 @@ +/* + * MG_STAN.pgc - mg 모듈 서비스 MG_STAN (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STAN.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_STATS.h b/app/src/mg/svc/MG_STATS.h new file mode 100644 index 0000000..031d337 --- /dev/null +++ b/app/src/mg/svc/MG_STATS.h @@ -0,0 +1,12 @@ +/* + * MG_STATS.h - mg (전문게이트웨이) 서비스 MG_STATS 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATS_H +#define MG_STATS_H + +#include "mg_svc.h" + +void MG_STATS(TPSVCINFO *p); + +#endif /* MG_STATS_H */ diff --git a/app/src/mg/svc/MG_STATS.pgc b/app/src/mg/svc/MG_STATS.pgc new file mode 100644 index 0000000..18b1fab --- /dev/null +++ b/app/src/mg/svc/MG_STATS.pgc @@ -0,0 +1,35 @@ +/* + * MG_STATS.pgc - mg 모듈 서비스 MG_STATS (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATS.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_STATUS.h b/app/src/mg/svc/MG_STATUS.h new file mode 100644 index 0000000..100327a --- /dev/null +++ b/app/src/mg/svc/MG_STATUS.h @@ -0,0 +1,12 @@ +/* + * MG_STATUS.h - mg (전문게이트웨이) 서비스 MG_STATUS 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATUS_H +#define MG_STATUS_H + +#include "mg_svc.h" + +void MG_STATUS(TPSVCINFO *p); + +#endif /* MG_STATUS_H */ diff --git a/app/src/mg/svc/MG_STATUS.pgc b/app/src/mg/svc/MG_STATUS.pgc new file mode 100644 index 0000000..f56f316 --- /dev/null +++ b/app/src/mg/svc/MG_STATUS.pgc @@ -0,0 +1,17 @@ +/* + * MG_STATUS.pgc - mg 모듈 서비스 MG_STATUS (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATUS.h" + +/* 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); +} diff --git a/app/src/mg/svc/MG_UNPACK.h b/app/src/mg/svc/MG_UNPACK.h new file mode 100644 index 0000000..f1c1503 --- /dev/null +++ b/app/src/mg/svc/MG_UNPACK.h @@ -0,0 +1,12 @@ +/* + * MG_UNPACK.h - mg (전문게이트웨이) 서비스 MG_UNPACK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_UNPACK_H +#define MG_UNPACK_H + +#include "mg_svc.h" + +void MG_UNPACK(TPSVCINFO *p); + +#endif /* MG_UNPACK_H */ diff --git a/app/src/mg/svc/MG_UNPACK.pgc b/app/src/mg/svc/MG_UNPACK.pgc new file mode 100644 index 0000000..3d7134c --- /dev/null +++ b/app/src/mg/svc/MG_UNPACK.pgc @@ -0,0 +1,24 @@ +/* + * MG_UNPACK.pgc - mg 모듈 서비스 MG_UNPACK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_UNPACK.h" + +/* 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); +} diff --git a/app/src/mg/svc/mg_svc.h b/app/src/mg/svc/mg_svc.h new file mode 100644 index 0000000..00e1b15 --- /dev/null +++ b/app/src/mg/svc/mg_svc.h @@ -0,0 +1,34 @@ +/* + * mg_svc.h - mg (전문게이트웨이) 서비스 공통 카피북 (shared copybook for all mg svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the mg dbio layer (libmgdbio.a) + * and the common math (libacqcommon.a), and provides the tiny UBF helpers + + * tpreturn macros every service uses. No business logic lives here. + */ +#ifndef MG_SVC_H +#define MG_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "mg_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* MG_SVC_H */ diff --git a/app/src/mm/batch/mm_acct_check_batch.pgc b/app/src/mm/batch/mm_acct_check_batch.pgc new file mode 100644 index 0000000..1699b84 --- /dev/null +++ b/app/src/mm/batch/mm_acct_check_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_acct_check_batch.pgc - 정산계좌 미설정 가맹점 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_acct_check_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_missing; + 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; } + + EXEC SQL SELECT count(*) INTO :h_missing FROM mm_merchant_ext + WHERE (settle_acct IS NULL OR settle_acct = '') AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_acct_check_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_acct_check_batch COMMIT: 계좌미설정=%ld\n", h_missing); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_bin_load_batch.pgc b/app/src/mm/batch/mm_bin_load_batch.pgc new file mode 100644 index 0000000..5c1c4dd --- /dev/null +++ b/app/src/mm/batch/mm_bin_load_batch.pgc @@ -0,0 +1,43 @@ +/* + * mm_bin_load_batch.pgc - 발급사별 BIN 적재 현황 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_bin_load_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_issuer[32]; long h_cnt; + 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; } + + EXEC SQL DECLARE bic CURSOR FOR + SELECT issuer_cd, count(*) FROM mm_bin GROUP BY issuer_cd ORDER BY issuer_cd; + EXEC SQL OPEN bic; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bic INTO :h_issuer, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bic; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE bic; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_bin_load_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_bin_load_batch COMMIT: 발급사구간=%ld\n", rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_code_sync_batch.pgc b/app/src/mm/batch/mm_code_sync_batch.pgc new file mode 100644 index 0000000..1886621 --- /dev/null +++ b/app/src/mm/batch/mm_code_sync_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_code_sync_batch.pgc - 공통코드 활성 수 동기화 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_code_sync_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_grp, h_code; + 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; } + + EXEC SQL SELECT count(DISTINCT code_group), count(*) INTO :h_grp, :h_code + FROM mm_code WHERE status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_code_sync_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_code_sync_batch COMMIT: 코드그룹=%ld 활성코드=%ld\n", h_grp, h_code); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_dedup_batch.pgc b/app/src/mm/batch/mm_dedup_batch.pgc new file mode 100644 index 0000000..0ef8602 --- /dev/null +++ b/app/src/mm/batch/mm_dedup_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_dedup_batch.pgc - 동일 상호 가맹점 중복 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_dedup_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_dupgrp; + 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; } + + EXEC SQL SELECT count(*) INTO :h_dupgrp FROM + (SELECT name FROM mm_merchant_ext GROUP BY name HAVING count(*) > 1) t; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_dedup_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_dedup_batch COMMIT: 중복상호그룹=%ld\n", h_dupgrp); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_dormant_batch.pgc b/app/src/mm/batch/mm_dormant_batch.pgc new file mode 100644 index 0000000..7ee13a5 --- /dev/null +++ b/app/src/mm/batch/mm_dormant_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_dormant_batch.pgc - 휴면 가맹점 탐지 배치 (N일 미갱신) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_dormant_batch [DAYS] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_days = (argc > 1) ? atol(argv[1]) : 180; + 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; } + + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext + WHERE status = 'ACTIVE' AND updated_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_dormant_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_dormant_batch COMMIT: days=%ld 휴면=%ld\n", h_days, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_feerate_avg_batch.pgc b/app/src/mm/batch/mm_feerate_avg_batch.pgc new file mode 100644 index 0000000..23b120b --- /dev/null +++ b/app/src/mm/batch/mm_feerate_avg_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_feerate_avg_batch.pgc - 가맹점 수수료율 평균 통계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_feerate_avg_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_max, h_avg; + 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; } + + EXEC SQL SELECT coalesce(min(rate_bps),0), coalesce(max(rate_bps),0), + coalesce(round(avg(rate_bps)),0) INTO :h_min, :h_max, :h_avg FROM mm_fee_rate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_feerate_avg_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_feerate_avg_batch COMMIT: min=%ld max=%ld avg=%ld\n", h_min, h_max, h_avg); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_fx_snapshot_batch.pgc b/app/src/mm/batch/mm_fx_snapshot_batch.pgc new file mode 100644 index 0000000..d0009b9 --- /dev/null +++ b/app/src/mm/batch/mm_fx_snapshot_batch.pgc @@ -0,0 +1,43 @@ +/* + * mm_fx_snapshot_batch.pgc - 환율 마스터 스냅샷 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_fx_snapshot_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8]; long h_rate; + 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; } + + EXEC SQL DECLARE fxc CURSOR FOR + SELECT ccy, rate_bps FROM mm_fx_rate ORDER BY ccy; + EXEC SQL OPEN fxc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH fxc INTO :h_ccy, :h_rate; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE fxc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE fxc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_fx_snapshot_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_fx_snapshot_batch COMMIT: 통화=%ld\n", rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_grade_dist_batch.pgc b/app/src/mm/batch/mm_grade_dist_batch.pgc new file mode 100644 index 0000000..efb0b4f --- /dev/null +++ b/app/src/mm/batch/mm_grade_dist_batch.pgc @@ -0,0 +1,48 @@ +/* + * mm_grade_dist_batch.pgc - 가맹점 등급 분포 배치 (커서 + upsert) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_grade_dist_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_grade[8], h_date[16]; long h_cnt; + EXEC SQL END DECLARE SECTION; + strncpy(h_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(h_date)-1); h_date[sizeof(h_date)-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 gdc CURSOR FOR + SELECT grade, count(*) FROM mm_merchant_ext GROUP BY grade ORDER BY grade; + EXEC SQL OPEN gdc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH gdc INTO :h_grade, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE gdc; tpabort(0); return 1; } + EXEC SQL INSERT INTO mm_grade_dist (dist_date, grade, merch_cnt) + VALUES (:h_date, :h_grade, :h_cnt) + ON CONFLICT (dist_date, grade) DO UPDATE SET merch_cnt = EXCLUDED.merch_cnt; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE gdc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE gdc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_grade_dist_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_grade_dist_batch COMMIT: date=%s 등급=%ld\n", h_date, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_holiday_batch.pgc b/app/src/mm/batch/mm_holiday_batch.pgc new file mode 100644 index 0000000..2264250 --- /dev/null +++ b/app/src/mm/batch/mm_holiday_batch.pgc @@ -0,0 +1,35 @@ +/* + * mm_holiday_batch.pgc - 연도별 공휴일 건수 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_holiday_batch [YEAR] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_year[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_year, (argc > 1) ? argv[1] : "2026", sizeof(h_year)-1); h_year[sizeof(h_year)-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 SELECT count(*) INTO :h_cnt FROM mm_holiday + WHERE to_char(holiday_date, 'YYYY') = :h_year; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_holiday_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_holiday_batch COMMIT: year=%s 공휴일=%ld\n", h_year, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_issuer_stat_batch.pgc b/app/src/mm/batch/mm_issuer_stat_batch.pgc new file mode 100644 index 0000000..3d1f0c2 --- /dev/null +++ b/app/src/mm/batch/mm_issuer_stat_batch.pgc @@ -0,0 +1,44 @@ +/* + * mm_issuer_stat_batch.pgc - 국가별 발급사 통계 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_issuer_stat_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_country[8]; long h_cnt; + 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; } + + EXEC SQL DECLARE isc CURSOR FOR + SELECT country, count(*) FROM mm_issuer WHERE status = 'ACTIVE' + GROUP BY country ORDER BY country; + EXEC SQL OPEN isc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH isc INTO :h_country, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE isc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE isc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_issuer_stat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_issuer_stat_batch COMMIT: 국가구간=%ld\n", rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_limit_audit_batch.pgc b/app/src/mm/batch/mm_limit_audit_batch.pgc new file mode 100644 index 0000000..45ef967 --- /dev/null +++ b/app/src/mm/batch/mm_limit_audit_batch.pgc @@ -0,0 +1,33 @@ +/* + * mm_limit_audit_batch.pgc - 일한도 초과 위험 가맹점 감사 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_limit_audit_batch [THRESHOLD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_over, h_thr = (argc > 1) ? atol(argv[1]) : 500000000; + 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; } + + EXEC SQL SELECT count(*) INTO :h_over FROM mm_limit WHERE daily_limit > :h_thr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_limit_audit_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_limit_audit_batch COMMIT: threshold=%ld 초과=%ld\n", h_thr, h_over); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_mcc_risk_batch.pgc b/app/src/mm/batch/mm_mcc_risk_batch.pgc new file mode 100644 index 0000000..40de74e --- /dev/null +++ b/app/src/mm/batch/mm_mcc_risk_batch.pgc @@ -0,0 +1,33 @@ +/* + * mm_mcc_risk_batch.pgc - MCC 위험도 합계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_mcc_risk_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_risk; + 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; } + + EXEC SQL SELECT count(*), coalesce(sum(risk_level),0) INTO :h_cnt, :h_risk FROM mm_mcc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_mcc_risk_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_mcc_risk_batch COMMIT: MCC=%ld 위험합=%ld\n", h_cnt, h_risk); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_merch_count_batch.pgc b/app/src/mm/batch/mm_merch_count_batch.pgc new file mode 100644 index 0000000..5fae929 --- /dev/null +++ b/app/src/mm/batch/mm_merch_count_batch.pgc @@ -0,0 +1,34 @@ +/* + * mm_merch_count_batch.pgc - 전체 가맹점 마스터 건수 집계 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_merch_count_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_total, h_active; + 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; } + + EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'ACTIVE') + INTO :h_total, :h_active FROM mm_merchant_ext; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_merch_count_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_merch_count_batch COMMIT: total=%ld active=%ld\n", h_total, h_active); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_summary_batch.pgc b/app/src/mm/batch/mm_summary_batch.pgc new file mode 100644 index 0000000..6c4e4c6 --- /dev/null +++ b/app/src/mm/batch/mm_summary_batch.pgc @@ -0,0 +1,38 @@ +/* + * mm_summary_batch.pgc - 마스터 종합 요약 배치 (다중 집계) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_summary_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_merch, h_bin, h_code, h_issuer; + 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; } + + EXEC SQL SELECT + (SELECT count(*) FROM mm_merchant_ext), + (SELECT count(*) FROM mm_bin), + (SELECT count(*) FROM mm_code), + (SELECT count(*) FROM mm_issuer) + INTO :h_merch, :h_bin, :h_code, :h_issuer; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_summary_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_summary_batch COMMIT: merch=%ld bin=%ld code=%ld issuer=%ld\n", h_merch, h_bin, h_code, h_issuer); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_terminal_stat_batch.pgc b/app/src/mm/batch/mm_terminal_stat_batch.pgc new file mode 100644 index 0000000..893ab2b --- /dev/null +++ b/app/src/mm/batch/mm_terminal_stat_batch.pgc @@ -0,0 +1,43 @@ +/* + * mm_terminal_stat_batch.pgc - 단말 모델별 통계 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, the EXEC SQL work runs on that branch, then tpcommit drives XA 2PC. + * Usage: mm_terminal_stat_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_model[32]; long h_cnt; + 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; } + + EXEC SQL DECLARE tmc CURSOR FOR + SELECT model, count(*) FROM mm_terminal GROUP BY model ORDER BY model; + EXEC SQL OPEN tmc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH tmc INTO :h_model, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE tmc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE tmc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mm_terminal_stat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_terminal_stat_batch COMMIT: 모델구간=%ld\n", rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/dbio/mm_acct_dbio.h b/app/src/mm/dbio/mm_acct_dbio.h new file mode 100644 index 0000000..1bee90f --- /dev/null +++ b/app/src/mm/dbio/mm_acct_dbio.h @@ -0,0 +1,12 @@ +/* + * mm_acct_dbio.h - 정산계좌 조회 + 미설정 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_ACCT_DBIO_H +#define MM_ACCT_DBIO_H + +int mmdb_settle_acct(const char *merch, char *acct_out /*>=64*/); +long mmdb_missing_acct_count(void); + +#endif /* MM_ACCT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_acct_dbio.pgc b/app/src/mm/dbio/mm_acct_dbio.pgc new file mode 100644 index 0000000..158f411 --- /dev/null +++ b/app/src/mm/dbio/mm_acct_dbio.pgc @@ -0,0 +1,31 @@ +/* + * mm_acct_dbio.pgc - 정산계좌 조회 + 미설정 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_acct_dbio.h" + +int mmdb_settle_acct(const char *merch, char *acct_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_acct[64]; + EXEC SQL END DECLARE SECTION; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT coalesce(settle_acct, '') INTO :h_acct FROM mm_merchant_ext + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_settle_acct FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(acct_out, h_acct, 63); acct_out[63] = 0; + return 0; +} + +long mmdb_missing_acct_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext + WHERE (settle_acct IS NULL OR settle_acct = '') AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_missing_acct_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_bin_count_dbio.h b/app/src/mm/dbio/mm_bin_count_dbio.h new file mode 100644 index 0000000..a494cf7 --- /dev/null +++ b/app/src/mm/dbio/mm_bin_count_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_bin_count_dbio.h - 발급사별 BIN 건수 집계 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_BIN_COUNT_DBIO_H +#define MM_BIN_COUNT_DBIO_H + +long mmdb_bin_by_issuer(const char *issuer); + +#endif /* MM_BIN_COUNT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_bin_count_dbio.pgc b/app/src/mm/dbio/mm_bin_count_dbio.pgc new file mode 100644 index 0000000..4f29e91 --- /dev/null +++ b/app/src/mm/dbio/mm_bin_count_dbio.pgc @@ -0,0 +1,18 @@ +/* + * mm_bin_count_dbio.pgc - 발급사별 BIN 건수 집계 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_bin_count_dbio.h" + +long mmdb_bin_by_issuer(const char *issuer) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_issuer[32]; + EXEC SQL END DECLARE SECTION; + strncpy(h_issuer, issuer, sizeof(h_issuer)-1); h_issuer[sizeof(h_issuer)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_bin WHERE issuer_cd = :h_issuer AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_bin_by_issuer FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_bin_query_dbio.h b/app/src/mm/dbio/mm_bin_query_dbio.h new file mode 100644 index 0000000..21f35f7 --- /dev/null +++ b/app/src/mm/dbio/mm_bin_query_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_bin_query_dbio.h - BIN 마스터 조회 (발급사/브랜드) copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_BIN_QUERY_DBIO_H +#define MM_BIN_QUERY_DBIO_H + +int mmdb_bin_issuer(const char *bin, char *issuer_out /*>=32*/); + +#endif /* MM_BIN_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_bin_query_dbio.pgc b/app/src/mm/dbio/mm_bin_query_dbio.pgc new file mode 100644 index 0000000..8c4613e --- /dev/null +++ b/app/src/mm/dbio/mm_bin_query_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_bin_query_dbio.pgc - BIN 마스터 조회 (발급사/브랜드) (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_bin_query_dbio.h" + +int mmdb_bin_issuer(const char *bin, char *issuer_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bin[16], h_issuer[32]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bin, bin, sizeof(h_bin)-1); h_bin[sizeof(h_bin)-1] = 0; + EXEC SQL SELECT issuer_cd INTO :h_issuer FROM mm_bin WHERE bin = :h_bin; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_bin_issuer FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(issuer_out, h_issuer, 31); issuer_out[31] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_code_active_dbio.h b/app/src/mm/dbio/mm_code_active_dbio.h new file mode 100644 index 0000000..b863fb6 --- /dev/null +++ b/app/src/mm/dbio/mm_code_active_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_code_active_dbio.h - 코드그룹 활성 코드 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_CODE_ACTIVE_DBIO_H +#define MM_CODE_ACTIVE_DBIO_H + +long mmdb_code_active_count(const char *grp); + +#endif /* MM_CODE_ACTIVE_DBIO_H */ diff --git a/app/src/mm/dbio/mm_code_active_dbio.pgc b/app/src/mm/dbio/mm_code_active_dbio.pgc new file mode 100644 index 0000000..5fc7940 --- /dev/null +++ b/app/src/mm/dbio/mm_code_active_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_code_active_dbio.pgc - 코드그룹 활성 코드 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_code_active_dbio.h" + +long mmdb_code_active_count(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 mm_code + WHERE code_group = :h_grp AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_code_active_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_code_query_dbio.h b/app/src/mm/dbio/mm_code_query_dbio.h new file mode 100644 index 0000000..13e2136 --- /dev/null +++ b/app/src/mm/dbio/mm_code_query_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_code_query_dbio.h - 공통코드 명칭 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_CODE_QUERY_DBIO_H +#define MM_CODE_QUERY_DBIO_H + +int mmdb_code_name(const char *grp, const char *code, char *name_out /*>=64*/); + +#endif /* MM_CODE_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_code_query_dbio.pgc b/app/src/mm/dbio/mm_code_query_dbio.pgc new file mode 100644 index 0000000..6088a87 --- /dev/null +++ b/app/src/mm/dbio/mm_code_query_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mm_code_query_dbio.pgc - 공통코드 명칭 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_code_query_dbio.h" + +int mmdb_code_name(const char *grp, const char *code, char *name_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32], h_code[32], h_name[64]; + 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 mm_code + WHERE code_group = :h_grp AND code = :h_code; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_code_name FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(name_out, h_name, 63); name_out[63] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_dormant_dbio.h b/app/src/mm/dbio/mm_dormant_dbio.h new file mode 100644 index 0000000..093af4f --- /dev/null +++ b/app/src/mm/dbio/mm_dormant_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_dormant_dbio.h - 휴면 가맹점 수 (N일 미갱신) copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_DORMANT_DBIO_H +#define MM_DORMANT_DBIO_H + +long mmdb_dormant_count(long days); + +#endif /* MM_DORMANT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_dormant_dbio.pgc b/app/src/mm/dbio/mm_dormant_dbio.pgc new file mode 100644 index 0000000..673babf --- /dev/null +++ b/app/src/mm/dbio/mm_dormant_dbio.pgc @@ -0,0 +1,18 @@ +/* + * mm_dormant_dbio.pgc - 휴면 가맹점 수 (N일 미갱신) (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_dormant_dbio.h" + +long mmdb_dormant_count(long days) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_days = days; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext + WHERE status = 'ACTIVE' AND updated_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) { userlog("mmdb_dormant_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_feerate_stat_dbio.h b/app/src/mm/dbio/mm_feerate_stat_dbio.h new file mode 100644 index 0000000..eaed215 --- /dev/null +++ b/app/src/mm/dbio/mm_feerate_stat_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_feerate_stat_dbio.h - 가맹점 평균 수수료율 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FEERATE_STAT_DBIO_H +#define MM_FEERATE_STAT_DBIO_H + +long mmdb_feerate_avg(const char *merch); + +#endif /* MM_FEERATE_STAT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_feerate_stat_dbio.pgc b/app/src/mm/dbio/mm_feerate_stat_dbio.pgc new file mode 100644 index 0000000..3681050 --- /dev/null +++ b/app/src/mm/dbio/mm_feerate_stat_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_feerate_stat_dbio.pgc - 가맹점 평균 수수료율 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_feerate_stat_dbio.h" + +long mmdb_feerate_avg(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_avg; 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 coalesce(round(avg(rate_bps)),0) INTO :h_avg FROM mm_fee_rate + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) { userlog("mmdb_feerate_avg FAIL [%d]", sqlca.sqlcode); return -1; } + return h_avg; +} diff --git a/app/src/mm/dbio/mm_fx_count_dbio.h b/app/src/mm/dbio/mm_fx_count_dbio.h new file mode 100644 index 0000000..eca0a8a --- /dev/null +++ b/app/src/mm/dbio/mm_fx_count_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_fx_count_dbio.h - 등록 통화 종류 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FX_COUNT_DBIO_H +#define MM_FX_COUNT_DBIO_H + +long mmdb_fx_ccy_count(void); + +#endif /* MM_FX_COUNT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_fx_count_dbio.pgc b/app/src/mm/dbio/mm_fx_count_dbio.pgc new file mode 100644 index 0000000..d1a5dd5 --- /dev/null +++ b/app/src/mm/dbio/mm_fx_count_dbio.pgc @@ -0,0 +1,17 @@ +/* + * mm_fx_count_dbio.pgc - 등록 통화 종류 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_fx_count_dbio.h" + +long mmdb_fx_ccy_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(DISTINCT ccy) INTO :h_cnt FROM mm_fx_rate; + if (sqlca.sqlcode < 0) { userlog("mmdb_fx_ccy_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_fx_query_dbio.h b/app/src/mm/dbio/mm_fx_query_dbio.h new file mode 100644 index 0000000..38a5645 --- /dev/null +++ b/app/src/mm/dbio/mm_fx_query_dbio.h @@ -0,0 +1,12 @@ +/* + * mm_fx_query_dbio.h - 환율 조회 + 원화 환산 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FX_QUERY_DBIO_H +#define MM_FX_QUERY_DBIO_H + +long mmdb_fx_rate(const char *ccy); +long mmdb_fx_to_krw(const char *ccy, long amt); + +#endif /* MM_FX_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_fx_query_dbio.pgc b/app/src/mm/dbio/mm_fx_query_dbio.pgc new file mode 100644 index 0000000..7e9a059 --- /dev/null +++ b/app/src/mm/dbio/mm_fx_query_dbio.pgc @@ -0,0 +1,25 @@ +/* + * mm_fx_query_dbio.pgc - 환율 조회 + 원화 환산 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_fx_query_dbio.h" + +long mmdb_fx_rate(const char *ccy) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_rate; char h_ccy[8]; + EXEC SQL END DECLARE SECTION; + 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) { userlog("mmdb_fx_rate FAIL [%d]", sqlca.sqlcode); return -1; } + return h_rate; +} + +long mmdb_fx_to_krw(const char *ccy, long amt) +{ + long rate = mmdb_fx_rate(ccy); + if (rate < 0) return -1; + return amt * rate / 100; +} diff --git a/app/src/mm/dbio/mm_grade_count_dbio.h b/app/src/mm/dbio/mm_grade_count_dbio.h new file mode 100644 index 0000000..2af5479 --- /dev/null +++ b/app/src/mm/dbio/mm_grade_count_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_grade_count_dbio.h - 등급별 가맹점 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_GRADE_COUNT_DBIO_H +#define MM_GRADE_COUNT_DBIO_H + +long mmdb_grade_count(const char *grade); + +#endif /* MM_GRADE_COUNT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_grade_count_dbio.pgc b/app/src/mm/dbio/mm_grade_count_dbio.pgc new file mode 100644 index 0000000..6d4afa4 --- /dev/null +++ b/app/src/mm/dbio/mm_grade_count_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_grade_count_dbio.pgc - 등급별 가맹점 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_grade_count_dbio.h" + +long mmdb_grade_count(const char *grade) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_grade[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_grade, grade, sizeof(h_grade)-1); h_grade[sizeof(h_grade)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext + WHERE grade = :h_grade AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_grade_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_grade_stat_dbio.h b/app/src/mm/dbio/mm_grade_stat_dbio.h new file mode 100644 index 0000000..c0bb22c --- /dev/null +++ b/app/src/mm/dbio/mm_grade_stat_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_grade_stat_dbio.h - 등급 통계 upsert copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_GRADE_STAT_DBIO_H +#define MM_GRADE_STAT_DBIO_H + +int mmdb_upsert_grade_stat(const char *statdate, const char *grade, long cnt); + +#endif /* MM_GRADE_STAT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_grade_stat_dbio.pgc b/app/src/mm/dbio/mm_grade_stat_dbio.pgc new file mode 100644 index 0000000..f1b6038 --- /dev/null +++ b/app/src/mm/dbio/mm_grade_stat_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mm_grade_stat_dbio.pgc - 등급 통계 upsert (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_grade_stat_dbio.h" + +int mmdb_upsert_grade_stat(const char *statdate, const char *grade, long cnt) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = cnt; char h_date[16], h_grade[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_date, statdate, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0; + strncpy(h_grade, grade, sizeof(h_grade)-1); h_grade[sizeof(h_grade)-1] = 0; + EXEC SQL INSERT INTO mm_grade_stat (stat_date, grade, merch_cnt) + VALUES (:h_date, :h_grade, :h_cnt) + ON CONFLICT (stat_date, grade) DO UPDATE + SET merch_cnt = EXCLUDED.merch_cnt, updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("mmdb_upsert_grade_stat FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/mm/dbio/mm_hist_query_dbio.h b/app/src/mm/dbio/mm_hist_query_dbio.h new file mode 100644 index 0000000..2403a00 --- /dev/null +++ b/app/src/mm/dbio/mm_hist_query_dbio.h @@ -0,0 +1,12 @@ +/* + * mm_hist_query_dbio.h - 가맹점 변경 이력 건수/최근액션 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_HIST_QUERY_DBIO_H +#define MM_HIST_QUERY_DBIO_H + +long mmdb_hist_count(const char *merch); +int mmdb_last_action(const char *merch, char *act_out /*>=16*/); + +#endif /* MM_HIST_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_hist_query_dbio.pgc b/app/src/mm/dbio/mm_hist_query_dbio.pgc new file mode 100644 index 0000000..fa7a981 --- /dev/null +++ b/app/src/mm/dbio/mm_hist_query_dbio.pgc @@ -0,0 +1,31 @@ +/* + * mm_hist_query_dbio.pgc - 가맹점 변경 이력 건수/최근액션 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_hist_query_dbio.h" + +long mmdb_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_merch_hist WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) { userlog("mmdb_hist_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} + +int mmdb_last_action(const char *merch, char *act_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_act[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT action INTO :h_act FROM mm_merch_hist + WHERE merchant_id = :h_merch ORDER BY hist_id DESC LIMIT 1; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_last_action FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(act_out, h_act, 15); act_out[15] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_holiday_dbio.h b/app/src/mm/dbio/mm_holiday_dbio.h new file mode 100644 index 0000000..ff18220 --- /dev/null +++ b/app/src/mm/dbio/mm_holiday_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_holiday_dbio.h - 영업일/공휴일 판정 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_HOLIDAY_DBIO_H +#define MM_HOLIDAY_DBIO_H + +int mmdb_is_holiday(const char *date); + +#endif /* MM_HOLIDAY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_holiday_dbio.pgc b/app/src/mm/dbio/mm_holiday_dbio.pgc new file mode 100644 index 0000000..bcaae6a --- /dev/null +++ b/app/src/mm/dbio/mm_holiday_dbio.pgc @@ -0,0 +1,18 @@ +/* + * mm_holiday_dbio.pgc - 영업일/공휴일 판정 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_holiday_dbio.h" + +int mmdb_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 mm_holiday WHERE holiday_date = :h_date; + if (sqlca.sqlcode < 0) { userlog("mmdb_is_holiday FAIL [%d]", sqlca.sqlcode); return -1; } + return (h_cnt > 0) ? 1 : 0; +} diff --git a/app/src/mm/dbio/mm_issuer_count_dbio.h b/app/src/mm/dbio/mm_issuer_count_dbio.h new file mode 100644 index 0000000..a1bb29a --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_count_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_issuer_count_dbio.h - 국가별 발급사 수 집계 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_ISSUER_COUNT_DBIO_H +#define MM_ISSUER_COUNT_DBIO_H + +long mmdb_issuer_by_country(const char *country); + +#endif /* MM_ISSUER_COUNT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_issuer_count_dbio.pgc b/app/src/mm/dbio/mm_issuer_count_dbio.pgc new file mode 100644 index 0000000..a98e369 --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_count_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_issuer_count_dbio.pgc - 국가별 발급사 수 집계 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_issuer_count_dbio.h" + +long mmdb_issuer_by_country(const char *country) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_country[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_country, country, sizeof(h_country)-1); h_country[sizeof(h_country)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_issuer + WHERE country = :h_country AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_issuer_by_country FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_issuer_query_dbio.h b/app/src/mm/dbio/mm_issuer_query_dbio.h new file mode 100644 index 0000000..69b32ba --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_query_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_issuer_query_dbio.h - 발급사 국가/상태 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_ISSUER_QUERY_DBIO_H +#define MM_ISSUER_QUERY_DBIO_H + +int mmdb_issuer_country(const char *cd, char *country_out /*>=8*/); + +#endif /* MM_ISSUER_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_issuer_query_dbio.pgc b/app/src/mm/dbio/mm_issuer_query_dbio.pgc new file mode 100644 index 0000000..c949fff --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_query_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_issuer_query_dbio.pgc - 발급사 국가/상태 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_issuer_query_dbio.h" + +int mmdb_issuer_country(const char *cd, char *country_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cd[32], h_country[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cd, cd, sizeof(h_cd)-1); h_cd[sizeof(h_cd)-1] = 0; + EXEC SQL SELECT country INTO :h_country FROM mm_issuer WHERE issuer_cd = :h_cd; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_issuer_country FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(country_out, h_country, 7); country_out[7] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_limit_bump_dbio.h b/app/src/mm/dbio/mm_limit_bump_dbio.h new file mode 100644 index 0000000..2f980d6 --- /dev/null +++ b/app/src/mm/dbio/mm_limit_bump_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_limit_bump_dbio.h - 가맹점 일한도 증감 반영 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_LIMIT_BUMP_DBIO_H +#define MM_LIMIT_BUMP_DBIO_H + +int mmdb_bump_daily_limit(const char *merch, long delta); + +#endif /* MM_LIMIT_BUMP_DBIO_H */ diff --git a/app/src/mm/dbio/mm_limit_bump_dbio.pgc b/app/src/mm/dbio/mm_limit_bump_dbio.pgc new file mode 100644 index 0000000..471e4ff --- /dev/null +++ b/app/src/mm/dbio/mm_limit_bump_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_limit_bump_dbio.pgc - 가맹점 일한도 증감 반영 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_limit_bump_dbio.h" + +int mmdb_bump_daily_limit(const char *merch, long delta) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_delta = delta; 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 UPDATE mm_limit SET daily_limit = daily_limit + :h_delta, updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_bump_daily_limit FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/mm/dbio/mm_limit_query_dbio.h b/app/src/mm/dbio/mm_limit_query_dbio.h new file mode 100644 index 0000000..a6b6e60 --- /dev/null +++ b/app/src/mm/dbio/mm_limit_query_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_limit_query_dbio.h - 가맹점 일한도 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_LIMIT_QUERY_DBIO_H +#define MM_LIMIT_QUERY_DBIO_H + +long mmdb_daily_limit(const char *merch); + +#endif /* MM_LIMIT_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_limit_query_dbio.pgc b/app/src/mm/dbio/mm_limit_query_dbio.pgc new file mode 100644 index 0000000..f844f49 --- /dev/null +++ b/app/src/mm/dbio/mm_limit_query_dbio.pgc @@ -0,0 +1,18 @@ +/* + * mm_limit_query_dbio.pgc - 가맹점 일한도 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_limit_query_dbio.h" + +long mmdb_daily_limit(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_lim = 0; 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 daily_limit INTO :h_lim FROM mm_limit WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_daily_limit FAIL [%d]", sqlca.sqlcode); return -1; } + return h_lim; +} diff --git a/app/src/mm/dbio/mm_mcc_dbio.h b/app/src/mm/dbio/mm_mcc_dbio.h new file mode 100644 index 0000000..cec290c --- /dev/null +++ b/app/src/mm/dbio/mm_mcc_dbio.h @@ -0,0 +1,12 @@ +/* + * mm_mcc_dbio.h - MCC 업종 위험도 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_MCC_DBIO_H +#define MM_MCC_DBIO_H + +long mmdb_mcc_risk(const char *mcc); +int mmdb_mcc_name(const char *mcc, char *name_out /*>=64*/); + +#endif /* MM_MCC_DBIO_H */ diff --git a/app/src/mm/dbio/mm_mcc_dbio.pgc b/app/src/mm/dbio/mm_mcc_dbio.pgc new file mode 100644 index 0000000..70aaea9 --- /dev/null +++ b/app/src/mm/dbio/mm_mcc_dbio.pgc @@ -0,0 +1,30 @@ +/* + * mm_mcc_dbio.pgc - MCC 업종 위험도 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_mcc_dbio.h" + +long mmdb_mcc_risk(const char *mcc) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_risk; char h_mcc[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0; + EXEC SQL SELECT risk_level INTO :h_risk FROM mm_mcc WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_mcc_risk FAIL [%d]", sqlca.sqlcode); return -1; } + return h_risk; +} + +int mmdb_mcc_name(const char *mcc, char *name_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_mcc[16], h_name[64]; + EXEC SQL END DECLARE SECTION; + strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0; + EXEC SQL SELECT mcc_name INTO :h_name FROM mm_mcc WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_mcc_name FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(name_out, h_name, 63); name_out[63] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_merch_total_dbio.h b/app/src/mm/dbio/mm_merch_total_dbio.h new file mode 100644 index 0000000..d471444 --- /dev/null +++ b/app/src/mm/dbio/mm_merch_total_dbio.h @@ -0,0 +1,12 @@ +/* + * mm_merch_total_dbio.h - 전체/상태별 가맹점 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_MERCH_TOTAL_DBIO_H +#define MM_MERCH_TOTAL_DBIO_H + +long mmdb_merch_total(void); +long mmdb_merch_by_status(const char *status); + +#endif /* MM_MERCH_TOTAL_DBIO_H */ diff --git a/app/src/mm/dbio/mm_merch_total_dbio.pgc b/app/src/mm/dbio/mm_merch_total_dbio.pgc new file mode 100644 index 0000000..d5805cf --- /dev/null +++ b/app/src/mm/dbio/mm_merch_total_dbio.pgc @@ -0,0 +1,28 @@ +/* + * mm_merch_total_dbio.pgc - 전체/상태별 가맹점 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_merch_total_dbio.h" + +long mmdb_merch_total(void) +{ + 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) { userlog("mmdb_merch_total FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} + +long mmdb_merch_by_status(const char *status) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_status[16]; + EXEC SQL END DECLARE SECTION; + 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) { userlog("mmdb_merch_by_status FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_status_dbio.h b/app/src/mm/dbio/mm_status_dbio.h new file mode 100644 index 0000000..d14543d --- /dev/null +++ b/app/src/mm/dbio/mm_status_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_status_dbio.h - 가맹점 상태 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_STATUS_DBIO_H +#define MM_STATUS_DBIO_H + +int mmdb_merch_status(const char *merch, char *status_out /*>=16*/); + +#endif /* MM_STATUS_DBIO_H */ diff --git a/app/src/mm/dbio/mm_status_dbio.pgc b/app/src/mm/dbio/mm_status_dbio.pgc new file mode 100644 index 0000000..abcd801 --- /dev/null +++ b/app/src/mm/dbio/mm_status_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_status_dbio.pgc - 가맹점 상태 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_status_dbio.h" + +int mmdb_merch_status(const char *merch, char *status_out) +{ + 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; + EXEC SQL SELECT status INTO :h_status FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_merch_status FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(status_out, h_status, 15); status_out[15] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_terminal_count_dbio.h b/app/src/mm/dbio/mm_terminal_count_dbio.h new file mode 100644 index 0000000..556d916 --- /dev/null +++ b/app/src/mm/dbio/mm_terminal_count_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_terminal_count_dbio.h - 가맹점 보유 단말 수 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_TERMINAL_COUNT_DBIO_H +#define MM_TERMINAL_COUNT_DBIO_H + +long mmdb_terminal_count(const char *merch); + +#endif /* MM_TERMINAL_COUNT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_terminal_count_dbio.pgc b/app/src/mm/dbio/mm_terminal_count_dbio.pgc new file mode 100644 index 0000000..a78d653 --- /dev/null +++ b/app/src/mm/dbio/mm_terminal_count_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_terminal_count_dbio.pgc - 가맹점 보유 단말 수 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_terminal_count_dbio.h" + +long mmdb_terminal_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_terminal + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_terminal_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/mm/dbio/mm_terminal_query_dbio.h b/app/src/mm/dbio/mm_terminal_query_dbio.h new file mode 100644 index 0000000..6bd61ad --- /dev/null +++ b/app/src/mm/dbio/mm_terminal_query_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_terminal_query_dbio.h - 단말 소속 가맹점 조회 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_TERMINAL_QUERY_DBIO_H +#define MM_TERMINAL_QUERY_DBIO_H + +int mmdb_terminal_merch(const char *term, char *merch_out /*>=64*/); + +#endif /* MM_TERMINAL_QUERY_DBIO_H */ diff --git a/app/src/mm/dbio/mm_terminal_query_dbio.pgc b/app/src/mm/dbio/mm_terminal_query_dbio.pgc new file mode 100644 index 0000000..eb0c92d --- /dev/null +++ b/app/src/mm/dbio/mm_terminal_query_dbio.pgc @@ -0,0 +1,19 @@ +/* + * mm_terminal_query_dbio.pgc - 단말 소속 가맹점 조회 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_terminal_query_dbio.h" + +int mmdb_terminal_merch(const char *term, char *merch_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_term[32], h_merch[64]; + EXEC SQL END DECLARE SECTION; + strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0; + EXEC SQL SELECT merchant_id INTO :h_merch FROM mm_terminal WHERE terminal_id = :h_term; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_terminal_merch FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(merch_out, h_merch, 63); merch_out[63] = 0; + return 0; +} diff --git a/app/src/mm/dbio/mm_touch_dbio.h b/app/src/mm/dbio/mm_touch_dbio.h new file mode 100644 index 0000000..8840db5 --- /dev/null +++ b/app/src/mm/dbio/mm_touch_dbio.h @@ -0,0 +1,11 @@ +/* + * mm_touch_dbio.h - 가맹점 갱신시각 터치 copybook (part of libmmdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_TOUCH_DBIO_H +#define MM_TOUCH_DBIO_H + +int mmdb_touch_merch(const char *merch); + +#endif /* MM_TOUCH_DBIO_H */ diff --git a/app/src/mm/dbio/mm_touch_dbio.pgc b/app/src/mm/dbio/mm_touch_dbio.pgc new file mode 100644 index 0000000..aecad3e --- /dev/null +++ b/app/src/mm/dbio/mm_touch_dbio.pgc @@ -0,0 +1,18 @@ +/* + * mm_touch_dbio.pgc - 가맹점 갱신시각 터치 (libmmdbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_touch_dbio.h" + +int mmdb_touch_merch(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE mm_merchant_ext SET updated_at = now() WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("mmdb_touch_merch FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/mm/mm_svr.pgc b/app/src/mm/mm_svr.pgc index 12cd395..0f90996 100644 --- a/app/src/mm/mm_svr.pgc +++ b/app/src/mm/mm_svr.pgc @@ -1,69 +1,84 @@ /* - * mm_svr.pgc - mm (마스터/master) MODULE SERVER. + * mm_svr.pgc - mm (마스터/master) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of mm's ~30 online services now lives in its own file + * (app/src/mm/svc/.pgc) with its own copybook header + * (app/src/mm/svc/.h); those objects are archived into libmmsvc.a and + * linked into this server. Here we only include every service copybook, tpopen() + * the ECPG XA RM, tpadvertise() all services from a {name, fn} table, tpclose(). * * 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. + * there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch. */ -#include -#include -#include #include #include #include -#include -#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); } +/* every mm service's copybook header (declares its prototype) */ +#include "MM_MERCH_REG.h" +#include "MM_MERCH_UPD.h" +#include "MM_MERCH_INQ.h" +#include "MM_MERCH_DEL.h" +#include "MM_FEERATE_SET.h" +#include "MM_FEERATE_INQ.h" +#include "MM_FEERATE_HIST.h" +#include "MM_BIN_REG.h" +#include "MM_BIN_INQ.h" +#include "MM_LIMIT_SET.h" +#include "MM_LIMIT_INQ.h" +#include "MM_CODE_REG.h" +#include "MM_CODE_INQ.h" +#include "MM_ISSUER_REG.h" +#include "MM_ISSUER_INQ.h" +#include "MM_MERCH_STATUS.h" +#include "MM_MERCH_ACCT.h" +#include "MM_MERCH_GRADE.h" +#include "MM_TERMINAL_REG.h" +#include "MM_TERMINAL_INQ.h" +#include "MM_MCC_REG.h" +#include "MM_HOLIDAY_REG.h" +#include "MM_FX_REG.h" +#include "MM_FX_INQ.h" +#include "MM_MERCH_BULK.h" +#include "MM_VALIDATE.h" +#include "MM_MERCH_SUM.h" +#include "MM_CODE_CACHE.h" +#include "MM_STATUS.h" +#include "MM_CONFIRM.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, + {"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} }; @@ -90,566 +105,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/mm/run/run_acct_check.sh b/app/src/mm/run/run_acct_check.sh new file mode 100755 index 0000000..900d4b4 --- /dev/null +++ b/app/src/mm/run/run_acct_check.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_acct_check.sh - 정산계좌 미설정 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_acct_check.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_acct_check.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_acct_check_batch diff --git a/app/src/mm/run/run_bin_load.sh b/app/src/mm/run/run_bin_load.sh new file mode 100755 index 0000000..b2fd523 --- /dev/null +++ b/app/src/mm/run/run_bin_load.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_bin_load.sh - 발급사별 BIN 적재 현황 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_bin_load.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_bin_load.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_bin_load_batch diff --git a/app/src/mm/run/run_code_sync.sh b/app/src/mm/run/run_code_sync.sh new file mode 100755 index 0000000..5672a2d --- /dev/null +++ b/app/src/mm/run/run_code_sync.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_code_sync.sh - 공통코드 활성 수 동기화 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_code_sync.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_code_sync.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_code_sync_batch diff --git a/app/src/mm/run/run_dedup.sh b/app/src/mm/run/run_dedup.sh new file mode 100755 index 0000000..ebbde56 --- /dev/null +++ b/app/src/mm/run/run_dedup.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_dedup.sh - 동일 상호 중복 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_dedup.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_dedup.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_dedup_batch diff --git a/app/src/mm/run/run_dormant.sh b/app/src/mm/run/run_dormant.sh new file mode 100755 index 0000000..b2155b1 --- /dev/null +++ b/app/src/mm/run/run_dormant.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_dormant.sh - 휴면 가맹점 탐지 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_dormant.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_dormant.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_dormant_batch "$ARG" diff --git a/app/src/mm/run/run_feerate_avg.sh b/app/src/mm/run/run_feerate_avg.sh new file mode 100755 index 0000000..75f94b3 --- /dev/null +++ b/app/src/mm/run/run_feerate_avg.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_feerate_avg.sh - 수수료율 평균 통계 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_feerate_avg.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_feerate_avg.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_feerate_avg_batch diff --git a/app/src/mm/run/run_fx_snapshot.sh b/app/src/mm/run/run_fx_snapshot.sh new file mode 100755 index 0000000..448ae4b --- /dev/null +++ b/app/src/mm/run/run_fx_snapshot.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_fx_snapshot.sh - 환율 마스터 스냅샷 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_fx_snapshot.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_fx_snapshot.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_fx_snapshot_batch diff --git a/app/src/mm/run/run_grade_dist.sh b/app/src/mm/run/run_grade_dist.sh new file mode 100755 index 0000000..4fa3c06 --- /dev/null +++ b/app/src/mm/run/run_grade_dist.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_grade_dist.sh - 가맹점 등급 분포 적재 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_grade_dist.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_grade_dist.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_grade_dist_batch "$BIZDATE" diff --git a/app/src/mm/run/run_grade_recalc.sh b/app/src/mm/run/run_grade_recalc.sh new file mode 100755 index 0000000..6474815 --- /dev/null +++ b/app/src/mm/run/run_grade_recalc.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_grade_recalc.sh - 가맹점 등급 재산정 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_grade_recalc.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_grade_recalc.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_grade_recalc_batch "$BIZDATE" diff --git a/app/src/mm/run/run_holiday.sh b/app/src/mm/run/run_holiday.sh new file mode 100755 index 0000000..a2219e1 --- /dev/null +++ b/app/src/mm/run/run_holiday.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_holiday.sh - 연도별 공휴일 건수 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_holiday.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_holiday.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_holiday_batch "$ARG" diff --git a/app/src/mm/run/run_issuer_stat.sh b/app/src/mm/run/run_issuer_stat.sh new file mode 100755 index 0000000..ab7109a --- /dev/null +++ b/app/src/mm/run/run_issuer_stat.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_issuer_stat.sh - 국가별 발급사 통계 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_issuer_stat.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_issuer_stat.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_issuer_stat_batch diff --git a/app/src/mm/run/run_limit_audit.sh b/app/src/mm/run/run_limit_audit.sh new file mode 100755 index 0000000..03deff5 --- /dev/null +++ b/app/src/mm/run/run_limit_audit.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_limit_audit.sh - 일한도 초과 위험 감사 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_limit_audit.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_limit_audit.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_limit_audit_batch "$ARG" diff --git a/app/src/mm/run/run_mcc_risk.sh b/app/src/mm/run/run_mcc_risk.sh new file mode 100755 index 0000000..4708d84 --- /dev/null +++ b/app/src/mm/run/run_mcc_risk.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_mcc_risk.sh - MCC 위험도 합계 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_mcc_risk.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_mcc_risk.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_mcc_risk_batch diff --git a/app/src/mm/run/run_merch_count.sh b/app/src/mm/run/run_merch_count.sh new file mode 100755 index 0000000..7f4042d --- /dev/null +++ b/app/src/mm/run/run_merch_count.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_merch_count.sh - 가맹점 마스터 건수 집계 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_merch_count.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_merch_count.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_merch_count_batch diff --git a/app/src/mm/run/run_stat.sh b/app/src/mm/run/run_stat.sh new file mode 100755 index 0000000..e7dc4cd --- /dev/null +++ b/app/src/mm/run/run_stat.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_stat.sh - 등급별 가맹점 통계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_stat.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_stat.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_stat_batch "$BIZDATE" diff --git a/app/src/mm/run/run_summary.sh b/app/src/mm/run/run_summary.sh new file mode 100755 index 0000000..8883e23 --- /dev/null +++ b/app/src/mm/run/run_summary.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_summary.sh - 마스터 종합 요약 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_summary.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_summary.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_summary_batch diff --git a/app/src/mm/run/run_terminal_stat.sh b/app/src/mm/run/run_terminal_stat.sh new file mode 100755 index 0000000..15bc38b --- /dev/null +++ b/app/src/mm/run/run_terminal_stat.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_terminal_stat.sh - 단말 모델별 통계 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 mm 배치를 실행. +## Usage: run_terminal_stat.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_terminal_stat.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/mm_terminal_stat_batch diff --git a/app/src/mm/svc/MM_BIN_INQ.h b/app/src/mm/svc/MM_BIN_INQ.h new file mode 100644 index 0000000..f57696c --- /dev/null +++ b/app/src/mm/svc/MM_BIN_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_BIN_INQ.h - mm 서비스 MM_BIN_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_ISSUER, T_KEY1, T_STATUS + */ +#ifndef MM_BIN_INQ_H +#define MM_BIN_INQ_H + +#include "mm_svc.h" + +void MM_BIN_INQ(TPSVCINFO *p); + +#endif /* MM_BIN_INQ_H */ diff --git a/app/src/mm/svc/MM_BIN_INQ.pgc b/app/src/mm/svc/MM_BIN_INQ.pgc new file mode 100644 index 0000000..6ff3b79 --- /dev/null +++ b/app/src/mm/svc/MM_BIN_INQ.pgc @@ -0,0 +1,24 @@ +/* + * MM_BIN_INQ.pgc - mm 모듈 서비스 MM_BIN_INQ (one service = one file). + * 기능: BIN 조회. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_BIN_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_BIN_REG.h b/app/src/mm/svc/MM_BIN_REG.h new file mode 100644 index 0000000..7af5012 --- /dev/null +++ b/app/src/mm/svc/MM_BIN_REG.h @@ -0,0 +1,12 @@ +/* + * MM_BIN_REG.h - mm 서비스 MM_BIN_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_ISSUER, T_KEY1, T_STR5 + */ +#ifndef MM_BIN_REG_H +#define MM_BIN_REG_H + +#include "mm_svc.h" + +void MM_BIN_REG(TPSVCINFO *p); + +#endif /* MM_BIN_REG_H */ diff --git a/app/src/mm/svc/MM_BIN_REG.pgc b/app/src/mm/svc/MM_BIN_REG.pgc new file mode 100644 index 0000000..bc03f28 --- /dev/null +++ b/app/src/mm/svc/MM_BIN_REG.pgc @@ -0,0 +1,30 @@ +/* + * MM_BIN_REG.pgc - mm 모듈 서비스 MM_BIN_REG (one service = one file). + * 기능: BIN 마스터 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_BIN_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_CODE_CACHE.h b/app/src/mm/svc/MM_CODE_CACHE.h new file mode 100644 index 0000000..6301b76 --- /dev/null +++ b/app/src/mm/svc/MM_CODE_CACHE.h @@ -0,0 +1,12 @@ +/* + * MM_CODE_CACHE.h - mm 서비스 MM_CODE_CACHE 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_STR1 + */ +#ifndef MM_CODE_CACHE_H +#define MM_CODE_CACHE_H + +#include "mm_svc.h" + +void MM_CODE_CACHE(TPSVCINFO *p); + +#endif /* MM_CODE_CACHE_H */ diff --git a/app/src/mm/svc/MM_CODE_CACHE.pgc b/app/src/mm/svc/MM_CODE_CACHE.pgc new file mode 100644 index 0000000..25b5308 --- /dev/null +++ b/app/src/mm/svc/MM_CODE_CACHE.pgc @@ -0,0 +1,24 @@ +/* + * MM_CODE_CACHE.pgc - mm 모듈 서비스 MM_CODE_CACHE (one service = one file). + * 기능: 코드그룹의 활성 코드 수 (캐시 워밍 시뮬). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_CODE_CACHE.h" + +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); +} diff --git a/app/src/mm/svc/MM_CODE_INQ.h b/app/src/mm/svc/MM_CODE_INQ.h new file mode 100644 index 0000000..9c5969a --- /dev/null +++ b/app/src/mm/svc/MM_CODE_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_CODE_INQ.h - mm 서비스 MM_CODE_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_STATUS, T_STR1, T_STR2 + */ +#ifndef MM_CODE_INQ_H +#define MM_CODE_INQ_H + +#include "mm_svc.h" + +void MM_CODE_INQ(TPSVCINFO *p); + +#endif /* MM_CODE_INQ_H */ diff --git a/app/src/mm/svc/MM_CODE_INQ.pgc b/app/src/mm/svc/MM_CODE_INQ.pgc new file mode 100644 index 0000000..bfbdb70 --- /dev/null +++ b/app/src/mm/svc/MM_CODE_INQ.pgc @@ -0,0 +1,26 @@ +/* + * MM_CODE_INQ.pgc - mm 모듈 서비스 MM_CODE_INQ (one service = one file). + * 기능: 공통코드 조회. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_CODE_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_CODE_REG.h b/app/src/mm/svc/MM_CODE_REG.h new file mode 100644 index 0000000..08c76ca --- /dev/null +++ b/app/src/mm/svc/MM_CODE_REG.h @@ -0,0 +1,12 @@ +/* + * MM_CODE_REG.h - mm 서비스 MM_CODE_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_STR1, T_STR2 + */ +#ifndef MM_CODE_REG_H +#define MM_CODE_REG_H + +#include "mm_svc.h" + +void MM_CODE_REG(TPSVCINFO *p); + +#endif /* MM_CODE_REG_H */ diff --git a/app/src/mm/svc/MM_CODE_REG.pgc b/app/src/mm/svc/MM_CODE_REG.pgc new file mode 100644 index 0000000..c000985 --- /dev/null +++ b/app/src/mm/svc/MM_CODE_REG.pgc @@ -0,0 +1,28 @@ +/* + * MM_CODE_REG.pgc - mm 모듈 서비스 MM_CODE_REG (one service = one file). + * 기능: 공통코드 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_CODE_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_CONFIRM.h b/app/src/mm/svc/MM_CONFIRM.h new file mode 100644 index 0000000..08f1687 --- /dev/null +++ b/app/src/mm/svc/MM_CONFIRM.h @@ -0,0 +1,12 @@ +/* + * MM_CONFIRM.h - mm 서비스 MM_CONFIRM 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS + */ +#ifndef MM_CONFIRM_H +#define MM_CONFIRM_H + +#include "mm_svc.h" + +void MM_CONFIRM(TPSVCINFO *p); + +#endif /* MM_CONFIRM_H */ diff --git a/app/src/mm/svc/MM_CONFIRM.pgc b/app/src/mm/svc/MM_CONFIRM.pgc new file mode 100644 index 0000000..58b244d --- /dev/null +++ b/app/src/mm/svc/MM_CONFIRM.pgc @@ -0,0 +1,18 @@ +/* + * MM_CONFIRM.pgc - mm 모듈 서비스 MM_CONFIRM (one service = one file). + * 기능: 가맹점 승인 확정 (상태 ACTIVE + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_CONFIRM.h" + +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); +} diff --git a/app/src/mm/svc/MM_FEERATE_HIST.h b/app/src/mm/svc/MM_FEERATE_HIST.h new file mode 100644 index 0000000..42a7a08 --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_HIST.h @@ -0,0 +1,12 @@ +/* + * MM_FEERATE_HIST.h - mm 서비스 MM_FEERATE_HIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_MERCHANT + */ +#ifndef MM_FEERATE_HIST_H +#define MM_FEERATE_HIST_H + +#include "mm_svc.h" + +void MM_FEERATE_HIST(TPSVCINFO *p); + +#endif /* MM_FEERATE_HIST_H */ diff --git a/app/src/mm/svc/MM_FEERATE_HIST.pgc b/app/src/mm/svc/MM_FEERATE_HIST.pgc new file mode 100644 index 0000000..0cf223b --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_HIST.pgc @@ -0,0 +1,19 @@ +/* + * MM_FEERATE_HIST.pgc - mm 모듈 서비스 MM_FEERATE_HIST (one service = one file). + * 기능: 수수료율 변경 이력 건수 (dbio). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_FEERATE_HIST.h" + +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); +} diff --git a/app/src/mm/svc/MM_FEERATE_INQ.h b/app/src/mm/svc/MM_FEERATE_INQ.h new file mode 100644 index 0000000..7166023 --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_FEERATE_INQ.h - mm 서비스 MM_FEERATE_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_MERCHANT, T_STR4 + */ +#ifndef MM_FEERATE_INQ_H +#define MM_FEERATE_INQ_H + +#include "mm_svc.h" + +void MM_FEERATE_INQ(TPSVCINFO *p); + +#endif /* MM_FEERATE_INQ_H */ diff --git a/app/src/mm/svc/MM_FEERATE_INQ.pgc b/app/src/mm/svc/MM_FEERATE_INQ.pgc new file mode 100644 index 0000000..72315f2 --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_INQ.pgc @@ -0,0 +1,19 @@ +/* + * MM_FEERATE_INQ.pgc - mm 모듈 서비스 MM_FEERATE_INQ (one service = one file). + * 기능: 수수료율 조회 (dbio). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_FEERATE_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_FEERATE_SET.h b/app/src/mm/svc/MM_FEERATE_SET.h new file mode 100644 index 0000000..8d5788d --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_SET.h @@ -0,0 +1,12 @@ +/* + * MM_FEERATE_SET.h - mm 서비스 MM_FEERATE_SET 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_MERCHANT, T_STR4 + */ +#ifndef MM_FEERATE_SET_H +#define MM_FEERATE_SET_H + +#include "mm_svc.h" + +void MM_FEERATE_SET(TPSVCINFO *p); + +#endif /* MM_FEERATE_SET_H */ diff --git a/app/src/mm/svc/MM_FEERATE_SET.pgc b/app/src/mm/svc/MM_FEERATE_SET.pgc new file mode 100644 index 0000000..2c0cec9 --- /dev/null +++ b/app/src/mm/svc/MM_FEERATE_SET.pgc @@ -0,0 +1,20 @@ +/* + * MM_FEERATE_SET.pgc - mm 모듈 서비스 MM_FEERATE_SET (one service = one file). + * 기능: 수수료율 설정 (dbio upsert + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_FEERATE_SET.h" + +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); +} diff --git a/app/src/mm/svc/MM_FX_INQ.h b/app/src/mm/svc/MM_FX_INQ.h new file mode 100644 index 0000000..9671c43 --- /dev/null +++ b/app/src/mm/svc/MM_FX_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_FX_INQ.h - mm 서비스 MM_FX_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_ARG2, T_CCY, T_FX_AMT + */ +#ifndef MM_FX_INQ_H +#define MM_FX_INQ_H + +#include "mm_svc.h" + +void MM_FX_INQ(TPSVCINFO *p); + +#endif /* MM_FX_INQ_H */ diff --git a/app/src/mm/svc/MM_FX_INQ.pgc b/app/src/mm/svc/MM_FX_INQ.pgc new file mode 100644 index 0000000..2f244ea --- /dev/null +++ b/app/src/mm/svc/MM_FX_INQ.pgc @@ -0,0 +1,27 @@ +/* + * MM_FX_INQ.pgc - mm 모듈 서비스 MM_FX_INQ (one service = one file). + * 기능: 환율 조회 + 금액 환산 (rate_bps는 100 기준 bps). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_FX_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_FX_REG.h b/app/src/mm/svc/MM_FX_REG.h new file mode 100644 index 0000000..73adbf6 --- /dev/null +++ b/app/src/mm/svc/MM_FX_REG.h @@ -0,0 +1,12 @@ +/* + * MM_FX_REG.h - mm 서비스 MM_FX_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG2, T_CCY + */ +#ifndef MM_FX_REG_H +#define MM_FX_REG_H + +#include "mm_svc.h" + +void MM_FX_REG(TPSVCINFO *p); + +#endif /* MM_FX_REG_H */ diff --git a/app/src/mm/svc/MM_FX_REG.pgc b/app/src/mm/svc/MM_FX_REG.pgc new file mode 100644 index 0000000..764a8db --- /dev/null +++ b/app/src/mm/svc/MM_FX_REG.pgc @@ -0,0 +1,26 @@ +/* + * MM_FX_REG.pgc - mm 모듈 서비스 MM_FX_REG (one service = one file). + * 기능: 환율 마스터 upsert. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_FX_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_HOLIDAY_REG.h b/app/src/mm/svc/MM_HOLIDAY_REG.h new file mode 100644 index 0000000..864f9ad --- /dev/null +++ b/app/src/mm/svc/MM_HOLIDAY_REG.h @@ -0,0 +1,12 @@ +/* + * MM_HOLIDAY_REG.h - mm 서비스 MM_HOLIDAY_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_STR1 + */ +#ifndef MM_HOLIDAY_REG_H +#define MM_HOLIDAY_REG_H + +#include "mm_svc.h" + +void MM_HOLIDAY_REG(TPSVCINFO *p); + +#endif /* MM_HOLIDAY_REG_H */ diff --git a/app/src/mm/svc/MM_HOLIDAY_REG.pgc b/app/src/mm/svc/MM_HOLIDAY_REG.pgc new file mode 100644 index 0000000..0cf32cc --- /dev/null +++ b/app/src/mm/svc/MM_HOLIDAY_REG.pgc @@ -0,0 +1,26 @@ +/* + * MM_HOLIDAY_REG.pgc - mm 모듈 서비스 MM_HOLIDAY_REG (one service = one file). + * 기능: 공휴일 마스터 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_HOLIDAY_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_ISSUER_INQ.h b/app/src/mm/svc/MM_ISSUER_INQ.h new file mode 100644 index 0000000..aba9ec8 --- /dev/null +++ b/app/src/mm/svc/MM_ISSUER_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_ISSUER_INQ.h - mm 서비스 MM_ISSUER_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_ISSUER, T_STATUS, T_STR1 + */ +#ifndef MM_ISSUER_INQ_H +#define MM_ISSUER_INQ_H + +#include "mm_svc.h" + +void MM_ISSUER_INQ(TPSVCINFO *p); + +#endif /* MM_ISSUER_INQ_H */ diff --git a/app/src/mm/svc/MM_ISSUER_INQ.pgc b/app/src/mm/svc/MM_ISSUER_INQ.pgc new file mode 100644 index 0000000..9e6952e --- /dev/null +++ b/app/src/mm/svc/MM_ISSUER_INQ.pgc @@ -0,0 +1,24 @@ +/* + * MM_ISSUER_INQ.pgc - mm 모듈 서비스 MM_ISSUER_INQ (one service = one file). + * 기능: 발급사 조회. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_ISSUER_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_ISSUER_REG.h b/app/src/mm/svc/MM_ISSUER_REG.h new file mode 100644 index 0000000..517fa86 --- /dev/null +++ b/app/src/mm/svc/MM_ISSUER_REG.h @@ -0,0 +1,12 @@ +/* + * MM_ISSUER_REG.h - mm 서비스 MM_ISSUER_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_ISSUER, T_STR1, T_STR2 + */ +#ifndef MM_ISSUER_REG_H +#define MM_ISSUER_REG_H + +#include "mm_svc.h" + +void MM_ISSUER_REG(TPSVCINFO *p); + +#endif /* MM_ISSUER_REG_H */ diff --git a/app/src/mm/svc/MM_ISSUER_REG.pgc b/app/src/mm/svc/MM_ISSUER_REG.pgc new file mode 100644 index 0000000..fc8dd71 --- /dev/null +++ b/app/src/mm/svc/MM_ISSUER_REG.pgc @@ -0,0 +1,29 @@ +/* + * MM_ISSUER_REG.pgc - mm 모듈 서비스 MM_ISSUER_REG (one service = one file). + * 기능: 발급사 마스터 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_ISSUER_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_LIMIT_INQ.h b/app/src/mm/svc/MM_LIMIT_INQ.h new file mode 100644 index 0000000..4b111ff --- /dev/null +++ b/app/src/mm/svc/MM_LIMIT_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_LIMIT_INQ.h - mm 서비스 MM_LIMIT_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMT1, T_AMT2, T_AMT3, T_MERCHANT + */ +#ifndef MM_LIMIT_INQ_H +#define MM_LIMIT_INQ_H + +#include "mm_svc.h" + +void MM_LIMIT_INQ(TPSVCINFO *p); + +#endif /* MM_LIMIT_INQ_H */ diff --git a/app/src/mm/svc/MM_LIMIT_INQ.pgc b/app/src/mm/svc/MM_LIMIT_INQ.pgc new file mode 100644 index 0000000..e3ffb12 --- /dev/null +++ b/app/src/mm/svc/MM_LIMIT_INQ.pgc @@ -0,0 +1,24 @@ +/* + * MM_LIMIT_INQ.pgc - mm 모듈 서비스 MM_LIMIT_INQ (one service = one file). + * 기능: 가맹점 한도 조회. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_LIMIT_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_LIMIT_SET.h b/app/src/mm/svc/MM_LIMIT_SET.h new file mode 100644 index 0000000..c9e5fa5 --- /dev/null +++ b/app/src/mm/svc/MM_LIMIT_SET.h @@ -0,0 +1,12 @@ +/* + * MM_LIMIT_SET.h - mm 서비스 MM_LIMIT_SET 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMT1, T_AMT2, T_AMT3, T_MERCHANT + */ +#ifndef MM_LIMIT_SET_H +#define MM_LIMIT_SET_H + +#include "mm_svc.h" + +void MM_LIMIT_SET(TPSVCINFO *p); + +#endif /* MM_LIMIT_SET_H */ diff --git a/app/src/mm/svc/MM_LIMIT_SET.pgc b/app/src/mm/svc/MM_LIMIT_SET.pgc new file mode 100644 index 0000000..e551b3c --- /dev/null +++ b/app/src/mm/svc/MM_LIMIT_SET.pgc @@ -0,0 +1,31 @@ +/* + * MM_LIMIT_SET.pgc - mm 모듈 서비스 MM_LIMIT_SET (one service = one file). + * 기능: 가맹점 한도 설정 (upsert). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_LIMIT_SET.h" + +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); +} diff --git a/app/src/mm/svc/MM_MCC_REG.h b/app/src/mm/svc/MM_MCC_REG.h new file mode 100644 index 0000000..754e899 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_REG.h @@ -0,0 +1,12 @@ +/* + * MM_MCC_REG.h - mm 서비스 MM_MCC_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_STR1, T_STR6 + */ +#ifndef MM_MCC_REG_H +#define MM_MCC_REG_H + +#include "mm_svc.h" + +void MM_MCC_REG(TPSVCINFO *p); + +#endif /* MM_MCC_REG_H */ diff --git a/app/src/mm/svc/MM_MCC_REG.pgc b/app/src/mm/svc/MM_MCC_REG.pgc new file mode 100644 index 0000000..65444e5 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_REG.pgc @@ -0,0 +1,29 @@ +/* + * MM_MCC_REG.pgc - mm 모듈 서비스 MM_MCC_REG (one service = one file). + * 기능: MCC 업종 마스터 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MCC_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_ACCT.h b/app/src/mm/svc/MM_MERCH_ACCT.h new file mode 100644 index 0000000..ca08d0d --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_ACCT.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_ACCT.h - mm 서비스 MM_MERCH_ACCT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR3 + */ +#ifndef MM_MERCH_ACCT_H +#define MM_MERCH_ACCT_H + +#include "mm_svc.h" + +void MM_MERCH_ACCT(TPSVCINFO *p); + +#endif /* MM_MERCH_ACCT_H */ diff --git a/app/src/mm/svc/MM_MERCH_ACCT.pgc b/app/src/mm/svc/MM_MERCH_ACCT.pgc new file mode 100644 index 0000000..19975c8 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_ACCT.pgc @@ -0,0 +1,25 @@ +/* + * MM_MERCH_ACCT.pgc - mm 모듈 서비스 MM_MERCH_ACCT (one service = one file). + * 기능: 정산계좌 변경 (직접 UPDATE + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_ACCT.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_BULK.h b/app/src/mm/svc/MM_MERCH_BULK.h new file mode 100644 index 0000000..4047ea0 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_BULK.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_BULK.h - mm 서비스 MM_MERCH_BULK 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_MERCHANT, T_STR2 + */ +#ifndef MM_MERCH_BULK_H +#define MM_MERCH_BULK_H + +#include "mm_svc.h" + +void MM_MERCH_BULK(TPSVCINFO *p); + +#endif /* MM_MERCH_BULK_H */ diff --git a/app/src/mm/svc/MM_MERCH_BULK.pgc b/app/src/mm/svc/MM_MERCH_BULK.pgc new file mode 100644 index 0000000..5c68105 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_BULK.pgc @@ -0,0 +1,33 @@ +/* + * MM_MERCH_BULK.pgc - mm 모듈 서비스 MM_MERCH_BULK (one service = one file). + * 기능: 가맹점 대량 등록 (T_COUNT개 파생 생성). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_BULK.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_DEL.h b/app/src/mm/svc/MM_MERCH_DEL.h new file mode 100644 index 0000000..be4e25a --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_DEL.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_DEL.h - mm 서비스 MM_MERCH_DEL 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS + */ +#ifndef MM_MERCH_DEL_H +#define MM_MERCH_DEL_H + +#include "mm_svc.h" + +void MM_MERCH_DEL(TPSVCINFO *p); + +#endif /* MM_MERCH_DEL_H */ diff --git a/app/src/mm/svc/MM_MERCH_DEL.pgc b/app/src/mm/svc/MM_MERCH_DEL.pgc new file mode 100644 index 0000000..8bb541b --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_DEL.pgc @@ -0,0 +1,18 @@ +/* + * MM_MERCH_DEL.pgc - mm 모듈 서비스 MM_MERCH_DEL (one service = one file). + * 기능: 가맹점 논리 삭제 (상태 DELETED + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_DEL.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_GRADE.h b/app/src/mm/svc/MM_MERCH_GRADE.h new file mode 100644 index 0000000..33d749c --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_GRADE.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_GRADE.h - mm 서비스 MM_MERCH_GRADE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR2 + */ +#ifndef MM_MERCH_GRADE_H +#define MM_MERCH_GRADE_H + +#include "mm_svc.h" + +void MM_MERCH_GRADE(TPSVCINFO *p); + +#endif /* MM_MERCH_GRADE_H */ diff --git a/app/src/mm/svc/MM_MERCH_GRADE.pgc b/app/src/mm/svc/MM_MERCH_GRADE.pgc new file mode 100644 index 0000000..346bdbb --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_GRADE.pgc @@ -0,0 +1,19 @@ +/* + * MM_MERCH_GRADE.pgc - mm 모듈 서비스 MM_MERCH_GRADE (one service = one file). + * 기능: 가맹점 등급 변경 (dbio + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_GRADE.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_INQ.h b/app/src/mm/svc/MM_MERCH_INQ.h new file mode 100644 index 0000000..a07fb50 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_INQ.h - mm 서비스 MM_MERCH_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS, T_STR1, T_STR2 + */ +#ifndef MM_MERCH_INQ_H +#define MM_MERCH_INQ_H + +#include "mm_svc.h" + +void MM_MERCH_INQ(TPSVCINFO *p); + +#endif /* MM_MERCH_INQ_H */ diff --git a/app/src/mm/svc/MM_MERCH_INQ.pgc b/app/src/mm/svc/MM_MERCH_INQ.pgc new file mode 100644 index 0000000..5e1ab7e --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_INQ.pgc @@ -0,0 +1,19 @@ +/* + * MM_MERCH_INQ.pgc - mm 모듈 서비스 MM_MERCH_INQ (one service = one file). + * 기능: 가맹점 마스터 조회 (dbio). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_REG.h b/app/src/mm/svc/MM_MERCH_REG.h new file mode 100644 index 0000000..ec61a03 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_REG.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_REG.h - mm 서비스 MM_MERCH_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS, T_STR1, T_STR2, T_STR3 + */ +#ifndef MM_MERCH_REG_H +#define MM_MERCH_REG_H + +#include "mm_svc.h" + +void MM_MERCH_REG(TPSVCINFO *p); + +#endif /* MM_MERCH_REG_H */ diff --git a/app/src/mm/svc/MM_MERCH_REG.pgc b/app/src/mm/svc/MM_MERCH_REG.pgc new file mode 100644 index 0000000..5888872 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_REG.pgc @@ -0,0 +1,26 @@ +/* + * MM_MERCH_REG.pgc - mm 모듈 서비스 MM_MERCH_REG (one service = one file). + * 기능: 가맹점 확장 마스터 등록 (dbio upsert + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_STATUS.h b/app/src/mm/svc/MM_MERCH_STATUS.h new file mode 100644 index 0000000..d35a687 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_STATUS.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_STATUS.h - mm 서비스 MM_MERCH_STATUS 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS + */ +#ifndef MM_MERCH_STATUS_H +#define MM_MERCH_STATUS_H + +#include "mm_svc.h" + +void MM_MERCH_STATUS(TPSVCINFO *p); + +#endif /* MM_MERCH_STATUS_H */ diff --git a/app/src/mm/svc/MM_MERCH_STATUS.pgc b/app/src/mm/svc/MM_MERCH_STATUS.pgc new file mode 100644 index 0000000..4441070 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_STATUS.pgc @@ -0,0 +1,18 @@ +/* + * MM_MERCH_STATUS.pgc - mm 모듈 서비스 MM_MERCH_STATUS (one service = one file). + * 기능: 가맹점 상태 변경 (dbio + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_STATUS.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_SUM.h b/app/src/mm/svc/MM_MERCH_SUM.h new file mode 100644 index 0000000..91a6b1c --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_SUM.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_SUM.h - mm 서비스 MM_MERCH_SUM 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_STATUS + */ +#ifndef MM_MERCH_SUM_H +#define MM_MERCH_SUM_H + +#include "mm_svc.h" + +void MM_MERCH_SUM(TPSVCINFO *p); + +#endif /* MM_MERCH_SUM_H */ diff --git a/app/src/mm/svc/MM_MERCH_SUM.pgc b/app/src/mm/svc/MM_MERCH_SUM.pgc new file mode 100644 index 0000000..7a019b8 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_SUM.pgc @@ -0,0 +1,23 @@ +/* + * MM_MERCH_SUM.pgc - mm 모듈 서비스 MM_MERCH_SUM (one service = one file). + * 기능: 상태별 가맹점 수 집계 (ACTIVE 기준). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_SUM.h" + +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); +} diff --git a/app/src/mm/svc/MM_MERCH_UPD.h b/app/src/mm/svc/MM_MERCH_UPD.h new file mode 100644 index 0000000..c67d774 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_UPD.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_UPD.h - mm 서비스 MM_MERCH_UPD 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1, T_STR3 + */ +#ifndef MM_MERCH_UPD_H +#define MM_MERCH_UPD_H + +#include "mm_svc.h" + +void MM_MERCH_UPD(TPSVCINFO *p); + +#endif /* MM_MERCH_UPD_H */ diff --git a/app/src/mm/svc/MM_MERCH_UPD.pgc b/app/src/mm/svc/MM_MERCH_UPD.pgc new file mode 100644 index 0000000..ea748ca --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_UPD.pgc @@ -0,0 +1,32 @@ +/* + * MM_MERCH_UPD.pgc - mm 모듈 서비스 MM_MERCH_UPD (one service = one file). + * 기능: 가맹점명/계좌 수정 (직접 UPDATE + 이력). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_MERCH_UPD.h" + +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); +} diff --git a/app/src/mm/svc/MM_STATUS.h b/app/src/mm/svc/MM_STATUS.h new file mode 100644 index 0000000..216c5ea --- /dev/null +++ b/app/src/mm/svc/MM_STATUS.h @@ -0,0 +1,12 @@ +/* + * MM_STATUS.h - mm 서비스 MM_STATUS 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_MSG, T_RC + */ +#ifndef MM_STATUS_H +#define MM_STATUS_H + +#include "mm_svc.h" + +void MM_STATUS(TPSVCINFO *p); + +#endif /* MM_STATUS_H */ diff --git a/app/src/mm/svc/MM_STATUS.pgc b/app/src/mm/svc/MM_STATUS.pgc new file mode 100644 index 0000000..69c8100 --- /dev/null +++ b/app/src/mm/svc/MM_STATUS.pgc @@ -0,0 +1,21 @@ +/* + * MM_STATUS.pgc - mm 모듈 서비스 MM_STATUS (one service = one file). + * 기능: 서비스 상태 (전체 가맹점 수, RC=0). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_STATUS.h" + +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); +} diff --git a/app/src/mm/svc/MM_TERMINAL_INQ.h b/app/src/mm/svc/MM_TERMINAL_INQ.h new file mode 100644 index 0000000..7b20815 --- /dev/null +++ b/app/src/mm/svc/MM_TERMINAL_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_TERMINAL_INQ.h - mm 서비스 MM_TERMINAL_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS, T_STR5 + */ +#ifndef MM_TERMINAL_INQ_H +#define MM_TERMINAL_INQ_H + +#include "mm_svc.h" + +void MM_TERMINAL_INQ(TPSVCINFO *p); + +#endif /* MM_TERMINAL_INQ_H */ diff --git a/app/src/mm/svc/MM_TERMINAL_INQ.pgc b/app/src/mm/svc/MM_TERMINAL_INQ.pgc new file mode 100644 index 0000000..ddaeeb8 --- /dev/null +++ b/app/src/mm/svc/MM_TERMINAL_INQ.pgc @@ -0,0 +1,24 @@ +/* + * MM_TERMINAL_INQ.pgc - mm 모듈 서비스 MM_TERMINAL_INQ (one service = one file). + * 기능: 단말 조회. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_TERMINAL_INQ.h" + +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); +} diff --git a/app/src/mm/svc/MM_TERMINAL_REG.h b/app/src/mm/svc/MM_TERMINAL_REG.h new file mode 100644 index 0000000..f02aa70 --- /dev/null +++ b/app/src/mm/svc/MM_TERMINAL_REG.h @@ -0,0 +1,12 @@ +/* + * MM_TERMINAL_REG.h - mm 서비스 MM_TERMINAL_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR5, T_STR6 + */ +#ifndef MM_TERMINAL_REG_H +#define MM_TERMINAL_REG_H + +#include "mm_svc.h" + +void MM_TERMINAL_REG(TPSVCINFO *p); + +#endif /* MM_TERMINAL_REG_H */ diff --git a/app/src/mm/svc/MM_TERMINAL_REG.pgc b/app/src/mm/svc/MM_TERMINAL_REG.pgc new file mode 100644 index 0000000..cee1ded --- /dev/null +++ b/app/src/mm/svc/MM_TERMINAL_REG.pgc @@ -0,0 +1,29 @@ +/* + * MM_TERMINAL_REG.pgc - mm 모듈 서비스 MM_TERMINAL_REG (one service = one file). + * 기능: 단말 마스터 등록. + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_TERMINAL_REG.h" + +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); +} diff --git a/app/src/mm/svc/MM_VALIDATE.h b/app/src/mm/svc/MM_VALIDATE.h new file mode 100644 index 0000000..a877643 --- /dev/null +++ b/app/src/mm/svc/MM_VALIDATE.h @@ -0,0 +1,12 @@ +/* + * MM_VALIDATE.h - mm 서비스 MM_VALIDATE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_RC + */ +#ifndef MM_VALIDATE_H +#define MM_VALIDATE_H + +#include "mm_svc.h" + +void MM_VALIDATE(TPSVCINFO *p); + +#endif /* MM_VALIDATE_H */ diff --git a/app/src/mm/svc/MM_VALIDATE.pgc b/app/src/mm/svc/MM_VALIDATE.pgc new file mode 100644 index 0000000..7908415 --- /dev/null +++ b/app/src/mm/svc/MM_VALIDATE.pgc @@ -0,0 +1,29 @@ +/* + * MM_VALIDATE.pgc - mm 모듈 서비스 MM_VALIDATE (one service = one file). + * 기능: 가맹점 마스터 정합성 검사 (존재+수수료율 존재). + * 기존 인라인 mm_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * mm_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "MM_VALIDATE.h" + +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); +} diff --git a/app/src/mm/svc/mm_svc.h b/app/src/mm/svc/mm_svc.h new file mode 100644 index 0000000..7b45da2 --- /dev/null +++ b/app/src/mm/svc/mm_svc.h @@ -0,0 +1,33 @@ +/* + * mm_svc.h - mm (마스터/master) 서비스 공통 카피북 (shared copybook for all mm svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the mm dbio layer (libmmdbio.a) and + * the common math (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros every service uses. No business logic and no service function lives here. + */ +#ifndef MM_SVC_H +#define MM_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "mm_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* MM_SVC_H */ diff --git a/app/src/py/batch/py_account_batch.pgc b/app/src/py/batch/py_account_batch.pgc new file mode 100644 index 0000000..72fb0a9 --- /dev/null +++ b/app/src/py/batch/py_account_batch.pgc @@ -0,0 +1,34 @@ +/* + * py_account_batch.pgc - py 계좌 현황 집계 (dbio 전용 배치, XA). + * + * Uses only the dbio layer (no direct EXEC SQL here) to read the active-account + * balance total and count under ONE XA transaction. Usage: py_account_batch + */ +#include +#include +#include +#include +#include "py_dbio.h" +#include "py_account_agg_dbio.h" + +int main(int argc, char **argv) +{ + long total, cnt; + + (void)argc; (void)argv; + + 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; } + + total = pydb_account_active_balance(); + if (total < 0) { fprintf(stderr, "py_account_batch balance FAIL\n"); tpabort(0); return 1; } + cnt = pydb_account_active_count(); + if (cnt < 0) { fprintf(stderr, "py_account_batch count FAIL\n"); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_account_batch COMMIT: active_accounts=%ld total_balance=%ld\n", cnt, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_adjust_apply_batch.pgc b/app/src/py/batch/py_adjust_apply_batch.pgc new file mode 100644 index 0000000..48a43d3 --- /dev/null +++ b/app/src/py/batch/py_adjust_apply_batch.pgc @@ -0,0 +1,50 @@ +/* + * py_adjust_apply_batch.pgc - py 조정 반영 (커서 + inline UPDATE, XA). + * + * Cursors the day's adjustments and applies each delta to the linked payout's + * net with an inline UPDATE, under ONE XA transaction. + * Usage: py_adjust_apply_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + long h_pay, h_delta; + 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 pyaac CURSOR FOR + SELECT payout_id, delta FROM py_adjust WHERE biz_date = :h_bizdate ORDER BY adj_id; + EXEC SQL OPEN pyaac; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pyaac INTO :h_pay, :h_delta; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE pyaac; tpabort(0); return 1; } + EXEC SQL UPDATE py_payout SET net = net + :h_delta, updated_at = now() WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pyaac; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE pyaac; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_adjust_apply_batch COMMIT: bizdate=%s applied=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_close_batch.pgc b/app/src/py/batch/py_close_batch.pgc new file mode 100644 index 0000000..bb63684 --- /dev/null +++ b/app/src/py/batch/py_close_batch.pgc @@ -0,0 +1,38 @@ +/* + * py_close_batch.pgc - py 일마감 (단건 UPDATE 배치, XA). + * + * Flips the day's paid(P) payouts to closed(X) with ONE guarded UPDATE and + * reports the affected count via sqlca.sqlerrd[2], all under ONE XA transaction. + * Usage: py_close_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_aff; + 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 UPDATE py_payout SET status = 'X' WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_close_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_aff = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_close_batch COMMIT: bizdate=%s closed(P->X)=%ld\n", bizdate, h_aff); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_daily_batch.pgc b/app/src/py/batch/py_daily_batch.pgc new file mode 100644 index 0000000..a4a567b --- /dev/null +++ b/app/src/py/batch/py_daily_batch.pgc @@ -0,0 +1,37 @@ +/* + * py_daily_batch.pgc - py 일일 지급 총계 (단건 집계 배치, XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, an EXEC SQL aggregate runs on the biz_date, then tpcommit drives + * XA 2PC. Usage: py_daily_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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 SELECT count(*), coalesce(sum(amount),0), coalesce(sum(fee),0), coalesce(sum(net),0) INTO :h_cnt, :h_gross, :h_fee, :h_net FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_daily_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_daily_batch COMMIT: bizdate=%s payouts=%ld gross=%ld fee=%ld net=%ld\n", bizdate, h_cnt, h_gross, h_fee, h_net); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_fee_recalc_batch.pgc b/app/src/py/batch/py_fee_recalc_batch.pgc new file mode 100644 index 0000000..0e4ffcd --- /dev/null +++ b/app/src/py/batch/py_fee_recalc_batch.pgc @@ -0,0 +1,55 @@ +/* + * py_fee_recalc_batch.pgc - py 수수료 재계산 (커서 + 계산 + inline UPDATE, XA). + * + * Cursors the day's requested(R) payouts, recomputes fee (30bps) and net in C, + * and writes them back with an inline UPDATE, under ONE XA transaction. + * Usage: py_fee_recalc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rows = 0, fee, net; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pay, h_amt, 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 pyfrc CURSOR FOR + SELECT payout_id, amount FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'R' + ORDER BY payout_id; + EXEC SQL OPEN pyfrc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pyfrc INTO :h_pay, :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 pyfrc; tpabort(0); return 1; } + fee = h_amt * 30 / 10000; + net = h_amt - 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) { EXEC SQL CLOSE pyfrc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE pyfrc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_fee_recalc_batch COMMIT: bizdate=%s recalced=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_file_confirm_batch.pgc b/app/src/py/batch/py_file_confirm_batch.pgc new file mode 100644 index 0000000..eb89570 --- /dev/null +++ b/app/src/py/batch/py_file_confirm_batch.pgc @@ -0,0 +1,39 @@ +/* + * py_file_confirm_batch.pgc - py 지급파일 확정 (조회 후 UPDATE 배치, XA). + * + * Counts the day's generated(G) files, then flips them to confirmed(C) stamping + * confirmed_at, under ONE XA transaction. Usage: py_file_confirm_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_aff; + 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 SELECT count(*) INTO :h_cnt FROM py_file WHERE biz_date = :h_bizdate AND status = 'G'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL UPDATE py_file SET status = 'C', confirmed_at = now() WHERE biz_date = :h_bizdate AND status = 'G'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_file_confirm_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_aff = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_file_confirm_batch COMMIT: bizdate=%s generated=%ld confirmed=%ld\n", bizdate, h_cnt, h_aff); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_ledger_batch.pgc b/app/src/py/batch/py_ledger_batch.pgc new file mode 100644 index 0000000..da33a97 --- /dev/null +++ b/app/src/py/batch/py_ledger_batch.pgc @@ -0,0 +1,39 @@ +/* + * py_ledger_batch.pgc - py 지급 원장 생성 (INSERT..SELECT 배치, XA). + * + * Bulk-inserts one XFER ledger row per paid(P) payout of the biz_date using a + * single INSERT ... SELECT, under ONE XA transaction. Usage: py_ledger_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rows; + 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 INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + SELECT nextval('py_ledger_seq'), payout_id, 'XFER', net, biz_date + FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_ledger_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_ledger_batch COMMIT: bizdate=%s xfer_rows=%ld\n", bizdate, h_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_merchsum_batch.pgc b/app/src/py/batch/py_merchsum_batch.pgc new file mode 100644 index 0000000..80b9608 --- /dev/null +++ b/app/src/py/batch/py_merchsum_batch.pgc @@ -0,0 +1,50 @@ +/* + * py_merchsum_batch.pgc - py 가맹점 지급 집계 (커서 + dbio upsert 배치, XA). + * + * Cursors the day's paid(P) payouts and rolls a per-merchant summary via the + * dbio upsert, under ONE XA transaction. Usage: py_merchsum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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 pymsc CURSOR FOR + SELECT payout_id, merchant_id, net FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'P' + ORDER BY payout_id; + EXEC SQL OPEN pymsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pymsc 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 pymsc; tpabort(0); return 1; } + if (pydb_upsert_summary(h_merch, bizdate, 1, h_net) < 0) { EXEC SQL CLOSE pymsc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE pymsc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_merchsum_batch COMMIT: bizdate=%s rolled=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_netoff_batch.pgc b/app/src/py/batch/py_netoff_batch.pgc new file mode 100644 index 0000000..9dac4d3 --- /dev/null +++ b/app/src/py/batch/py_netoff_batch.pgc @@ -0,0 +1,40 @@ +/* + * py_netoff_batch.pgc - py 일괄 상계 (단건 산술 UPDATE 배치, XA). + * + * Applies a flat offset amount to the net of every requested(R) payout of the + * biz_date with a single arithmetic UPDATE, under ONE XA transaction. + * Usage: py_netoff_batch [YYYY-MM-DD] [OFFSET] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_off, h_aff; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_off = (argc > 2) ? atol(argv[2]) : 100; + + 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 UPDATE py_payout SET net = net - :h_off, updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_netoff_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_aff = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_netoff_batch COMMIT: bizdate=%s offset=%ld affected=%ld\n", bizdate, h_off, h_aff); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_reject_report_batch.pgc b/app/src/py/batch/py_reject_report_batch.pgc new file mode 100644 index 0000000..ca79f06 --- /dev/null +++ b/app/src/py/batch/py_reject_report_batch.pgc @@ -0,0 +1,50 @@ +/* + * py_reject_report_batch.pgc - py 반려 현황 리포트 (읽기 전용 커서 누적 배치, XA). + * + * Cursors the day's rejected(J) payouts, accumulating count and amount in C + * (no writes), under ONE XA transaction. Usage: py_reject_report_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rows = 0, total = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pay, 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 pyjrc CURSOR FOR + SELECT payout_id, amount FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'J' + ORDER BY payout_id; + EXEC SQL OPEN pyjrc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pyjrc INTO :h_pay, :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 pyjrc; tpabort(0); return 1; } + total += h_amt; + rows++; + } + EXEC SQL CLOSE pyjrc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_reject_report_batch COMMIT: bizdate=%s rejects=%ld amount=%ld\n", bizdate, rows, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_retry_batch.pgc b/app/src/py/batch/py_retry_batch.pgc new file mode 100644 index 0000000..f566857 --- /dev/null +++ b/app/src/py/batch/py_retry_batch.pgc @@ -0,0 +1,39 @@ +/* + * py_retry_batch.pgc - py 실패 지급 재시도 (단건 UPDATE 배치, XA). + * + * One guarded UPDATE flips the day's failed/held(J,H) payouts back to requested(R) + * and bumps retry_count, reporting the affected count via sqlca.sqlerrd[2]. + * Usage: py_retry_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_aff; + 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 UPDATE py_payout SET status = 'R', retry_count = retry_count + 1, updated_at = now() + WHERE biz_date = :h_bizdate AND status IN ('J', 'H'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_retry_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_aff = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_retry_batch COMMIT: bizdate=%s requeued=%ld\n", bizdate, h_aff); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_schedule_run_batch.pgc b/app/src/py/batch/py_schedule_run_batch.pgc new file mode 100644 index 0000000..9288271 --- /dev/null +++ b/app/src/py/batch/py_schedule_run_batch.pgc @@ -0,0 +1,53 @@ +/* + * py_schedule_run_batch.pgc - py 예약 지급 실행 (커서 + dbio + inline UPDATE, XA). + * + * Cursors the day's pending(S) schedules; per row it flips the linked payout to + * requested(R) via dbio and marks the schedule done(D) inline, under ONE XA + * transaction. Usage: py_schedule_run_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *rundate = (argc > 1) ? argv[1] : "2026-07-19"; + long rows = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16]; + long h_sched, h_pay; + EXEC SQL END DECLARE SECTION; + + strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-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 pyrsc CURSOR FOR + SELECT schedule_id, payout_id FROM py_schedule + WHERE run_date = :h_rundate AND status = 'S' + ORDER BY schedule_id; + EXEC SQL OPEN pyrsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pyrsc INTO :h_sched, :h_pay; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE pyrsc; tpabort(0); return 1; } + if (pydb_update_status(h_pay, "R") < 0) { EXEC SQL CLOSE pyrsc; tpabort(0); return 1; } + EXEC SQL UPDATE py_schedule SET status = 'D' WHERE schedule_id = :h_sched; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pyrsc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE pyrsc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_schedule_run_batch COMMIT: rundate=%s executed=%ld\n", rundate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_settle_batch.pgc b/app/src/py/batch/py_settle_batch.pgc new file mode 100644 index 0000000..8dd9a9d --- /dev/null +++ b/app/src/py/batch/py_settle_batch.pgc @@ -0,0 +1,37 @@ +/* + * py_settle_batch.pgc - py 지급완료 정산 집계 (단건 집계 배치, XA). + * + * ATMI client: opens the ECPG XA RM, starts ONE global transaction, aggregates + * paid(P) payouts for the biz_date, then tpcommit drives XA 2PC. + * Usage: py_settle_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, 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 SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_net FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_settle_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_settle_batch COMMIT: bizdate=%s paid=%ld net=%ld\n", bizdate, h_cnt, h_net); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_stat_batch.pgc b/app/src/py/batch/py_stat_batch.pgc new file mode 100644 index 0000000..6206195 --- /dev/null +++ b/app/src/py/batch/py_stat_batch.pgc @@ -0,0 +1,40 @@ +/* + * py_stat_batch.pgc - py 집중 가맹점 통계 (GROUP BY HAVING 서브쿼리 배치, XA). + * + * Counts merchants whose daily payout amount clears a threshold using a single + * GROUP BY ... HAVING subquery, under ONE XA transaction. + * Usage: py_stat_batch [YYYY-MM-DD] [THRESHOLD] + */ +#include +#include +#include +#include +#include "py_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr, h_merchants; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_thr = (argc > 2) ? atol(argv[2]) : 1000000; + + 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(*) INTO :h_merchants FROM ( + SELECT merchant_id FROM py_payout WHERE biz_date = :h_bizdate + GROUP BY merchant_id HAVING coalesce(sum(net),0) >= :h_thr) t; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_stat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_stat_batch COMMIT: bizdate=%s threshold=%ld top_merchants=%ld\n", bizdate, h_thr, h_merchants); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_summary_reset_batch.pgc b/app/src/py/batch/py_summary_reset_batch.pgc new file mode 100644 index 0000000..7e73b91 --- /dev/null +++ b/app/src/py/batch/py_summary_reset_batch.pgc @@ -0,0 +1,49 @@ +/* + * py_summary_reset_batch.pgc - py 집계 리셋 (커서 DISTINCT + dbio upsert, XA). + * + * Cursors the DISTINCT merchants that traded on the biz_date and zeroes each + * merchant's daily summary via the dbio ON CONFLICT reset, under ONE XA + * transaction. Usage: py_summary_reset_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_dbio.h" +#include "py_summary_upsert_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]; + 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 pysrc CURSOR FOR + SELECT DISTINCT merchant_id FROM py_payout WHERE biz_date = :h_bizdate; + EXEC SQL OPEN pysrc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pysrc INTO :h_merch; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE pysrc; tpabort(0); return 1; } + if (pydb_summary_reset(h_merch, bizdate) < 0) { EXEC SQL CLOSE pysrc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE pysrc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_summary_reset_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/dbio/py_account_agg_dbio.h b/app/src/py/dbio/py_account_agg_dbio.h new file mode 100644 index 0000000..34c2000 --- /dev/null +++ b/app/src/py/dbio/py_account_agg_dbio.h @@ -0,0 +1,14 @@ +/* + * py_account_agg_dbio.h - py 모듈 DB 접근 계층 copybook (py_account_agg_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_ACCOUNT_AGG_DBIO_H +#define PY_ACCOUNT_AGG_DBIO_H + +typedef struct { long total_balance; long active_cnt; } py_account_agg_rec_t; + +long pydb_account_active_balance(void); +long pydb_account_active_count(void); + +#endif /* PY_ACCOUNT_AGG_DBIO_H */ diff --git a/app/src/py/dbio/py_account_agg_dbio.pgc b/app/src/py/dbio/py_account_agg_dbio.pgc new file mode 100644 index 0000000..a52a2c8 --- /dev/null +++ b/app/src/py/dbio/py_account_agg_dbio.pgc @@ -0,0 +1,29 @@ +/* + * py_account_agg_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_account_agg_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_account_agg_dbio.h" + +/* ACTIVE 계좌 잔액 총합 (무인자 집계). */ +long pydb_account_active_balance(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(balance),0) INTO :h_v FROM py_account WHERE status = 'ACTIVE'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* ACTIVE 계좌 수 (무인자 집계). */ +long pydb_account_active_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_v FROM py_account WHERE status = 'ACTIVE'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_account_minmax_dbio.h b/app/src/py/dbio/py_account_minmax_dbio.h new file mode 100644 index 0000000..2ef1ad8 --- /dev/null +++ b/app/src/py/dbio/py_account_minmax_dbio.h @@ -0,0 +1,14 @@ +/* + * py_account_minmax_dbio.h - py 모듈 DB 접근 계층 copybook (py_account_minmax_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_ACCOUNT_MINMAX_DBIO_H +#define PY_ACCOUNT_MINMAX_DBIO_H + +typedef struct { long threshold; long top_balance; long low_cnt; } py_account_minmax_rec_t; + +long pydb_account_top_balance(void); +long pydb_account_low_count(long threshold); + +#endif /* PY_ACCOUNT_MINMAX_DBIO_H */ diff --git a/app/src/py/dbio/py_account_minmax_dbio.pgc b/app/src/py/dbio/py_account_minmax_dbio.pgc new file mode 100644 index 0000000..d6388be --- /dev/null +++ b/app/src/py/dbio/py_account_minmax_dbio.pgc @@ -0,0 +1,29 @@ +/* + * py_account_minmax_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_account_minmax_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_account_minmax_dbio.h" + +/* 최대 잔액 계좌의 잔액 (무인자). */ +long pydb_account_top_balance(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(max(balance),0) INTO :h_v FROM py_account; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 임계 미만 잔액 계좌 수 (threshold 인자). */ +long pydb_account_low_count(long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_thr = threshold, h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_v FROM py_account WHERE balance < :h_thr; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_adjust_dbio.h b/app/src/py/dbio/py_adjust_dbio.h new file mode 100644 index 0000000..f6de2a2 --- /dev/null +++ b/app/src/py/dbio/py_adjust_dbio.h @@ -0,0 +1,14 @@ +/* + * py_adjust_dbio.h - py 모듈 DB 접근 계층 copybook (py_adjust_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_ADJUST_DBIO_H +#define PY_ADJUST_DBIO_H + +typedef struct { long payout_id; char biz_date[16]; long delta_sum; long cnt; } py_adjust_rec_t; + +long pydb_adjust_delta_sum(long payout_id); +long pydb_adjust_count(const char *bizdate); + +#endif /* PY_ADJUST_DBIO_H */ diff --git a/app/src/py/dbio/py_adjust_dbio.pgc b/app/src/py/dbio/py_adjust_dbio.pgc new file mode 100644 index 0000000..22672a1 --- /dev/null +++ b/app/src/py/dbio/py_adjust_dbio.pgc @@ -0,0 +1,31 @@ +/* + * py_adjust_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_adjust_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_adjust_dbio.h" + +/* 단일 지급건 조정 증감 누계 (payout_id 스칼라 키). */ +long pydb_adjust_delta_sum(long payout_id) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pay = payout_id, h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(delta),0) INTO :h_v FROM py_adjust WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 당일 조정 건수. */ +long pydb_adjust_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM py_adjust WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_adjust_reason_dbio.h b/app/src/py/dbio/py_adjust_reason_dbio.h new file mode 100644 index 0000000..da89b67 --- /dev/null +++ b/app/src/py/dbio/py_adjust_reason_dbio.h @@ -0,0 +1,13 @@ +/* + * py_adjust_reason_dbio.h - py 모듈 DB 접근 계층 copybook (py_adjust_reason_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_ADJUST_REASON_DBIO_H +#define PY_ADJUST_REASON_DBIO_H + +typedef struct { char biz_date[16]; long reason_kinds; } py_adjust_reason_rec_t; + +long pydb_adjust_reason_variety(const char *bizdate); + +#endif /* PY_ADJUST_REASON_DBIO_H */ diff --git a/app/src/py/dbio/py_adjust_reason_dbio.pgc b/app/src/py/dbio/py_adjust_reason_dbio.pgc new file mode 100644 index 0000000..cd065b9 --- /dev/null +++ b/app/src/py/dbio/py_adjust_reason_dbio.pgc @@ -0,0 +1,20 @@ +/* + * py_adjust_reason_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_adjust_reason_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_adjust_reason_dbio.h" + +/* 당일 조정 사유 가짓수 (count DISTINCT reason). */ +long pydb_adjust_reason_variety(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT reason) INTO :h_v FROM py_adjust WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_adjust_reason_variety FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_file_agg_dbio.h b/app/src/py/dbio/py_file_agg_dbio.h new file mode 100644 index 0000000..5cbce1a --- /dev/null +++ b/app/src/py/dbio/py_file_agg_dbio.h @@ -0,0 +1,14 @@ +/* + * py_file_agg_dbio.h - py 모듈 DB 접근 계층 copybook (py_file_agg_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_FILE_AGG_DBIO_H +#define PY_FILE_AGG_DBIO_H + +typedef struct { char biz_date[16]; long gen_cnt; long rec_sum; } py_file_agg_rec_t; + +long pydb_file_generated_count(const char *bizdate); +long pydb_file_record_sum(const char *bizdate); + +#endif /* PY_FILE_AGG_DBIO_H */ diff --git a/app/src/py/dbio/py_file_agg_dbio.pgc b/app/src/py/dbio/py_file_agg_dbio.pgc new file mode 100644 index 0000000..e2c8c43 --- /dev/null +++ b/app/src/py/dbio/py_file_agg_dbio.pgc @@ -0,0 +1,33 @@ +/* + * py_file_agg_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_file_agg_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_file_agg_dbio.h" + +/* 당일 생성(G) 상태 파일 건수 (2조건 필터 count). */ +long pydb_file_generated_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM py_file WHERE biz_date = :h_bizdate AND status = 'G'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 당일 파일 레코드 수 합계. */ +long pydb_file_record_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(record_count),0) INTO :h_v FROM py_file WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_file_dbio.h b/app/src/py/dbio/py_file_dbio.h new file mode 100644 index 0000000..111a002 --- /dev/null +++ b/app/src/py/dbio/py_file_dbio.h @@ -0,0 +1,14 @@ +/* + * py_file_dbio.h - py 모듈 DB 접근 계층 copybook (py_file_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_FILE_DBIO_H +#define PY_FILE_DBIO_H + +typedef struct { long file_id; long total_amount; char status[8]; } py_file_rec_t; + +long pydb_file_total(long file_id); +int pydb_file_status(long file_id, char *out /* >= 2 bytes */); + +#endif /* PY_FILE_DBIO_H */ diff --git a/app/src/py/dbio/py_file_dbio.pgc b/app/src/py/dbio/py_file_dbio.pgc new file mode 100644 index 0000000..269daa7 --- /dev/null +++ b/app/src/py/dbio/py_file_dbio.pgc @@ -0,0 +1,32 @@ +/* + * py_file_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_file_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_file_dbio.h" + +/* 지급 파일 총액 (단순 스칼라). */ +long pydb_file_total(long file_id) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_file = file_id, h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(total_amount,0) INTO :h_v FROM py_file WHERE file_id = :h_file; + if (sqlca.sqlcode < 0) { userlog("pydb_file_total FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} + +/* 지급 파일 상태 문자 회신. */ +int pydb_file_status(long file_id, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_file = file_id; + char h_status[8]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT status INTO :h_status FROM py_file WHERE file_id = :h_file; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("pydb_file_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + out[0] = h_status[0]; + out[1] = 0; + return 0; +} diff --git a/app/src/py/dbio/py_ledger_dbio.h b/app/src/py/dbio/py_ledger_dbio.h new file mode 100644 index 0000000..484946e --- /dev/null +++ b/app/src/py/dbio/py_ledger_dbio.h @@ -0,0 +1,14 @@ +/* + * py_ledger_dbio.h - py 모듈 DB 접근 계층 copybook (py_ledger_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_LEDGER_DBIO_H +#define PY_LEDGER_DBIO_H + +typedef struct { long ledger_id; long payout_id; char entry_type[16]; long amount; } py_ledger_rec_t; + +long pydb_ledger_xfer_sum(const char *bizdate); +long pydb_ledger_balance(long payout_id); + +#endif /* PY_LEDGER_DBIO_H */ diff --git a/app/src/py/dbio/py_ledger_dbio.pgc b/app/src/py/dbio/py_ledger_dbio.pgc new file mode 100644 index 0000000..fefaf9a --- /dev/null +++ b/app/src/py/dbio/py_ledger_dbio.pgc @@ -0,0 +1,45 @@ +/* + * py_ledger_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_ledger_dbio.h" + +/* XFER/ADJ 엔트리를 FILTER 로 한 번에 분리 집계하고 XFER 합 반환. */ +long pydb_ledger_xfer_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_xfer = 0, h_adj = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(amount) FILTER (WHERE entry_type = 'XFER'), 0), + coalesce(sum(amount) FILTER (WHERE entry_type = 'ADJ'), 0) + INTO :h_xfer, :h_adj FROM py_ledger WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_adj < 0) return -1; + return h_xfer; +} + +/* 단일 지급 원장 잔액 = XFER(-) 그 외(+), 커서 부호 누적. */ +long pydb_ledger_balance(long payout_id) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pay = payout_id, h_amt, bal = 0; + char h_type[16]; + EXEC SQL END DECLARE SECTION; + EXEC SQL DECLARE plbc CURSOR FOR + SELECT entry_type, amount FROM py_ledger WHERE payout_id = :h_pay; + EXEC SQL OPEN plbc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH plbc INTO :h_type, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE plbc; return -1; } + bal += (h_type[0] == 'X') ? -h_amt : h_amt; + } + EXEC SQL CLOSE plbc; + return bal; +} diff --git a/app/src/py/dbio/py_ledger_type_dbio.h b/app/src/py/dbio/py_ledger_type_dbio.h new file mode 100644 index 0000000..9f6b1bd --- /dev/null +++ b/app/src/py/dbio/py_ledger_type_dbio.h @@ -0,0 +1,13 @@ +/* + * py_ledger_type_dbio.h - py 모듈 DB 접근 계층 copybook (py_ledger_type_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_LEDGER_TYPE_DBIO_H +#define PY_LEDGER_TYPE_DBIO_H + +typedef struct { char entry_type[16]; char biz_date[16]; long total; } py_ledger_type_rec_t; + +long pydb_ledger_type_total(const char *entry_type, const char *bizdate); + +#endif /* PY_LEDGER_TYPE_DBIO_H */ diff --git a/app/src/py/dbio/py_ledger_type_dbio.pgc b/app/src/py/dbio/py_ledger_type_dbio.pgc new file mode 100644 index 0000000..bc68726 --- /dev/null +++ b/app/src/py/dbio/py_ledger_type_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_ledger_type_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_type_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_ledger_type_dbio.h" + +/* 지정 엔트리 유형의 당일 원장 합계 (entry_type 인자). */ +long pydb_ledger_type_total(const char *entry_type, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16], h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, entry_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 coalesce(sum(amount),0) INTO :h_v FROM py_ledger + WHERE entry_type = :h_type AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_ledger_type_total FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_payout_agg_dbio.h b/app/src/py/dbio/py_payout_agg_dbio.h new file mode 100644 index 0000000..25c4111 --- /dev/null +++ b/app/src/py/dbio/py_payout_agg_dbio.h @@ -0,0 +1,14 @@ +/* + * py_payout_agg_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_agg_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_AGG_DBIO_H +#define PY_PAYOUT_AGG_DBIO_H + +typedef struct { char biz_date[16]; long gross; long cnt; } py_payout_agg_rec_t; + +long pydb_payout_amount_sum(const char *bizdate); +long pydb_payout_txn_count(const char *bizdate); + +#endif /* PY_PAYOUT_AGG_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_agg_dbio.pgc b/app/src/py/dbio/py_payout_agg_dbio.pgc new file mode 100644 index 0000000..806985c --- /dev/null +++ b/app/src/py/dbio/py_payout_agg_dbio.pgc @@ -0,0 +1,33 @@ +/* + * py_payout_agg_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_agg_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_agg_dbio.h" + +/* 당일 지급 총액. */ +long pydb_payout_amount_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_amount_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} + +/* 당일 지급 건수. */ +long pydb_payout_txn_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_txn_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_payout_avg_dbio.h b/app/src/py/dbio/py_payout_avg_dbio.h new file mode 100644 index 0000000..c5ace23 --- /dev/null +++ b/app/src/py/dbio/py_payout_avg_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_avg_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_avg_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_AVG_DBIO_H +#define PY_PAYOUT_AVG_DBIO_H + +typedef struct { char biz_date[16]; long total; long cnt; long avg; } py_payout_avg_rec_t; + +long pydb_payout_avg_amount(const char *bizdate); + +#endif /* PY_PAYOUT_AVG_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_avg_dbio.pgc b/app/src/py/dbio/py_payout_avg_dbio.pgc new file mode 100644 index 0000000..d1655c1 --- /dev/null +++ b/app/src/py/dbio/py_payout_avg_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_payout_avg_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_avg_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_avg_dbio.h" + +/* 건당 평균 지급액 = sum(amount)/count(*) (0 나눗셈 가드). */ +long pydb_payout_avg_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_sum, :h_cnt + FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_cnt <= 0) return 0; + return h_sum / h_cnt; +} diff --git a/app/src/py/dbio/py_payout_case_dbio.h b/app/src/py/dbio/py_payout_case_dbio.h new file mode 100644 index 0000000..19ee681 --- /dev/null +++ b/app/src/py/dbio/py_payout_case_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_case_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_case_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_CASE_DBIO_H +#define PY_PAYOUT_CASE_DBIO_H + +typedef struct { char biz_date[16]; long threshold; long large_cnt; } py_payout_case_rec_t; + +long pydb_payout_large_count(const char *bizdate, long threshold); + +#endif /* PY_PAYOUT_CASE_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_case_dbio.pgc b/app/src/py/dbio/py_payout_case_dbio.pgc new file mode 100644 index 0000000..2ae3f2e --- /dev/null +++ b/app/src/py/dbio/py_payout_case_dbio.pgc @@ -0,0 +1,21 @@ +/* + * py_payout_case_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_case_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_case_dbio.h" + +/* CASE 버킷 집계: 임계금액 이상 지급건 수를 sum(CASE ...) 로 산출. */ +long pydb_payout_large_count(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr = threshold, h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE WHEN amount >= :h_thr THEN 1 ELSE 0 END),0) INTO :h_v + FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_large_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_payout_channel_dbio.h b/app/src/py/dbio/py_payout_channel_dbio.h new file mode 100644 index 0000000..48762ef --- /dev/null +++ b/app/src/py/dbio/py_payout_channel_dbio.h @@ -0,0 +1,14 @@ +/* + * py_payout_channel_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_channel_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_CHANNEL_DBIO_H +#define PY_PAYOUT_CHANNEL_DBIO_H + +typedef struct { char channel[16]; char biz_date[16]; long gross; } py_payout_channel_rec_t; + +long pydb_payout_channel_variety(const char *bizdate); +long pydb_payout_channel_gross(const char *channel, const char *bizdate); + +#endif /* PY_PAYOUT_CHANNEL_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_channel_dbio.pgc b/app/src/py/dbio/py_payout_channel_dbio.pgc new file mode 100644 index 0000000..d88b3c9 --- /dev/null +++ b/app/src/py/dbio/py_payout_channel_dbio.pgc @@ -0,0 +1,35 @@ +/* + * py_payout_channel_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_channel_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_channel_dbio.h" + +/* 당일 사용된 지급 채널 가짓수 (BULK/SPLIT/ADVANCE/...). */ +long pydb_payout_channel_variety(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT channel) INTO :h_v FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 지정 채널의 당일 지급 총액. */ +long pydb_payout_channel_gross(const char *channel, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_ch[16], h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_ch, channel, sizeof(h_ch)-1); h_ch[sizeof(h_ch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM py_payout + WHERE channel = :h_ch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_payout_join_dbio.h b/app/src/py/dbio/py_payout_join_dbio.h new file mode 100644 index 0000000..de03957 --- /dev/null +++ b/app/src/py/dbio/py_payout_join_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_join_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_join_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_JOIN_DBIO_H +#define PY_PAYOUT_JOIN_DBIO_H + +typedef struct { char biz_date[16]; long filed_net; } py_payout_join_rec_t; + +long pydb_payout_filed_net(const char *bizdate); + +#endif /* PY_PAYOUT_JOIN_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_join_dbio.pgc b/app/src/py/dbio/py_payout_join_dbio.pgc new file mode 100644 index 0000000..9636a1d --- /dev/null +++ b/app/src/py/dbio/py_payout_join_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_payout_join_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_join_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_join_dbio.h" + +/* 2-테이블 JOIN: 생성(G)된 파일에 편성된 지급건의 순액 합. */ +long pydb_payout_filed_net(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(pp.net),0) INTO :h_v + FROM py_payout pp JOIN py_file pf ON pp.file_id = pf.file_id + WHERE pf.biz_date = :h_bizdate AND pf.status = 'G'; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_filed_net FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_payout_merch_dbio.h b/app/src/py/dbio/py_payout_merch_dbio.h new file mode 100644 index 0000000..d48bf08 --- /dev/null +++ b/app/src/py/dbio/py_payout_merch_dbio.h @@ -0,0 +1,14 @@ +/* + * py_payout_merch_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_merch_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_MERCH_DBIO_H +#define PY_PAYOUT_MERCH_DBIO_H + +typedef struct { char merchant_id[64]; long cnt; long gross; } py_payout_merch_rec_t; + +long pydb_payout_merch_count(const char *merch); +long pydb_payout_merch_gross(const char *merch); + +#endif /* PY_PAYOUT_MERCH_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_merch_dbio.pgc b/app/src/py/dbio/py_payout_merch_dbio.pgc new file mode 100644 index 0000000..d121295 --- /dev/null +++ b/app/src/py/dbio/py_payout_merch_dbio.pgc @@ -0,0 +1,33 @@ +/* + * py_payout_merch_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_merch_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_merch_dbio.h" + +/* 가맹점 전체 지급 건수. */ +long pydb_payout_merch_count(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_v = 0; + 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_v FROM py_payout WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 가맹점 전체 지급 총액. */ +long pydb_payout_merch_gross(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM py_payout WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_payout_minmax_dbio.h b/app/src/py/dbio/py_payout_minmax_dbio.h new file mode 100644 index 0000000..ea8592e --- /dev/null +++ b/app/src/py/dbio/py_payout_minmax_dbio.h @@ -0,0 +1,14 @@ +/* + * py_payout_minmax_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_minmax_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_MINMAX_DBIO_H +#define PY_PAYOUT_MINMAX_DBIO_H + +typedef struct { char biz_date[16]; long peak; long floor_amt; } py_payout_minmax_rec_t; + +long pydb_payout_peak_amount(const char *bizdate); +long pydb_payout_floor_amount(const char *bizdate); + +#endif /* PY_PAYOUT_MINMAX_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_minmax_dbio.pgc b/app/src/py/dbio/py_payout_minmax_dbio.pgc new file mode 100644 index 0000000..3c33094 --- /dev/null +++ b/app/src/py/dbio/py_payout_minmax_dbio.pgc @@ -0,0 +1,33 @@ +/* + * py_payout_minmax_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_minmax_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_minmax_dbio.h" + +/* 당일 최대 지급액. */ +long pydb_payout_peak_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(amount),0) INTO :h_v FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 당일 최소 지급액. */ +long pydb_payout_floor_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(min(amount),0) INTO :h_v FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/py/dbio/py_payout_reject_dbio.h b/app/src/py/dbio/py_payout_reject_dbio.h new file mode 100644 index 0000000..a1a0d16 --- /dev/null +++ b/app/src/py/dbio/py_payout_reject_dbio.h @@ -0,0 +1,14 @@ +/* + * py_payout_reject_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_reject_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_REJECT_DBIO_H +#define PY_PAYOUT_REJECT_DBIO_H + +typedef struct { char biz_date[16]; long payout_id; long reject_cnt; char reason[128]; } py_payout_reject_rec_t; + +long pydb_payout_reject_count(const char *bizdate); +int pydb_payout_reject_reason(long payout_id, char *out /* >= 128 bytes */); + +#endif /* PY_PAYOUT_REJECT_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_reject_dbio.pgc b/app/src/py/dbio/py_payout_reject_dbio.pgc new file mode 100644 index 0000000..f4739f3 --- /dev/null +++ b/app/src/py/dbio/py_payout_reject_dbio.pgc @@ -0,0 +1,33 @@ +/* + * py_payout_reject_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_reject_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_reject_dbio.h" + +/* 당일 반려(J) 지급 건수. */ +long pydb_payout_reject_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM py_payout WHERE biz_date = :h_bizdate AND status = 'J'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 단건 반려 사유 문자열 회신. */ +int pydb_payout_reject_reason(long payout_id, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pay = payout_id; + char h_reason[128]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(reject_reason,'') INTO :h_reason FROM py_payout WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("pydb_payout_reject_reason FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strncpy(out, h_reason, 127); out[127] = 0; + return 0; +} diff --git a/app/src/py/dbio/py_payout_retry_dbio.h b/app/src/py/dbio/py_payout_retry_dbio.h new file mode 100644 index 0000000..d7f244a --- /dev/null +++ b/app/src/py/dbio/py_payout_retry_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_retry_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_retry_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_RETRY_DBIO_H +#define PY_PAYOUT_RETRY_DBIO_H + +typedef struct { char biz_date[16]; long retried; long failed; } py_payout_retry_rec_t; + +long pydb_payout_retry_load(const char *bizdate); + +#endif /* PY_PAYOUT_RETRY_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_retry_dbio.pgc b/app/src/py/dbio/py_payout_retry_dbio.pgc new file mode 100644 index 0000000..5b31dfc --- /dev/null +++ b/app/src/py/dbio/py_payout_retry_dbio.pgc @@ -0,0 +1,24 @@ +/* + * py_payout_retry_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_retry_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_retry_dbio.h" + +/* FILTER 집계로 실패(J)건과 재시도 누계를 한 번에 뽑아 재시도 합을 반환. */ +long pydb_payout_retry_load(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_retry = 0, h_fail = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(retry_count),0), + coalesce(count(*) FILTER (WHERE status = 'J'),0) + INTO :h_retry, :h_fail FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_fail < 0) return -1; + return h_retry; +} diff --git a/app/src/py/dbio/py_payout_scan_dbio.h b/app/src/py/dbio/py_payout_scan_dbio.h new file mode 100644 index 0000000..786086f --- /dev/null +++ b/app/src/py/dbio/py_payout_scan_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_scan_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_scan_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_SCAN_DBIO_H +#define PY_PAYOUT_SCAN_DBIO_H + +typedef struct { char biz_date[16]; char status[8]; long cnt; } py_payout_scan_rec_t; + +long pydb_payout_paid_count(const char *bizdate); + +#endif /* PY_PAYOUT_SCAN_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_scan_dbio.pgc b/app/src/py/dbio/py_payout_scan_dbio.pgc new file mode 100644 index 0000000..16fbc8f --- /dev/null +++ b/app/src/py/dbio/py_payout_scan_dbio.pgc @@ -0,0 +1,29 @@ +/* + * py_payout_scan_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_scan_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_scan_dbio.h" + +/* 상태별 건수를 커서로 순회하여 지급완료(P) 건수만 추출. */ +long pydb_payout_paid_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_st[8]; + long h_cnt, paid = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE pysc CURSOR FOR + SELECT status, count(*) FROM py_payout WHERE biz_date = :h_bizdate GROUP BY status; + EXEC SQL OPEN pysc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH pysc INTO :h_st, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pysc; return -1; } + if (h_st[0] == 'P') paid = h_cnt; + } + EXEC SQL CLOSE pysc; + return paid; +} diff --git a/app/src/py/dbio/py_payout_subq_dbio.h b/app/src/py/dbio/py_payout_subq_dbio.h new file mode 100644 index 0000000..f073908 --- /dev/null +++ b/app/src/py/dbio/py_payout_subq_dbio.h @@ -0,0 +1,13 @@ +/* + * py_payout_subq_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_subq_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_SUBQ_DBIO_H +#define PY_PAYOUT_SUBQ_DBIO_H + +typedef struct { char run_date[16]; long scheduled_sum; } py_payout_subq_rec_t; + +long pydb_payout_scheduled_sum(const char *rundate); + +#endif /* PY_PAYOUT_SUBQ_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_subq_dbio.pgc b/app/src/py/dbio/py_payout_subq_dbio.pgc new file mode 100644 index 0000000..741ae5b --- /dev/null +++ b/app/src/py/dbio/py_payout_subq_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_payout_subq_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_subq_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_subq_dbio.h" + +/* 서브쿼리: 해당 실행일에 예약(S)된 지급건들의 순액 합. */ +long pydb_payout_scheduled_sum(const char *rundate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_v FROM py_payout + WHERE payout_id IN (SELECT payout_id FROM py_schedule + WHERE run_date = :h_rundate AND status = 'S'); + if (sqlca.sqlcode < 0) { userlog("pydb_payout_scheduled_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_schedule_dbio.h b/app/src/py/dbio/py_schedule_dbio.h new file mode 100644 index 0000000..92b564c --- /dev/null +++ b/app/src/py/dbio/py_schedule_dbio.h @@ -0,0 +1,14 @@ +/* + * py_schedule_dbio.h - py 모듈 DB 접근 계층 copybook (py_schedule_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SCHEDULE_DBIO_H +#define PY_SCHEDULE_DBIO_H + +typedef struct { char run_date[16]; long pending; } py_schedule_rec_t; + +long pydb_schedule_pending_count(const char *rundate); +long pydb_schedule_next_id(void); + +#endif /* PY_SCHEDULE_DBIO_H */ diff --git a/app/src/py/dbio/py_schedule_dbio.pgc b/app/src/py/dbio/py_schedule_dbio.pgc new file mode 100644 index 0000000..14634b7 --- /dev/null +++ b/app/src/py/dbio/py_schedule_dbio.pgc @@ -0,0 +1,31 @@ +/* + * py_schedule_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_schedule_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_schedule_dbio.h" + +/* 지정 실행일의 대기(S) 예약 건수. */ +long pydb_schedule_pending_count(const char *rundate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM py_schedule WHERE run_date = :h_rundate AND status = 'S'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 예약 시퀀스 다음 값. */ +long pydb_schedule_next_id(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_id = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT nextval('py_schedule_seq') INTO :h_id; + if (sqlca.sqlcode < 0) { userlog("pydb_schedule_next_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_id; +} diff --git a/app/src/py/dbio/py_schedule_scan_dbio.h b/app/src/py/dbio/py_schedule_scan_dbio.h new file mode 100644 index 0000000..905be01 --- /dev/null +++ b/app/src/py/dbio/py_schedule_scan_dbio.h @@ -0,0 +1,13 @@ +/* + * py_schedule_scan_dbio.h - py 모듈 DB 접근 계층 copybook (py_schedule_scan_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SCHEDULE_SCAN_DBIO_H +#define PY_SCHEDULE_SCAN_DBIO_H + +typedef struct { char run_date[16]; long due; } py_schedule_scan_rec_t; + +long pydb_schedule_due_count(const char *rundate); + +#endif /* PY_SCHEDULE_SCAN_DBIO_H */ diff --git a/app/src/py/dbio/py_schedule_scan_dbio.pgc b/app/src/py/dbio/py_schedule_scan_dbio.pgc new file mode 100644 index 0000000..ebf793e --- /dev/null +++ b/app/src/py/dbio/py_schedule_scan_dbio.pgc @@ -0,0 +1,29 @@ +/* + * py_schedule_scan_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_schedule_scan_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_schedule_scan_dbio.h" + +/* 커서로 실행일 예약을 순회하며 대기(S) 상태 건만 집계. */ +long pydb_schedule_due_count(const char *rundate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16], h_st[8]; + long h_pay, due = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-1] = 0; + EXEC SQL DECLARE pydsc CURSOR FOR + SELECT payout_id, status FROM py_schedule WHERE run_date = :h_rundate; + EXEC SQL OPEN pydsc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH pydsc INTO :h_pay, :h_st; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pydsc; return -1; } + if (h_st[0] == 'S') due++; + } + EXEC SQL CLOSE pydsc; + return due; +} diff --git a/app/src/py/dbio/py_summary_dbio.h b/app/src/py/dbio/py_summary_dbio.h new file mode 100644 index 0000000..fdf24b1 --- /dev/null +++ b/app/src/py/dbio/py_summary_dbio.h @@ -0,0 +1,13 @@ +/* + * py_summary_dbio.h - py 모듈 DB 접근 계층 copybook (py_summary_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SUMMARY_DBIO_H +#define PY_SUMMARY_DBIO_H + +typedef struct { char biz_date[16]; long threshold; long merchants; } py_summary_rec_t; + +long pydb_summary_active_merchants(const char *bizdate, long threshold); + +#endif /* PY_SUMMARY_DBIO_H */ diff --git a/app/src/py/dbio/py_summary_dbio.pgc b/app/src/py/dbio/py_summary_dbio.pgc new file mode 100644 index 0000000..a1fbe20 --- /dev/null +++ b/app/src/py/dbio/py_summary_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_summary_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_summary_dbio.h" + +/* GROUP BY ... HAVING: 당일 지급총액이 임계 이상인 가맹점 수. */ +long pydb_summary_active_merchants(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr = threshold, h_v = 0; + 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_v FROM ( + SELECT merchant_id FROM py_summary WHERE biz_date = :h_bizdate + GROUP BY merchant_id HAVING sum(payout_amount) >= :h_thr) t; + if (sqlca.sqlcode < 0) { userlog("pydb_summary_active_merchants FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_summary_upsert_dbio.h b/app/src/py/dbio/py_summary_upsert_dbio.h new file mode 100644 index 0000000..217dec1 --- /dev/null +++ b/app/src/py/dbio/py_summary_upsert_dbio.h @@ -0,0 +1,13 @@ +/* + * py_summary_upsert_dbio.h - py 모듈 DB 접근 계층 copybook (py_summary_upsert_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: 0 ok, -1 = SQL error. + */ +#ifndef PY_SUMMARY_UPSERT_DBIO_H +#define PY_SUMMARY_UPSERT_DBIO_H + +typedef struct { char merchant_id[64]; char biz_date[16]; } py_summary_upsert_rec_t; + +int pydb_summary_reset(const char *merch, const char *bizdate); + +#endif /* PY_SUMMARY_UPSERT_DBIO_H */ diff --git a/app/src/py/dbio/py_summary_upsert_dbio.pgc b/app/src/py/dbio/py_summary_upsert_dbio.pgc new file mode 100644 index 0000000..7f94d21 --- /dev/null +++ b/app/src/py/dbio/py_summary_upsert_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_summary_upsert_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_upsert_dbio), archived into libpydbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_summary_upsert_dbio.h" + +/* ON CONFLICT upsert: 가맹점 당일 집계를 0으로 리셋(재집계 준비). */ +int pydb_summary_reset(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 py_summary (merchant_id, biz_date, payout_count, payout_amount) + VALUES (:h_merch, :h_bizdate, 0, 0) + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET payout_count = 0, payout_amount = 0, updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("pydb_summary_reset FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return 0; +} diff --git a/app/src/py/py_svr.pgc b/app/src/py/py_svr.pgc index 1882c67..3bbad68 100644 --- a/app/src/py/py_svr.pgc +++ b/app/src/py/py_svr.pgc @@ -1,53 +1,56 @@ /* - * py_svr.pgc - py (지급) MODULE SERVER. + * py_svr.pgc - py (지급) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of py's 30 online services now lives in its own file + * (app/src/py/svc/.pgc) with its own copybook header + * (app/src/py/svc/.h); those objects are archived into libpysvc.a and + * linked into this server. Here we only: + * - include every service copybook (for the extern prototypes), + * - tpopen() the ECPG XA RM, + * - tpadvertise() all services from a {name, fn} table, + * - tpclose() at shutdown. * - * 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. + * 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 -#include -#include #include #include #include -#include -#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); } +/* every py service's copybook header (declares its record struct + prototype) */ +#include "PAY_FILEGEN.h" +#include "PAY_RESULT.h" +#include "PAY_REPROC.h" +#include "PAY_HOLD.h" +#include "PAY_RELEASE.h" +#include "PAY_CANCEL.h" +#include "PAY_ACCTCHK.h" +#include "PAY_SPLIT.h" +#include "PAY_INT.h" +#include "PAY_XFERLINK.h" +#include "PAY_STATUS.h" +#include "PAY_SCHEDULE.h" +#include "PAY_MERCHSUM.h" +#include "PAY_NETOFF.h" +#include "PAY_ADVANCE.h" +#include "PAY_RETRY.h" +#include "PAY_APPROVE.h" +#include "PAY_REJECT.h" +#include "PAY_RECON.h" +#include "PAY_STATEMENT.h" +#include "PAY_FEEDED.h" +#include "PAY_ADJUST.h" +#include "PAY_BULK.h" +#include "PAY_INQUIRY.h" +#include "PAY_HISTORY.h" +#include "PAY_REVERSE.h" +#include "PAY_CONFIRM.h" +#include "PAY_REMIT.h" +#include "PAY_CLOSE.h" +#include "PAY_SUMMARY.h" -#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) ------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, @@ -90,532 +93,4 @@ void tpsvrdone(void) 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: */ diff --git a/app/src/py/run/run_account.sh b/app/src/py/run/run_account.sh new file mode 100755 index 0000000..af8f71d --- /dev/null +++ b/app/src/py/run/run_account.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_account.sh - 계좌 현황 집계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_account.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_account.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_account_batch "$BIZDATE" diff --git a/app/src/py/run/run_adjust.sh b/app/src/py/run/run_adjust.sh new file mode 100755 index 0000000..61497e2 --- /dev/null +++ b/app/src/py/run/run_adjust.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_adjust.sh - 지급 조정 반영 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_adjust.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_adjust.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_adjust_apply_batch "$BIZDATE" diff --git a/app/src/py/run/run_close.sh b/app/src/py/run/run_close.sh new file mode 100755 index 0000000..6aca3f2 --- /dev/null +++ b/app/src/py/run/run_close.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_close.sh - 일마감(P->X) 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_close.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_close.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_close_batch "$BIZDATE" diff --git a/app/src/py/run/run_confirm.sh b/app/src/py/run/run_confirm.sh new file mode 100755 index 0000000..5f1961f --- /dev/null +++ b/app/src/py/run/run_confirm.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_confirm.sh - 지급 파일 확정 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_confirm.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_confirm.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_file_confirm_batch "$BIZDATE" diff --git a/app/src/py/run/run_daily.sh b/app/src/py/run/run_daily.sh new file mode 100755 index 0000000..c042866 --- /dev/null +++ b/app/src/py/run/run_daily.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_daily.sh - 일일 지급 총계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_daily.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_daily.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_daily_batch "$BIZDATE" diff --git a/app/src/py/run/run_eod.sh b/app/src/py/run/run_eod.sh new file mode 100755 index 0000000..0796a08 --- /dev/null +++ b/app/src/py/run/run_eod.sh @@ -0,0 +1,21 @@ +#!/bin/bash +## +## run_eod.sh - 일마감(EOD): 지급 집계/정산 배치 일괄 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_eod.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_eod.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +for b in py_filegen_batch py_result_batch py_merchsum_batch py_ledger_batch \ + py_daily_batch py_settle_batch py_stat_batch py_reject_report_batch \ + py_file_confirm_batch py_close_batch; do + echo "--- $b $BIZDATE ---" + "/app/bin/$b" "$BIZDATE" || exit 1 +done diff --git a/app/src/py/run/run_fee.sh b/app/src/py/run/run_fee.sh new file mode 100755 index 0000000..9661e9c --- /dev/null +++ b/app/src/py/run/run_fee.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_fee.sh - 수수료 재계산 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_fee.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_fee.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_fee_recalc_batch "$BIZDATE" diff --git a/app/src/py/run/run_filegen.sh b/app/src/py/run/run_filegen.sh new file mode 100755 index 0000000..0e50cda --- /dev/null +++ b/app/src/py/run/run_filegen.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_filegen.sh - 지급 파일 생성 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_filegen.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_filegen.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_filegen_batch "$BIZDATE" diff --git a/app/src/py/run/run_ledger.sh b/app/src/py/run/run_ledger.sh new file mode 100755 index 0000000..df2e2e2 --- /dev/null +++ b/app/src/py/run/run_ledger.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_ledger.sh - 지급 원장 생성 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_ledger.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_ledger.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_ledger_batch "$BIZDATE" diff --git a/app/src/py/run/run_merchsum.sh b/app/src/py/run/run_merchsum.sh new file mode 100755 index 0000000..61b98f9 --- /dev/null +++ b/app/src/py/run/run_merchsum.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_merchsum.sh - 가맹점 지급 집계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_merchsum.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_merchsum.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_merchsum_batch "$BIZDATE" diff --git a/app/src/py/run/run_netoff.sh b/app/src/py/run/run_netoff.sh new file mode 100755 index 0000000..39ddb78 --- /dev/null +++ b/app/src/py/run/run_netoff.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_netoff.sh - 일괄 상계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_netoff.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_netoff.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_netoff_batch "$BIZDATE" diff --git a/app/src/py/run/run_reject.sh b/app/src/py/run/run_reject.sh new file mode 100755 index 0000000..38905ad --- /dev/null +++ b/app/src/py/run/run_reject.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_reject.sh - 반려 현황 리포트 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_reject.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_reject.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_reject_report_batch "$BIZDATE" diff --git a/app/src/py/run/run_result.sh b/app/src/py/run/run_result.sh new file mode 100755 index 0000000..278195b --- /dev/null +++ b/app/src/py/run/run_result.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_result.sh - 지급 결과 반영 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_result.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_result.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_result_batch "$BIZDATE" diff --git a/app/src/py/run/run_retry.sh b/app/src/py/run/run_retry.sh new file mode 100755 index 0000000..1e321a7 --- /dev/null +++ b/app/src/py/run/run_retry.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_retry.sh - 실패 지급 재시도 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_retry.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_retry.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_retry_batch "$BIZDATE" diff --git a/app/src/py/run/run_schedule.sh b/app/src/py/run/run_schedule.sh new file mode 100755 index 0000000..d97404e --- /dev/null +++ b/app/src/py/run/run_schedule.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_schedule.sh - 예약 지급 실행 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_schedule.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_schedule.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_schedule_run_batch "$BIZDATE" diff --git a/app/src/py/run/run_settle.sh b/app/src/py/run/run_settle.sh new file mode 100755 index 0000000..9bb6657 --- /dev/null +++ b/app/src/py/run/run_settle.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_settle.sh - 지급완료 정산 집계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_settle.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_settle.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_settle_batch "$BIZDATE" diff --git a/app/src/py/run/run_stat.sh b/app/src/py/run/run_stat.sh new file mode 100755 index 0000000..4ff40b1 --- /dev/null +++ b/app/src/py/run/run_stat.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## +## run_stat.sh - 집중 가맹점 통계 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 배치/서비스를 실행. +## Usage: run_stat.sh [BIZDATE] [MERCH] [AMOUNT] [PID] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +MERCH="${2:-M0001}" +AMOUNT="${3:-1000000}" +PID="${4:-1}" + +echo "[run_stat.sh] BIZDATE=$BIZDATE MERCH=$MERCH AMOUNT=$AMOUNT" +exec /app/bin/py_stat_batch "$BIZDATE" diff --git a/app/src/py/svc/PAY_ACCTCHK.h b/app/src/py/svc/PAY_ACCTCHK.h new file mode 100644 index 0000000..1b53617 --- /dev/null +++ b/app/src/py/svc/PAY_ACCTCHK.h @@ -0,0 +1,19 @@ +/* + * PAY_ACCTCHK.h - py 서비스 PAY_ACCTCHK 카피북 (copybook / record header). + * Request fields: T_KEY1(account_no) + * Response fields: T_AMT1(balance), T_STR1(status), T_RC(0 active / 1 inactive) + */ +#ifndef PAY_ACCTCHK_H +#define PAY_ACCTCHK_H + +#include "py_svc.h" + +typedef struct { + char account_no[32]; + char status[8]; + long balance; +} py_acctchk_rec_t; + +void PAY_ACCTCHK(TPSVCINFO *p); + +#endif /* PAY_ACCTCHK_H */ diff --git a/app/src/py/svc/PAY_ACCTCHK.pgc b/app/src/py/svc/PAY_ACCTCHK.pgc new file mode 100644 index 0000000..d88cab0 --- /dev/null +++ b/app/src/py/svc/PAY_ACCTCHK.pgc @@ -0,0 +1,20 @@ +/* + * PAY_ACCTCHK.pgc - py 모듈 서비스 PAY_ACCTCHK (one service = one file). + * 지급계좌 유효성 점검: dbio 로 계좌 상태/잔액을 읽어 ACTIVE 여부를 회신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_ACCTCHK.h" + +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); +} diff --git a/app/src/py/svc/PAY_ADJUST.h b/app/src/py/svc/PAY_ADJUST.h new file mode 100644 index 0000000..1468929 --- /dev/null +++ b/app/src/py/svc/PAY_ADJUST.h @@ -0,0 +1,21 @@ +/* + * PAY_ADJUST.h - py 서비스 PAY_ADJUST 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_DELTA, T_BIZDATE, T_REASON + * Response fields: T_ADJ(adj_id) + */ +#ifndef PAY_ADJUST_H +#define PAY_ADJUST_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long delta; + long adj_id; + char reason[128]; + char biz_date[16]; +} py_adjust_rec_t; + +void PAY_ADJUST(TPSVCINFO *p); + +#endif /* PAY_ADJUST_H */ diff --git a/app/src/py/svc/PAY_ADJUST.pgc b/app/src/py/svc/PAY_ADJUST.pgc new file mode 100644 index 0000000..ce633d1 --- /dev/null +++ b/app/src/py/svc/PAY_ADJUST.pgc @@ -0,0 +1,29 @@ +/* + * PAY_ADJUST.pgc - py 모듈 서비스 PAY_ADJUST (one service = one file). + * 지급 조정: py_adjust 에 증감(delta)을 기록하고 지급건 net 에 즉시 반영. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_ADJUST.h" + +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); +} diff --git a/app/src/py/svc/PAY_ADVANCE.h b/app/src/py/svc/PAY_ADVANCE.h new file mode 100644 index 0000000..2d327c7 --- /dev/null +++ b/app/src/py/svc/PAY_ADVANCE.h @@ -0,0 +1,23 @@ +/* + * PAY_ADVANCE.h - py 서비스 PAY_ADVANCE 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_KEY1(account_no), T_AMOUNT, T_BIZDATE + * Response fields: T_ID1(payout_id), T_FEE, T_NET + */ +#ifndef PAY_ADVANCE_H +#define PAY_ADVANCE_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char account_no[32]; + long amount; + long fee; + long net; + long payout_id; + char biz_date[16]; +} py_advance_rec_t; + +void PAY_ADVANCE(TPSVCINFO *p); + +#endif /* PAY_ADVANCE_H */ diff --git a/app/src/py/svc/PAY_ADVANCE.pgc b/app/src/py/svc/PAY_ADVANCE.pgc new file mode 100644 index 0000000..6e308b9 --- /dev/null +++ b/app/src/py/svc/PAY_ADVANCE.pgc @@ -0,0 +1,27 @@ +/* + * PAY_ADVANCE.pgc - py 모듈 서비스 PAY_ADVANCE (one service = one file). + * 선지급(advance): 선지급 수수료(1%)를 공제한 지급건(ADVANCE 채널)을 생성. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_ADVANCE.h" + +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); +} diff --git a/app/src/py/svc/PAY_APPROVE.h b/app/src/py/svc/PAY_APPROVE.h new file mode 100644 index 0000000..0de8fab --- /dev/null +++ b/app/src/py/svc/PAY_APPROVE.h @@ -0,0 +1,19 @@ +/* + * PAY_APPROVE.h - py 서비스 PAY_APPROVE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_AMOUNT, T_STATUS, T_RC(0 approved / 1 no-op) + */ +#ifndef PAY_APPROVE_H +#define PAY_APPROVE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + long affected; +} py_approve_rec_t; + +void PAY_APPROVE(TPSVCINFO *p); + +#endif /* PAY_APPROVE_H */ diff --git a/app/src/py/svc/PAY_APPROVE.pgc b/app/src/py/svc/PAY_APPROVE.pgc new file mode 100644 index 0000000..bee7843 --- /dev/null +++ b/app/src/py/svc/PAY_APPROVE.pgc @@ -0,0 +1,27 @@ +/* + * PAY_APPROVE.pgc - py 모듈 서비스 PAY_APPROVE (one service = one file). + * 지급 승인: 금액을 먼저 조회한 뒤 요청(R) 상태만 승인(A)으로 전이하고 금액을 + * 함께 회신. SELECT amount + guarded UPDATE(status='R') + affected-count. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_APPROVE.h" + +void PAY_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pay, h_amt, h_aff; + EXEC SQL END DECLARE SECTION; + + h_pay = getl(b, T_ID1); + EXEC SQL SELECT coalesce(amount,0) INTO :h_amt FROM py_payout WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL UPDATE py_payout SET status = 'A', updated_at = now() + WHERE payout_id = :h_pay AND status = 'R'; + if (sqlca.sqlcode < 0) FAIL(b); + h_aff = sqlca.sqlerrd[2]; + setl(b, T_AMOUNT, h_amt); + if (h_aff > 0) Bchg(b, T_STATUS, 0, "A", 0L); + setl(b, T_RC, h_aff > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/py/svc/PAY_BULK.h b/app/src/py/svc/PAY_BULK.h new file mode 100644 index 0000000..ec8215b --- /dev/null +++ b/app/src/py/svc/PAY_BULK.h @@ -0,0 +1,21 @@ +/* + * PAY_BULK.h - py 서비스 PAY_BULK 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_KEY1(account_no), T_AMOUNT, T_COUNT(n), T_BIZDATE + * Response fields: T_COUNT(created) + */ +#ifndef PAY_BULK_H +#define PAY_BULK_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char account_no[32]; + long amount; + long count; + char biz_date[16]; +} py_bulk_rec_t; + +void PAY_BULK(TPSVCINFO *p); + +#endif /* PAY_BULK_H */ diff --git a/app/src/py/svc/PAY_BULK.pgc b/app/src/py/svc/PAY_BULK.pgc new file mode 100644 index 0000000..1525c6d --- /dev/null +++ b/app/src/py/svc/PAY_BULK.pgc @@ -0,0 +1,26 @@ +/* + * PAY_BULK.pgc - py 모듈 서비스 PAY_BULK (one service = one file). + * 대량 지급 생성: 동일 금액의 지급건(BULK 채널)을 N건 반복 등록. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_BULK.h" + +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); +} diff --git a/app/src/py/svc/PAY_CANCEL.h b/app/src/py/svc/PAY_CANCEL.h new file mode 100644 index 0000000..2d89ee4 --- /dev/null +++ b/app/src/py/svc/PAY_CANCEL.h @@ -0,0 +1,21 @@ +/* + * PAY_CANCEL.h - py 서비스 PAY_CANCEL 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_AMOUNT, T_BIZDATE, T_REASON + * Response fields: T_ADJ(adj_id), T_STATUS + */ +#ifndef PAY_CANCEL_H +#define PAY_CANCEL_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + long adj_id; + char reason[128]; + char biz_date[16]; +} py_cancel_rec_t; + +void PAY_CANCEL(TPSVCINFO *p); + +#endif /* PAY_CANCEL_H */ diff --git a/app/src/py/svc/PAY_CANCEL.pgc b/app/src/py/svc/PAY_CANCEL.pgc new file mode 100644 index 0000000..a15feb8 --- /dev/null +++ b/app/src/py/svc/PAY_CANCEL.pgc @@ -0,0 +1,24 @@ +/* + * PAY_CANCEL.pgc - py 모듈 서비스 PAY_CANCEL (one service = one file). + * 지급 취소: 취소 조정(-amount)을 py_adjust 에 기록하고 지급건 상태 C 로 전이. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_CANCEL.h" + +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); +} diff --git a/app/src/py/svc/PAY_CLOSE.h b/app/src/py/svc/PAY_CLOSE.h new file mode 100644 index 0000000..992f4a4 --- /dev/null +++ b/app/src/py/svc/PAY_CLOSE.h @@ -0,0 +1,18 @@ +/* + * PAY_CLOSE.h - py 서비스 PAY_CLOSE 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(closed) + */ +#ifndef PAY_CLOSE_H +#define PAY_CLOSE_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long closed_count; +} py_close_rec_t; + +void PAY_CLOSE(TPSVCINFO *p); + +#endif /* PAY_CLOSE_H */ diff --git a/app/src/py/svc/PAY_CLOSE.pgc b/app/src/py/svc/PAY_CLOSE.pgc new file mode 100644 index 0000000..1f57f10 --- /dev/null +++ b/app/src/py/svc/PAY_CLOSE.pgc @@ -0,0 +1,25 @@ +/* + * PAY_CLOSE.pgc - py 모듈 서비스 PAY_CLOSE (one service = one file). + * 일마감: 당일 지급완료(P)건을 마감(X)으로 일괄 전환하고 전환 건수를 회신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_CLOSE.h" + +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); +} diff --git a/app/src/py/svc/PAY_CONFIRM.h b/app/src/py/svc/PAY_CONFIRM.h new file mode 100644 index 0000000..787dba7 --- /dev/null +++ b/app/src/py/svc/PAY_CONFIRM.h @@ -0,0 +1,17 @@ +/* + * PAY_CONFIRM.h - py 서비스 PAY_CONFIRM 카피북 (copybook / record header). + * Request fields: T_ID2(file_id) + * Response fields: (none; flips py_file status to confirmed) + */ +#ifndef PAY_CONFIRM_H +#define PAY_CONFIRM_H + +#include "py_svc.h" + +typedef struct { + long file_id; +} py_confirm_rec_t; + +void PAY_CONFIRM(TPSVCINFO *p); + +#endif /* PAY_CONFIRM_H */ diff --git a/app/src/py/svc/PAY_CONFIRM.pgc b/app/src/py/svc/PAY_CONFIRM.pgc new file mode 100644 index 0000000..d403992 --- /dev/null +++ b/app/src/py/svc/PAY_CONFIRM.pgc @@ -0,0 +1,19 @@ +/* + * PAY_CONFIRM.pgc - py 모듈 서비스 PAY_CONFIRM (one service = one file). + * 지급 확정(confirm): 지급 파일 상태를 확정(C)으로 전이하고 확정 시각 기록. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_CONFIRM.h" + +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); +} diff --git a/app/src/py/svc/PAY_FEEDED.h b/app/src/py/svc/PAY_FEEDED.h new file mode 100644 index 0000000..a6d3ca0 --- /dev/null +++ b/app/src/py/svc/PAY_FEEDED.h @@ -0,0 +1,21 @@ +/* + * PAY_FEEDED.h - py 서비스 PAY_FEEDED 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_AMOUNT, T_ARG1(bps) + * Response fields: T_FEE, T_NET + */ +#ifndef PAY_FEEDED_H +#define PAY_FEEDED_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + long bps; + long fee; + long net; +} py_feeded_rec_t; + +void PAY_FEEDED(TPSVCINFO *p); + +#endif /* PAY_FEEDED_H */ diff --git a/app/src/py/svc/PAY_FEEDED.pgc b/app/src/py/svc/PAY_FEEDED.pgc new file mode 100644 index 0000000..82f56c8 --- /dev/null +++ b/app/src/py/svc/PAY_FEEDED.pgc @@ -0,0 +1,26 @@ +/* + * PAY_FEEDED.pgc - py 모듈 서비스 PAY_FEEDED (one service = one file). + * 수수료 공제(fee deduction): bps 로 수수료를 산출해 fee/net 을 재계산·갱신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_FEEDED.h" + +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); +} diff --git a/app/src/py/svc/PAY_FILEGEN.h b/app/src/py/svc/PAY_FILEGEN.h new file mode 100644 index 0000000..3e9f09c --- /dev/null +++ b/app/src/py/svc/PAY_FILEGEN.h @@ -0,0 +1,21 @@ +/* + * PAY_FILEGEN.h - py 서비스 PAY_FILEGEN 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_ID2(file_id), T_COUNT, T_GROSS, T_STR1(file_name) + */ +#ifndef PAY_FILEGEN_H +#define PAY_FILEGEN_H + +#include "py_svc.h" + +typedef struct { + long file_id; + char biz_date[16]; + char file_name[64]; + long record_count; + long total_amount; +} py_filegen_rec_t; + +void PAY_FILEGEN(TPSVCINFO *p); + +#endif /* PAY_FILEGEN_H */ diff --git a/app/src/py/svc/PAY_FILEGEN.pgc b/app/src/py/svc/PAY_FILEGEN.pgc new file mode 100644 index 0000000..3b2e01e --- /dev/null +++ b/app/src/py/svc/PAY_FILEGEN.pgc @@ -0,0 +1,37 @@ +/* + * PAY_FILEGEN.pgc - py 모듈 서비스 PAY_FILEGEN (one service = one file). + * 지급 파일 헤더 생성: 당일 승인(A) 지급건 집계 -> py_file. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_FILEGEN.h" + +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); +} diff --git a/app/src/py/svc/PAY_HISTORY.h b/app/src/py/svc/PAY_HISTORY.h new file mode 100644 index 0000000..27e24bf --- /dev/null +++ b/app/src/py/svc/PAY_HISTORY.h @@ -0,0 +1,18 @@ +/* + * PAY_HISTORY.h - py 서비스 PAY_HISTORY 카피북 (copybook / record header). + * Request fields: T_MERCHANT + * Response fields: T_COUNT + */ +#ifndef PAY_HISTORY_H +#define PAY_HISTORY_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long history_count; +} py_history_rec_t; + +void PAY_HISTORY(TPSVCINFO *p); + +#endif /* PAY_HISTORY_H */ diff --git a/app/src/py/svc/PAY_HISTORY.pgc b/app/src/py/svc/PAY_HISTORY.pgc new file mode 100644 index 0000000..67ee236 --- /dev/null +++ b/app/src/py/svc/PAY_HISTORY.pgc @@ -0,0 +1,23 @@ +/* + * PAY_HISTORY.pgc - py 모듈 서비스 PAY_HISTORY (one service = one file). + * 가맹점 지급 이력 건수: 해당 가맹점의 전체 지급건 수를 집계해 회신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_HISTORY.h" + +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); +} diff --git a/app/src/py/svc/PAY_HOLD.h b/app/src/py/svc/PAY_HOLD.h new file mode 100644 index 0000000..06a0766 --- /dev/null +++ b/app/src/py/svc/PAY_HOLD.h @@ -0,0 +1,18 @@ +/* + * PAY_HOLD.h - py 서비스 PAY_HOLD 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS, T_RC(0 held / 1 rejected) + */ +#ifndef PAY_HOLD_H +#define PAY_HOLD_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char cur_status[8]; +} py_hold_rec_t; + +void PAY_HOLD(TPSVCINFO *p); + +#endif /* PAY_HOLD_H */ diff --git a/app/src/py/svc/PAY_HOLD.pgc b/app/src/py/svc/PAY_HOLD.pgc new file mode 100644 index 0000000..5069859 --- /dev/null +++ b/app/src/py/svc/PAY_HOLD.pgc @@ -0,0 +1,21 @@ +/* + * PAY_HOLD.pgc - py 모듈 서비스 PAY_HOLD (one service = one file). + * 지급 보류: 이미 지급완료(P)/마감(X)된 건은 보류 불가 - dbio 로 현재 상태를 + * 읽어 가드한 뒤 dbio 로 H 전이. dbio wrapper + status guard + extra field. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_HOLD.h" + +void PAY_HOLD(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); + if (st[0] == 'P' || st[0] == 'X') { setl(b, T_RC, 1); FAIL(b); } + if (pydb_update_status(payout_id, "H") < 0) FAIL(b); + setl(b, T_RC, 0); + Bchg(b, T_STATUS, 0, "H", 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_INQUIRY.h b/app/src/py/svc/PAY_INQUIRY.h new file mode 100644 index 0000000..91ed314 --- /dev/null +++ b/app/src/py/svc/PAY_INQUIRY.h @@ -0,0 +1,23 @@ +/* + * PAY_INQUIRY.h - py 서비스 PAY_INQUIRY 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_MERCHANT, T_KEY1(account_no), T_STATUS, T_AMOUNT, T_FEE, T_NET + */ +#ifndef PAY_INQUIRY_H +#define PAY_INQUIRY_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char merchant_id[64]; + char account_no[32]; + long amount; + long fee; + long net; + char status[8]; +} py_inquiry_rec_t; + +void PAY_INQUIRY(TPSVCINFO *p); + +#endif /* PAY_INQUIRY_H */ diff --git a/app/src/py/svc/PAY_INQUIRY.pgc b/app/src/py/svc/PAY_INQUIRY.pgc new file mode 100644 index 0000000..fc3ea03 --- /dev/null +++ b/app/src/py/svc/PAY_INQUIRY.pgc @@ -0,0 +1,32 @@ +/* + * PAY_INQUIRY.pgc - py 모듈 서비스 PAY_INQUIRY (one service = one file). + * 지급 단건 상세 조회: 가맹점/계좌/금액/수수료/순액/상태를 한 번에 회신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_INQUIRY.h" + +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); +} diff --git a/app/src/py/svc/PAY_INT.h b/app/src/py/svc/PAY_INT.h new file mode 100644 index 0000000..9647671 --- /dev/null +++ b/app/src/py/svc/PAY_INT.h @@ -0,0 +1,20 @@ +/* + * PAY_INT.h - py 서비스 PAY_INT 카피북 (copybook / record header). + * Request fields: T_AMOUNT(principal), T_ARG1(days), T_ARG2(bps) + * Response fields: T_AMT1(interest), T_NET(principal-interest) + */ +#ifndef PAY_INT_H +#define PAY_INT_H + +#include "py_svc.h" + +typedef struct { + long principal; + long days; + long bps; + long interest; +} py_int_rec_t; + +void PAY_INT(TPSVCINFO *p); + +#endif /* PAY_INT_H */ diff --git a/app/src/py/svc/PAY_INT.pgc b/app/src/py/svc/PAY_INT.pgc new file mode 100644 index 0000000..23c2ccd --- /dev/null +++ b/app/src/py/svc/PAY_INT.pgc @@ -0,0 +1,20 @@ +/* + * PAY_INT.pgc - py 모듈 서비스 PAY_INT (one service = one file). + * 선지급 이자 계산: 일할(days) * bps 로 이자와 차감 후 순액을 산출(순수 계산). + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_INT.h" + +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); +} diff --git a/app/src/py/svc/PAY_MERCHSUM.h b/app/src/py/svc/PAY_MERCHSUM.h new file mode 100644 index 0000000..0717b23 --- /dev/null +++ b/app/src/py/svc/PAY_MERCHSUM.h @@ -0,0 +1,20 @@ +/* + * PAY_MERCHSUM.h - py 서비스 PAY_MERCHSUM 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_GROSS + */ +#ifndef PAY_MERCHSUM_H +#define PAY_MERCHSUM_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long payout_count; + long payout_amount; +} py_merchsum_rec_t; + +void PAY_MERCHSUM(TPSVCINFO *p); + +#endif /* PAY_MERCHSUM_H */ diff --git a/app/src/py/svc/PAY_MERCHSUM.pgc b/app/src/py/svc/PAY_MERCHSUM.pgc new file mode 100644 index 0000000..079a5dd --- /dev/null +++ b/app/src/py/svc/PAY_MERCHSUM.pgc @@ -0,0 +1,28 @@ +/* + * PAY_MERCHSUM.pgc - py 모듈 서비스 PAY_MERCHSUM (one service = one file). + * 가맹점 당일 지급 집계: py_payout 집계 후 dbio 로 py_summary upsert. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_MERCHSUM.h" + +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); +} diff --git a/app/src/py/svc/PAY_NETOFF.h b/app/src/py/svc/PAY_NETOFF.h new file mode 100644 index 0000000..3b4934d --- /dev/null +++ b/app/src/py/svc/PAY_NETOFF.h @@ -0,0 +1,19 @@ +/* + * PAY_NETOFF.h - py 서비스 PAY_NETOFF 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_AMOUNT(offset) + * Response fields: T_NET(new net) + */ +#ifndef PAY_NETOFF_H +#define PAY_NETOFF_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long offset_amount; + long new_net; +} py_netoff_rec_t; + +void PAY_NETOFF(TPSVCINFO *p); + +#endif /* PAY_NETOFF_H */ diff --git a/app/src/py/svc/PAY_NETOFF.pgc b/app/src/py/svc/PAY_NETOFF.pgc new file mode 100644 index 0000000..89c684c --- /dev/null +++ b/app/src/py/svc/PAY_NETOFF.pgc @@ -0,0 +1,24 @@ +/* + * PAY_NETOFF.pgc - py 모듈 서비스 PAY_NETOFF (one service = one file). + * 상계(net-off): 지급건의 net 을 조회해 차감액을 반영하고 갱신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_NETOFF.h" + +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); +} diff --git a/app/src/py/svc/PAY_RECON.h b/app/src/py/svc/PAY_RECON.h new file mode 100644 index 0000000..c11520f --- /dev/null +++ b/app/src/py/svc/PAY_RECON.h @@ -0,0 +1,19 @@ +/* + * PAY_RECON.h - py 서비스 PAY_RECON 카피북 (copybook / record header). + * Request fields: T_ID2(file_id) + * Response fields: T_COUNT, T_GROSS + */ +#ifndef PAY_RECON_H +#define PAY_RECON_H + +#include "py_svc.h" + +typedef struct { + long file_id; + long payout_count; + long payout_sum; +} py_recon_rec_t; + +void PAY_RECON(TPSVCINFO *p); + +#endif /* PAY_RECON_H */ diff --git a/app/src/py/svc/PAY_RECON.pgc b/app/src/py/svc/PAY_RECON.pgc new file mode 100644 index 0000000..768c7ff --- /dev/null +++ b/app/src/py/svc/PAY_RECON.pgc @@ -0,0 +1,22 @@ +/* + * PAY_RECON.pgc - py 모듈 서비스 PAY_RECON (one service = one file). + * 지급-파일 대사: 특정 file_id 에 편성된 지급 건수/합계를 확인. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_RECON.h" + +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); +} diff --git a/app/src/py/svc/PAY_REJECT.h b/app/src/py/svc/PAY_REJECT.h new file mode 100644 index 0000000..74fb70e --- /dev/null +++ b/app/src/py/svc/PAY_REJECT.h @@ -0,0 +1,18 @@ +/* + * PAY_REJECT.h - py 서비스 PAY_REJECT 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_REASON + * Response fields: T_STATUS + */ +#ifndef PAY_REJECT_H +#define PAY_REJECT_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char reason[128]; +} py_reject_rec_t; + +void PAY_REJECT(TPSVCINFO *p); + +#endif /* PAY_REJECT_H */ diff --git a/app/src/py/svc/PAY_REJECT.pgc b/app/src/py/svc/PAY_REJECT.pgc new file mode 100644 index 0000000..3db90df --- /dev/null +++ b/app/src/py/svc/PAY_REJECT.pgc @@ -0,0 +1,25 @@ +/* + * PAY_REJECT.pgc - py 모듈 서비스 PAY_REJECT (one service = one file). + * 지급 반려: 상태를 반려(J)로 전이하고 반려 사유를 reject_reason 에 기록. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_REJECT.h" + +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); +} diff --git a/app/src/py/svc/PAY_RELEASE.h b/app/src/py/svc/PAY_RELEASE.h new file mode 100644 index 0000000..1416b2c --- /dev/null +++ b/app/src/py/svc/PAY_RELEASE.h @@ -0,0 +1,18 @@ +/* + * PAY_RELEASE.h - py 서비스 PAY_RELEASE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS, T_STR1(prev status), T_RC(0 released / 1 no-op) + */ +#ifndef PAY_RELEASE_H +#define PAY_RELEASE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char prev_status[8]; +} py_release_rec_t; + +void PAY_RELEASE(TPSVCINFO *p); + +#endif /* PAY_RELEASE_H */ diff --git a/app/src/py/svc/PAY_RELEASE.pgc b/app/src/py/svc/PAY_RELEASE.pgc new file mode 100644 index 0000000..71b13d8 --- /dev/null +++ b/app/src/py/svc/PAY_RELEASE.pgc @@ -0,0 +1,28 @@ +/* + * PAY_RELEASE.pgc - py 모듈 서비스 PAY_RELEASE (one service = one file). + * 보류 해제: 직전 상태를 조회하여 보류(H)가 아니면 무효 처리, 보류면 dbio 로 + * 요청(R) 전이하고 이전 상태를 T_STR1 로 함께 회신. SELECT + guard + dbio. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_RELEASE.h" + +void PAY_RELEASE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char prev[4]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pay; + char h_prev[8]; + EXEC SQL END DECLARE SECTION; + + h_pay = getl(b, T_ID1); + EXEC SQL SELECT status INTO :h_prev FROM py_payout WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + prev[0] = h_prev[0]; prev[1] = 0; + if (prev[0] != 'H') { Bchg(b, T_STATUS, 0, prev, 0L); setl(b, T_RC, 1); OK(b); } + if (pydb_update_status(h_pay, "R") < 0) FAIL(b); + Bchg(b, T_STR1, 0, prev, 0L); + Bchg(b, T_STATUS, 0, "R", 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_REMIT.h b/app/src/py/svc/PAY_REMIT.h new file mode 100644 index 0000000..e0504a4 --- /dev/null +++ b/app/src/py/svc/PAY_REMIT.h @@ -0,0 +1,21 @@ +/* + * PAY_REMIT.h - py 서비스 PAY_REMIT 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_NET, T_KEY1(account_no) + * Response fields: T_STATUS, T_RC(0 remitted / 1 insufficient) + */ +#ifndef PAY_REMIT_H +#define PAY_REMIT_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long net; + long balance; + char account_no[32]; + char status[8]; +} py_remit_rec_t; + +void PAY_REMIT(TPSVCINFO *p); + +#endif /* PAY_REMIT_H */ diff --git a/app/src/py/svc/PAY_REMIT.pgc b/app/src/py/svc/PAY_REMIT.pgc new file mode 100644 index 0000000..211c5ec --- /dev/null +++ b/app/src/py/svc/PAY_REMIT.pgc @@ -0,0 +1,24 @@ +/* + * PAY_REMIT.pgc - py 모듈 서비스 PAY_REMIT (one service = one file). + * 실지급 송금: 계좌 잔액을 확인해 부족하면 거절, 충분하면 차감 후 상태 P. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_REMIT.h" + +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); +} diff --git a/app/src/py/svc/PAY_REPROC.h b/app/src/py/svc/PAY_REPROC.h new file mode 100644 index 0000000..2f34c36 --- /dev/null +++ b/app/src/py/svc/PAY_REPROC.h @@ -0,0 +1,19 @@ +/* + * PAY_REPROC.h - py 서비스 PAY_REPROC 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS(new or prev), T_RC(transition fired ? 0 : 1) + */ +#ifndef PAY_REPROC_H +#define PAY_REPROC_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long affected; + char prev_status[8]; +} py_reproc_rec_t; + +void PAY_REPROC(TPSVCINFO *p); + +#endif /* PAY_REPROC_H */ diff --git a/app/src/py/svc/PAY_REPROC.pgc b/app/src/py/svc/PAY_REPROC.pgc new file mode 100644 index 0000000..ec8dcb9 --- /dev/null +++ b/app/src/py/svc/PAY_REPROC.pgc @@ -0,0 +1,28 @@ +/* + * PAY_REPROC.pgc - py 모듈 서비스 PAY_REPROC (one service = one file). + * 재처리: 직전 상태를 조회한 뒤 실패/보류/취소(J,H,C) -> 요청(R) 전이, 이전 상태 회신. + * SELECT prev status then UPDATE with IN(...) guard, report the transition. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_REPROC.h" + +void PAY_REPROC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pay, h_aff; + char h_prev[8]; + EXEC SQL END DECLARE SECTION; + + h_pay = getl(b, T_ID1); + EXEC SQL SELECT status INTO :h_prev FROM py_payout WHERE payout_id = :h_pay; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + /* reprocess only failed/held/cancelled; report whether the transition fired */ + 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', 'C'); + if (sqlca.sqlcode < 0) FAIL(b); + h_aff = sqlca.sqlerrd[2]; + Bchg(b, T_STATUS, 0, h_aff > 0 ? "R" : h_prev, 0L); + setl(b, T_RC, h_aff > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/py/svc/PAY_RESULT.h b/app/src/py/svc/PAY_RESULT.h new file mode 100644 index 0000000..76c2730 --- /dev/null +++ b/app/src/py/svc/PAY_RESULT.h @@ -0,0 +1,19 @@ +/* + * PAY_RESULT.h - py 서비스 PAY_RESULT 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS, T_RC(affected>0 ? 0 : 1) + */ +#ifndef PAY_RESULT_H +#define PAY_RESULT_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long affected; + char status[8]; +} py_result_rec_t; + +void PAY_RESULT(TPSVCINFO *p); + +#endif /* PAY_RESULT_H */ diff --git a/app/src/py/svc/PAY_RESULT.pgc b/app/src/py/svc/PAY_RESULT.pgc new file mode 100644 index 0000000..f1003db --- /dev/null +++ b/app/src/py/svc/PAY_RESULT.pgc @@ -0,0 +1,25 @@ +/* + * PAY_RESULT.pgc - py 모듈 서비스 PAY_RESULT (one service = one file). + * 지급 결과 반영: 편성/요청(F,R) 상태만 지급완료(P)로 전이, 반영 건수를 T_RC 로. + * Inline UPDATE with IN(...) guard + affected-count via sqlca.sqlerrd[2]. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_RESULT.h" + +void PAY_RESULT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pay, h_aff; + EXEC SQL END DECLARE SECTION; + + h_pay = getl(b, T_ID1); + /* only a filed(F)/requested(R) payout may be marked paid(P) */ + EXEC SQL UPDATE py_payout SET status = 'P', updated_at = now() + WHERE payout_id = :h_pay AND status IN ('F', 'R'); + if (sqlca.sqlcode < 0) { userlog("PAY_RESULT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + h_aff = sqlca.sqlerrd[2]; + if (h_aff > 0) Bchg(b, T_STATUS, 0, "P", 0L); + setl(b, T_RC, h_aff > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/py/svc/PAY_RETRY.h b/app/src/py/svc/PAY_RETRY.h new file mode 100644 index 0000000..c746d0b --- /dev/null +++ b/app/src/py/svc/PAY_RETRY.h @@ -0,0 +1,17 @@ +/* + * PAY_RETRY.h - py 서비스 PAY_RETRY 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS, T_RC(0 retried / 1 not eligible) + */ +#ifndef PAY_RETRY_H +#define PAY_RETRY_H + +#include "py_svc.h" + +typedef struct { + long payout_id; +} py_retry_rec_t; + +void PAY_RETRY(TPSVCINFO *p); + +#endif /* PAY_RETRY_H */ diff --git a/app/src/py/svc/PAY_RETRY.pgc b/app/src/py/svc/PAY_RETRY.pgc new file mode 100644 index 0000000..010859a --- /dev/null +++ b/app/src/py/svc/PAY_RETRY.pgc @@ -0,0 +1,23 @@ +/* + * PAY_RETRY.pgc - py 모듈 서비스 PAY_RETRY (one service = one file). + * 실패 지급 재시도: 실패/보류(J,H) 건만 요청(R)으로 되돌리고 retry_count 증가. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_RETRY.h" + +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); +} diff --git a/app/src/py/svc/PAY_REVERSE.h b/app/src/py/svc/PAY_REVERSE.h new file mode 100644 index 0000000..1912a02 --- /dev/null +++ b/app/src/py/svc/PAY_REVERSE.h @@ -0,0 +1,19 @@ +/* + * PAY_REVERSE.h - py 서비스 PAY_REVERSE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_AMOUNT, T_KEY1(account_no) + * Response fields: T_STATUS + */ +#ifndef PAY_REVERSE_H +#define PAY_REVERSE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + char account_no[32]; +} py_reverse_rec_t; + +void PAY_REVERSE(TPSVCINFO *p); + +#endif /* PAY_REVERSE_H */ diff --git a/app/src/py/svc/PAY_REVERSE.pgc b/app/src/py/svc/PAY_REVERSE.pgc new file mode 100644 index 0000000..ab4454b --- /dev/null +++ b/app/src/py/svc/PAY_REVERSE.pgc @@ -0,0 +1,19 @@ +/* + * PAY_REVERSE.pgc - py 모듈 서비스 PAY_REVERSE (one service = one file). + * 지급 역분개(reverse): 상태 V(역분개)로 전이하고 계좌 잔액을 원복. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_REVERSE.h" + +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); +} diff --git a/app/src/py/svc/PAY_SCHEDULE.h b/app/src/py/svc/PAY_SCHEDULE.h new file mode 100644 index 0000000..75d469c --- /dev/null +++ b/app/src/py/svc/PAY_SCHEDULE.h @@ -0,0 +1,19 @@ +/* + * PAY_SCHEDULE.h - py 서비스 PAY_SCHEDULE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_BIZDATE(run_date) + * Response fields: T_ID2(schedule_id) + */ +#ifndef PAY_SCHEDULE_H +#define PAY_SCHEDULE_H + +#include "py_svc.h" + +typedef struct { + long schedule_id; + long payout_id; + char run_date[16]; +} py_schedule_rec_t; + +void PAY_SCHEDULE(TPSVCINFO *p); + +#endif /* PAY_SCHEDULE_H */ diff --git a/app/src/py/svc/PAY_SCHEDULE.pgc b/app/src/py/svc/PAY_SCHEDULE.pgc new file mode 100644 index 0000000..1ebf21a --- /dev/null +++ b/app/src/py/svc/PAY_SCHEDULE.pgc @@ -0,0 +1,27 @@ +/* + * PAY_SCHEDULE.pgc - py 모듈 서비스 PAY_SCHEDULE (one service = one file). + * 지급 예약 등록: 지정 실행일로 py_schedule 예약(S) 레코드를 생성. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_SCHEDULE.h" + +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); +} diff --git a/app/src/py/svc/PAY_SPLIT.h b/app/src/py/svc/PAY_SPLIT.h new file mode 100644 index 0000000..b0661ac --- /dev/null +++ b/app/src/py/svc/PAY_SPLIT.h @@ -0,0 +1,21 @@ +/* + * PAY_SPLIT.h - py 서비스 PAY_SPLIT 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_KEY1(account_no), T_AMOUNT, T_COUNT(n), T_BIZDATE + * Response fields: T_COUNT(created) + */ +#ifndef PAY_SPLIT_H +#define PAY_SPLIT_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char account_no[32]; + long amount; + long parts; + char biz_date[16]; +} py_split_rec_t; + +void PAY_SPLIT(TPSVCINFO *p); + +#endif /* PAY_SPLIT_H */ diff --git a/app/src/py/svc/PAY_SPLIT.pgc b/app/src/py/svc/PAY_SPLIT.pgc new file mode 100644 index 0000000..2503e08 --- /dev/null +++ b/app/src/py/svc/PAY_SPLIT.pgc @@ -0,0 +1,27 @@ +/* + * PAY_SPLIT.pgc - py 모듈 서비스 PAY_SPLIT (one service = one file). + * 지급 분할: 금액을 N건으로 나눠 각각의 지급건(SPLIT 채널)으로 생성. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_SPLIT.h" + +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); +} diff --git a/app/src/py/svc/PAY_STATEMENT.h b/app/src/py/svc/PAY_STATEMENT.h new file mode 100644 index 0000000..704aa47 --- /dev/null +++ b/app/src/py/svc/PAY_STATEMENT.h @@ -0,0 +1,22 @@ +/* + * PAY_STATEMENT.h - py 서비스 PAY_STATEMENT 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_GROSS, T_FEE, T_NET + */ +#ifndef PAY_STATEMENT_H +#define PAY_STATEMENT_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long paid_count; + long paid_amount; + long paid_fee; + long paid_net; +} py_statement_rec_t; + +void PAY_STATEMENT(TPSVCINFO *p); + +#endif /* PAY_STATEMENT_H */ diff --git a/app/src/py/svc/PAY_STATEMENT.pgc b/app/src/py/svc/PAY_STATEMENT.pgc new file mode 100644 index 0000000..d2f37ac --- /dev/null +++ b/app/src/py/svc/PAY_STATEMENT.pgc @@ -0,0 +1,30 @@ +/* + * PAY_STATEMENT.pgc - py 모듈 서비스 PAY_STATEMENT (one service = one file). + * 가맹점 지급 명세: 당일 지급완료(P) 건의 건수/금액/수수료/순액을 집계. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_STATEMENT.h" + +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); +} diff --git a/app/src/py/svc/PAY_STATUS.h b/app/src/py/svc/PAY_STATUS.h new file mode 100644 index 0000000..1552986 --- /dev/null +++ b/app/src/py/svc/PAY_STATUS.h @@ -0,0 +1,18 @@ +/* + * PAY_STATUS.h - py 서비스 PAY_STATUS 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_STATUS + */ +#ifndef PAY_STATUS_H +#define PAY_STATUS_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char status[8]; +} py_status_rec_t; + +void PAY_STATUS(TPSVCINFO *p); + +#endif /* PAY_STATUS_H */ diff --git a/app/src/py/svc/PAY_STATUS.pgc b/app/src/py/svc/PAY_STATUS.pgc new file mode 100644 index 0000000..d1e3b7f --- /dev/null +++ b/app/src/py/svc/PAY_STATUS.pgc @@ -0,0 +1,17 @@ +/* + * PAY_STATUS.pgc - py 모듈 서비스 PAY_STATUS (one service = one file). + * 지급 상태 조회: dbio 로 단건 상태를 읽어 회신. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_STATUS.h" + +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); +} diff --git a/app/src/py/svc/PAY_SUMMARY.h b/app/src/py/svc/PAY_SUMMARY.h new file mode 100644 index 0000000..bb6bac2 --- /dev/null +++ b/app/src/py/svc/PAY_SUMMARY.h @@ -0,0 +1,20 @@ +/* + * PAY_SUMMARY.h - py 서비스 PAY_SUMMARY 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_GROSS + */ +#ifndef PAY_SUMMARY_H +#define PAY_SUMMARY_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long payout_count; + long payout_amount; +} py_summary_rec_t; + +void PAY_SUMMARY(TPSVCINFO *p); + +#endif /* PAY_SUMMARY_H */ diff --git a/app/src/py/svc/PAY_SUMMARY.pgc b/app/src/py/svc/PAY_SUMMARY.pgc new file mode 100644 index 0000000..1bfa74b --- /dev/null +++ b/app/src/py/svc/PAY_SUMMARY.pgc @@ -0,0 +1,28 @@ +/* + * PAY_SUMMARY.pgc - py 모듈 서비스 PAY_SUMMARY (one service = one file). + * 가맹점 일별 지급 집계 조회: py_summary 에서 건수/금액을 읽어 회신(무행 시 0). + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_SUMMARY.h" + +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); +} diff --git a/app/src/py/svc/PAY_XFERLINK.h b/app/src/py/svc/PAY_XFERLINK.h new file mode 100644 index 0000000..108c19e --- /dev/null +++ b/app/src/py/svc/PAY_XFERLINK.h @@ -0,0 +1,19 @@ +/* + * PAY_XFERLINK.h - py 서비스 PAY_XFERLINK 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_AMOUNT, T_BIZDATE + * Response fields: (none; inserts a py_ledger XFER entry) + */ +#ifndef PAY_XFERLINK_H +#define PAY_XFERLINK_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + char biz_date[16]; +} py_xferlink_rec_t; + +void PAY_XFERLINK(TPSVCINFO *p); + +#endif /* PAY_XFERLINK_H */ diff --git a/app/src/py/svc/PAY_XFERLINK.pgc b/app/src/py/svc/PAY_XFERLINK.pgc new file mode 100644 index 0000000..b1534de --- /dev/null +++ b/app/src/py/svc/PAY_XFERLINK.pgc @@ -0,0 +1,24 @@ +/* + * PAY_XFERLINK.pgc - py 모듈 서비스 PAY_XFERLINK (one service = one file). + * 이체 연계: 지급건에 대한 XFER 원장 엔트리를 py_ledger 에 기록. + * Moved verbatim from the former inline py_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by py_svr at tpsvrinit. + */ +#include "PAY_XFERLINK.h" + +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); +} diff --git a/app/src/py/svc/py_svc.h b/app/src/py/svc/py_svc.h new file mode 100644 index 0000000..e35a1d7 --- /dev/null +++ b/app/src/py/svc/py_svc.h @@ -0,0 +1,33 @@ +/* + * py_svc.h - py (지급) 서비스 공통 카피북 (shared copybook for all py svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the py dbio layer (libpydbio.a) and the + * common math (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros every service uses. No business logic and no service function lives here. + */ +#ifndef PY_SVC_H +#define PY_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "py_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* PY_SVC_H */ diff --git a/app/src/rc/batch/rc_alert_batch.pgc b/app/src/rc/batch/rc_alert_batch.pgc new file mode 100644 index 0000000..e2a634b --- /dev/null +++ b/app/src/rc/batch/rc_alert_batch.pgc @@ -0,0 +1,35 @@ +/* + * rc_alert_batch.pgc - 경보 집계 (XA 배치). + * 레벨별 경보 건수(WARN) 집계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_alert_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_warn; + 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 SELECT count(*) FILTER (WHERE lvl='WARN') INTO :h_warn FROM rc_alert WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_alert_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_alert_batch COMMIT: bizdate=%s warn_alerts=%ld\n", bizdate, h_warn); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_byissuer_batch.pgc b/app/src/rc/batch/rc_byissuer_batch.pgc new file mode 100644 index 0000000..bc83742 --- /dev/null +++ b/app/src/rc/batch/rc_byissuer_batch.pgc @@ -0,0 +1,46 @@ +/* + * rc_byissuer_batch.pgc - 발급사별 매입 집계 (XA 배치). + * 발급사별 매입 건수를 커서로 순회 집계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_byissuer_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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_cnt; + char h_issuer[32]; + 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 rc_byissuer_batch_c CURSOR FOR + SELECT issuer, count(*) FROM purchase WHERE biz_date=:h_bizdate GROUP BY issuer ORDER BY issuer; + EXEC SQL OPEN rc_byissuer_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rc_byissuer_batch_c INTO :h_issuer, :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 rc_byissuer_batch_c; tpabort(0); return 1; } + printf(" issuer=%s cnt=%ld\n", h_issuer, h_cnt); rows++; + } + EXEC SQL CLOSE rc_byissuer_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_byissuer_batch COMMIT: bizdate=%s issuers=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_bymerch_batch.pgc b/app/src/rc/batch/rc_bymerch_batch.pgc new file mode 100644 index 0000000..5576cee --- /dev/null +++ b/app/src/rc/batch/rc_bymerch_batch.pgc @@ -0,0 +1,46 @@ +/* + * rc_bymerch_batch.pgc - 가맹점별 net 집계 (XA 배치). + * 가맹점별 순매출을 커서로 순회 집계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_bymerch_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rows = 0, total = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum; + char h_merch[64]; + 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 rc_bymerch_batch_c CURSOR FOR + SELECT merchant_id, coalesce(sum(net),0) FROM purchase WHERE biz_date=:h_bizdate GROUP BY merchant_id ORDER BY merchant_id; + EXEC SQL OPEN rc_bymerch_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rc_bymerch_batch_c INTO :h_merch, :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 rc_bymerch_batch_c; tpabort(0); return 1; } + printf(" merch=%s net=%ld\n", h_merch, h_sum); total += h_sum; rows++; + } + EXEC SQL CLOSE rc_bymerch_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_bymerch_batch COMMIT: bizdate=%s merchants=%ld total=%ld\n", bizdate, rows, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_confirm_batch.pgc b/app/src/rc/batch/rc_confirm_batch.pgc new file mode 100644 index 0000000..f493681 --- /dev/null +++ b/app/src/rc/batch/rc_confirm_batch.pgc @@ -0,0 +1,34 @@ +/* + * rc_confirm_batch.pgc - 대사 일괄 확정 (XA 배치). + * MATCHED 대사 로그를 CONFIRMED 로 일괄 확정. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_confirm_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE rc_match_log SET status='CONFIRMED', updated_at=now() WHERE status='MATCHED' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_confirm_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_confirm_batch COMMIT: bizdate=%s confirmed rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_diffsum_batch.pgc b/app/src/rc/batch/rc_diffsum_batch.pgc new file mode 100644 index 0000000..668acf5 --- /dev/null +++ b/app/src/rc/batch/rc_diffsum_batch.pgc @@ -0,0 +1,35 @@ +/* + * rc_diffsum_batch.pgc - 차이 총계 집계 (XA 배치). + * 당일 차이 금액 절대 총합. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_diffsum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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 SELECT coalesce(sum(abs(diff_amt)),0) INTO :h_sum FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_diffsum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_diffsum_batch COMMIT: bizdate=%s abs_diff=%ld\n", bizdate, h_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_exception_batch.pgc b/app/src/rc/batch/rc_exception_batch.pgc new file mode 100644 index 0000000..b22fab3 --- /dev/null +++ b/app/src/rc/batch/rc_exception_batch.pgc @@ -0,0 +1,35 @@ +/* + * rc_exception_batch.pgc - 미해소 예외 집계 (XA 배치). + * resolved=false 예외 건수 집계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_exception_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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; } + + EXEC SQL SELECT count(*) INTO :h_cnt FROM rc_exception WHERE resolved = false AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_exception_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_exception_batch COMMIT: bizdate=%s open=%ld\n", bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_progress_batch.pgc b/app/src/rc/batch/rc_progress_batch.pgc new file mode 100644 index 0000000..db676b1 --- /dev/null +++ b/app/src/rc/batch/rc_progress_batch.pgc @@ -0,0 +1,38 @@ +/* + * rc_progress_batch.pgc - 대사 진행률 집계 (XA 배치). + * 매입 대비 대사 로그 건수 비교. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_progress_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_p, h_m; + 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 SELECT + (SELECT count(*) FROM purchase WHERE biz_date = :h_bizdate), + (SELECT count(*) FROM rc_match_log WHERE biz_date = :h_bizdate) + INTO :h_p, :h_m; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_progress_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_progress_batch COMMIT: bizdate=%s purchases=%ld matches=%ld\n", bizdate, h_p, h_m); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_purge_batch.pgc b/app/src/rc/batch/rc_purge_batch.pgc new file mode 100644 index 0000000..8a02264 --- /dev/null +++ b/app/src/rc/batch/rc_purge_batch.pgc @@ -0,0 +1,34 @@ +/* + * rc_purge_batch.pgc - 롤백 로그 정리 (XA 배치). + * ROLLBACK 대사 로그를 삭제. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_purge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 DELETE FROM rc_match_log WHERE status='ROLLBACK' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_purge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_purge_batch COMMIT: bizdate=%s purged rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_release_batch.pgc b/app/src/rc/batch/rc_release_batch.pgc new file mode 100644 index 0000000..c87e8bd --- /dev/null +++ b/app/src/rc/batch/rc_release_batch.pgc @@ -0,0 +1,34 @@ +/* + * rc_release_batch.pgc - 보류 일괄 해제 (XA 배치). + * HOLD 대사 로그를 RELEASED 로 일괄 전환. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_release_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE rc_match_log SET status='RELEASED', updated_at=now() WHERE status='HOLD' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_release_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_release_batch COMMIT: bizdate=%s released rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_resolve_batch.pgc b/app/src/rc/batch/rc_resolve_batch.pgc new file mode 100644 index 0000000..3d845cb --- /dev/null +++ b/app/src/rc/batch/rc_resolve_batch.pgc @@ -0,0 +1,34 @@ +/* + * rc_resolve_batch.pgc - 예외 일괄 해소 (XA 배치). + * 미해소 예외를 resolved=true 로 일괄 처리. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_resolve_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE rc_exception SET resolved=true WHERE resolved=false AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_resolve_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_resolve_batch COMMIT: bizdate=%s resolved rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_return_batch.pgc b/app/src/rc/batch/rc_return_batch.pgc new file mode 100644 index 0000000..3ad50d2 --- /dev/null +++ b/app/src/rc/batch/rc_return_batch.pgc @@ -0,0 +1,46 @@ +/* + * rc_return_batch.pgc - 반품 대사 배치 (XA 배치). + * 당일 반품(음수 원장)을 커서로 순회하여 반품 대사 로그 기록. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_return_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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_pid, 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 rc_return_batch_c CURSOR FOR + SELECT purchase_id, amount FROM ledger WHERE biz_date=:h_bizdate AND entry_type='RETURN'; + EXEC SQL OPEN rc_return_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rc_return_batch_c 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 rc_return_batch_c; tpabort(0); return 1; } + if (rcdb_insert_match(h_pid, "RETURN", h_amt, "MATCHED", bizdate) < 0) { EXEC SQL CLOSE rc_return_batch_c; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE rc_return_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_return_batch COMMIT: bizdate=%s returns=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_rollup_batch.pgc b/app/src/rc/batch/rc_rollup_batch.pgc new file mode 100644 index 0000000..0670815 --- /dev/null +++ b/app/src/rc/batch/rc_rollup_batch.pgc @@ -0,0 +1,49 @@ +/* + * rc_rollup_batch.pgc - 요약 upsert 롤업 (XA 배치). + * MATCHED/미대사/총액을 계산해 rc_summary 에 upsert. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_rollup_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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_m, h_u, h_g; + 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 rc_rollup_batch_c CURSOR FOR + SELECT + (SELECT count(*) FROM rc_match_log WHERE biz_date=:h_bizdate AND status='MATCHED'), + (SELECT count(*) FROM purchase p WHERE p.biz_date=:h_bizdate AND NOT EXISTS (SELECT 1 FROM ledger l WHERE l.purchase_id=p.purchase_id)), + (SELECT coalesce(sum(net),0) FROM purchase WHERE biz_date=:h_bizdate); + EXEC SQL OPEN rc_rollup_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rc_rollup_batch_c INTO :h_m, :h_u, :h_g; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rc_rollup_batch_c; tpabort(0); return 1; } + if (rcdb_upsert_summary(bizdate, h_m, h_u, h_g) < 0) { EXEC SQL CLOSE rc_rollup_batch_c; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE rc_rollup_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_rollup_batch COMMIT: bizdate=%s summary_rows=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_stats_batch.pgc b/app/src/rc/batch/rc_stats_batch.pgc new file mode 100644 index 0000000..78e6cf5 --- /dev/null +++ b/app/src/rc/batch/rc_stats_batch.pgc @@ -0,0 +1,35 @@ +/* + * rc_stats_batch.pgc - 대사 통계 집계 (XA 배치). + * 대사 로그 건수/평균/최대 통계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_stats_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_avg, h_max; + 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 SELECT count(*), coalesce(round(avg(amount)),0), coalesce(max(amount),0) INTO :h_cnt, :h_avg, :h_max FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_stats_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_stats_batch COMMIT: bizdate=%s cnt=%ld avg=%ld max=%ld\n", bizdate, h_cnt, h_avg, h_max); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_summary_batch.pgc b/app/src/rc/batch/rc_summary_batch.pgc new file mode 100644 index 0000000..f08bc3c --- /dev/null +++ b/app/src/rc/batch/rc_summary_batch.pgc @@ -0,0 +1,35 @@ +/* + * rc_summary_batch.pgc - 대사 요약 집계 (XA 배치). + * 당일 MATCHED/EXCEPTION 건수 집계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_summary_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_m, h_e; + 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 SELECT count(*) FILTER (WHERE status='MATCHED'), count(*) FILTER (WHERE status='EXCEPTION') INTO :h_m, :h_e FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "rc_summary_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_summary_batch COMMIT: bizdate=%s matched=%ld exception=%ld\n", bizdate, h_m, h_e); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_tolerance_batch.pgc b/app/src/rc/batch/rc_tolerance_batch.pgc new file mode 100644 index 0000000..25a8f2d --- /dev/null +++ b/app/src/rc/batch/rc_tolerance_batch.pgc @@ -0,0 +1,50 @@ +/* + * rc_tolerance_batch.pgc - 허용오차 대사 배치 (XA 배치). + * 차이 라인을 커서로 순회하여 허용치 이내면 MATCHED 대사 기록. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: rc_tolerance_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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_pid, 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 rc_tolerance_batch_c CURSOR FOR + SELECT purchase_id, diff_amt FROM rc_diff WHERE biz_date=:h_bizdate AND diff_kind='AMT'; + EXEC SQL OPEN rc_tolerance_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rc_tolerance_batch_c INTO :h_pid, :h_diff; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rc_tolerance_batch_c; tpabort(0); return 1; } + { + long ad = h_diff < 0 ? -h_diff : h_diff; + const char *st = (ad <= 100) ? "MATCHED" : "EXCEPTION"; + if (rcdb_insert_match(h_pid, "TOLERANCE", h_diff, st, bizdate) < 0) { EXEC SQL CLOSE rc_tolerance_batch_c; tpabort(0); return 1; } + rows++; + } + } + EXEC SQL CLOSE rc_tolerance_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> rc_tolerance_batch COMMIT: bizdate=%s tolerance_checked=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/dbio/rc_alertcnt_dbio.h b/app/src/rc/dbio/rc_alertcnt_dbio.h new file mode 100644 index 0000000..d06343e --- /dev/null +++ b/app/src/rc/dbio/rc_alertcnt_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_alertcnt_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_alertcnt_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_ALERTCNT_DBIO +#define RC_ALERTCNT_DBIO + +typedef struct { char biz_date[16]; char lvl[16]; long cnt; } rc_alertcnt_rec_t; + +long rcdb_alert_level_count(const char *bizdate, const char *lvl); + +#endif /* RC_ALERTCNT_DBIO */ diff --git a/app/src/rc/dbio/rc_alertcnt_dbio.pgc b/app/src/rc/dbio/rc_alertcnt_dbio.pgc new file mode 100644 index 0000000..41995b6 --- /dev/null +++ b/app/src/rc/dbio/rc_alertcnt_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_alertcnt_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_alertcnt_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 경보 레벨별 건수 조회. + */ +#include +#include +#include "rc_alertcnt_dbio.h" + +long rcdb_alert_level_count(const char *bizdate, const char *lvl) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_lvl[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_lvl, lvl, sizeof(h_lvl)-1); h_lvl[sizeof(h_lvl)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM rc_alert + WHERE biz_date = :h_bizdate AND lvl = :h_lvl; + if (sqlca.sqlcode < 0) { userlog("rcdb_alert_level_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_alertwin_dbio.h b/app/src/rc/dbio/rc_alertwin_dbio.h new file mode 100644 index 0000000..a63b4be --- /dev/null +++ b/app/src/rc/dbio/rc_alertwin_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_alertwin_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_alertwin_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_ALERTWIN_DBIO +#define RC_ALERTWIN_DBIO + +typedef struct { long alert_id; long rn; char lvl[16]; } rc_alertwin_rec_t; + +long rcdb_recent_warn_count(const char *bizdate, long topn); + +#endif /* RC_ALERTWIN_DBIO */ diff --git a/app/src/rc/dbio/rc_alertwin_dbio.pgc b/app/src/rc/dbio/rc_alertwin_dbio.pgc new file mode 100644 index 0000000..8a59cba --- /dev/null +++ b/app/src/rc/dbio/rc_alertwin_dbio.pgc @@ -0,0 +1,30 @@ +/* + * rc_alertwin_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_alertwin_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 윈도우 함수(row_number) 커서 - 최근 N 경보 내 WARN 수. + */ +#include +#include +#include "rc_alertwin_dbio.h" + +long rcdb_recent_warn_count(const char *bizdate, long topn) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_lvl[16]; + long h_top = topn, h_rn, warns = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE awc CURSOR FOR + SELECT lvl, row_number() OVER (ORDER BY alert_id DESC) AS rn + FROM rc_alert WHERE biz_date = :h_bizdate; + EXEC SQL OPEN awc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH awc INTO :h_lvl, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE awc; return -1; } + if (h_rn <= h_top && strcmp(h_lvl, "WARN") == 0) warns++; + } + EXEC SQL CLOSE awc; + return warns; +} diff --git a/app/src/rc/dbio/rc_derived_dbio.h b/app/src/rc/dbio/rc_derived_dbio.h new file mode 100644 index 0000000..d628de6 --- /dev/null +++ b/app/src/rc/dbio/rc_derived_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_derived_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_derived_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_DERIVED_DBIO +#define RC_DERIVED_DBIO + +typedef struct { char biz_date[16]; long avg_per_pid; } rc_derived_rec_t; + +long rcdb_avg_match_per_purchase(const char *bizdate); + +#endif /* RC_DERIVED_DBIO */ diff --git a/app/src/rc/dbio/rc_derived_dbio.pgc b/app/src/rc/dbio/rc_derived_dbio.pgc new file mode 100644 index 0000000..08753c0 --- /dev/null +++ b/app/src/rc/dbio/rc_derived_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_derived_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_derived_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 파생 테이블(FROM 서브쿼리) - 매입별 대사건수의 평균. + */ +#include +#include +#include "rc_derived_dbio.h" + +long rcdb_avg_match_per_purchase(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(round(avg(c)),0) INTO :h_v FROM ( + SELECT purchase_id, count(*) AS c FROM rc_match_log + WHERE biz_date = :h_bizdate GROUP BY purchase_id) t; + if (sqlca.sqlcode < 0) { userlog("rcdb_avg_match_per_purchase FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_diffabs_dbio.h b/app/src/rc/dbio/rc_diffabs_dbio.h new file mode 100644 index 0000000..4de3689 --- /dev/null +++ b/app/src/rc/dbio/rc_diffabs_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_diffabs_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_diffabs_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_DIFFABS_DBIO +#define RC_DIFFABS_DBIO + +typedef struct { char biz_date[16]; long abs_total; } rc_diffabs_rec_t; + +long rcdb_diff_abs_total(const char *bizdate); + +#endif /* RC_DIFFABS_DBIO */ diff --git a/app/src/rc/dbio/rc_diffabs_dbio.pgc b/app/src/rc/dbio/rc_diffabs_dbio.pgc new file mode 100644 index 0000000..aa109a4 --- /dev/null +++ b/app/src/rc/dbio/rc_diffabs_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_diffabs_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_diffabs_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 차이 절대값 총합 - abs() 스칼라 집계. + */ +#include +#include +#include "rc_diffabs_dbio.h" + +long rcdb_diff_abs_total(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(abs(diff_amt)),0) INTO :h_v FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_diff_abs_total FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_diffavg_dbio.h b/app/src/rc/dbio/rc_diffavg_dbio.h new file mode 100644 index 0000000..d4b5d51 --- /dev/null +++ b/app/src/rc/dbio/rc_diffavg_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_diffavg_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_diffavg_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_DIFFAVG_DBIO +#define RC_DIFFAVG_DBIO + +typedef struct { char biz_date[16]; long avg_diff; } rc_diffavg_rec_t; + +long rcdb_diff_avg(const char *bizdate); + +#endif /* RC_DIFFAVG_DBIO */ diff --git a/app/src/rc/dbio/rc_diffavg_dbio.pgc b/app/src/rc/dbio/rc_diffavg_dbio.pgc new file mode 100644 index 0000000..efbb3f5 --- /dev/null +++ b/app/src/rc/dbio/rc_diffavg_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_diffavg_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_diffavg_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 차이 평균 - avg() 후 정수 반올림. + */ +#include +#include +#include "rc_diffavg_dbio.h" + +long rcdb_diff_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(round(avg(diff_amt)),0) INTO :h_v FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_diff_avg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_diffkind_dbio.h b/app/src/rc/dbio/rc_diffkind_dbio.h new file mode 100644 index 0000000..68df760 --- /dev/null +++ b/app/src/rc/dbio/rc_diffkind_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_diffkind_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_diffkind_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_DIFFKIND_DBIO +#define RC_DIFFKIND_DBIO + +typedef struct { char biz_date[16]; long amt_cnt; long cnt_cnt; } rc_diffkind_rec_t; + +long rcdb_diff_amt_kind_count(const char *bizdate); + +#endif /* RC_DIFFKIND_DBIO */ diff --git a/app/src/rc/dbio/rc_diffkind_dbio.pgc b/app/src/rc/dbio/rc_diffkind_dbio.pgc new file mode 100644 index 0000000..e0dcbbb --- /dev/null +++ b/app/src/rc/dbio/rc_diffkind_dbio.pgc @@ -0,0 +1,24 @@ +/* + * rc_diffkind_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_diffkind_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * AMT/CNT 차이 종류별 FILTER 건수 - AMT 건수 반환. + */ +#include +#include +#include "rc_diffkind_dbio.h" + +long rcdb_diff_amt_kind_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_amt = 0, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + count(*) FILTER (WHERE diff_kind = 'AMT'), + count(*) FILTER (WHERE diff_kind = 'CNT') + INTO :h_amt, :h_cnt FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + userlog("rcdb_diff_amt_kind_count cnt_kind=%ld", h_cnt); + return h_amt; +} diff --git a/app/src/rc/dbio/rc_distinctpid_dbio.h b/app/src/rc/dbio/rc_distinctpid_dbio.h new file mode 100644 index 0000000..cfde98c --- /dev/null +++ b/app/src/rc/dbio/rc_distinctpid_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_distinctpid_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_distinctpid_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_DISTINCTPID_DBIO +#define RC_DISTINCTPID_DBIO + +typedef struct { char biz_date[16]; long uniq; } rc_distinctpid_rec_t; + +long rcdb_distinct_purchases(const char *bizdate); + +#endif /* RC_DISTINCTPID_DBIO */ diff --git a/app/src/rc/dbio/rc_distinctpid_dbio.pgc b/app/src/rc/dbio/rc_distinctpid_dbio.pgc new file mode 100644 index 0000000..911f716 --- /dev/null +++ b/app/src/rc/dbio/rc_distinctpid_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_distinctpid_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_distinctpid_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 대사 로그의 고유 매입 건수 - count(distinct). + */ +#include +#include +#include "rc_distinctpid_dbio.h" + +long rcdb_distinct_purchases(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT purchase_id) INTO :h_v FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/rc/dbio/rc_exccursor_dbio.h b/app/src/rc/dbio/rc_exccursor_dbio.h new file mode 100644 index 0000000..cf0fb34 --- /dev/null +++ b/app/src/rc/dbio/rc_exccursor_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_exccursor_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_exccursor_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_EXCCURSOR_DBIO +#define RC_EXCCURSOR_DBIO + +typedef struct { char biz_date[16]; long total; } rc_exccursor_rec_t; + +long rcdb_exception_amount_accum(const char *bizdate); + +#endif /* RC_EXCCURSOR_DBIO */ diff --git a/app/src/rc/dbio/rc_exccursor_dbio.pgc b/app/src/rc/dbio/rc_exccursor_dbio.pgc new file mode 100644 index 0000000..8a60bf0 --- /dev/null +++ b/app/src/rc/dbio/rc_exccursor_dbio.pgc @@ -0,0 +1,29 @@ +/* + * rc_exccursor_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_exccursor_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * EXCEPTION 대사 금액 커서 누적 - 부호 무시 절대합. + */ +#include +#include +#include "rc_exccursor_dbio.h" + +long rcdb_exception_amount_accum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_amt, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE eac CURSOR FOR + SELECT amount FROM rc_match_log WHERE biz_date = :h_bizdate AND status = 'EXCEPTION'; + EXEC SQL OPEN eac; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH eac INTO :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE eac; return -1; } + total += (h_amt < 0) ? -h_amt : h_amt; + } + EXEC SQL CLOSE eac; + return total; +} diff --git a/app/src/rc/dbio/rc_excopen_dbio.h b/app/src/rc/dbio/rc_excopen_dbio.h new file mode 100644 index 0000000..a38779d --- /dev/null +++ b/app/src/rc/dbio/rc_excopen_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_excopen_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_excopen_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_EXCOPEN_DBIO +#define RC_EXCOPEN_DBIO + +typedef struct { char biz_date[16]; long open_cnt; } rc_excopen_rec_t; + +long rcdb_exception_open_count(const char *bizdate); + +#endif /* RC_EXCOPEN_DBIO */ diff --git a/app/src/rc/dbio/rc_excopen_dbio.pgc b/app/src/rc/dbio/rc_excopen_dbio.pgc new file mode 100644 index 0000000..1d52747 --- /dev/null +++ b/app/src/rc/dbio/rc_excopen_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_excopen_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_excopen_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 미해소(open) 대사 예외 건수 - boolean 필터. + */ +#include +#include +#include "rc_excopen_dbio.h" + +long rcdb_exception_open_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false; + if (sqlca.sqlcode < 0) { userlog("rcdb_exception_open_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_excpct_dbio.h b/app/src/rc/dbio/rc_excpct_dbio.h new file mode 100644 index 0000000..4c0e093 --- /dev/null +++ b/app/src/rc/dbio/rc_excpct_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_excpct_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_excpct_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_EXCPCT_DBIO +#define RC_EXCPCT_DBIO + +typedef struct { char biz_date[16]; long pct; } rc_excpct_rec_t; + +long rcdb_exception_pct(const char *bizdate); + +#endif /* RC_EXCPCT_DBIO */ diff --git a/app/src/rc/dbio/rc_excpct_dbio.pgc b/app/src/rc/dbio/rc_excpct_dbio.pgc new file mode 100644 index 0000000..6d23e56 --- /dev/null +++ b/app/src/rc/dbio/rc_excpct_dbio.pgc @@ -0,0 +1,23 @@ +/* + * rc_excpct_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_excpct_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 예외율(%) - 두 서브쿼리 비율 계산. + */ +#include +#include +#include "rc_excpct_dbio.h" + +long rcdb_exception_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_exc = 0, h_all = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + (SELECT count(*) FROM rc_match_log WHERE biz_date = :h_bizdate AND status = 'EXCEPTION'), + (SELECT count(*) FROM rc_match_log WHERE biz_date = :h_bizdate) + INTO :h_exc, :h_all; + if (sqlca.sqlcode < 0) { userlog("rcdb_exception_pct FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_all > 0 ? (h_exc * 100 / h_all) : 0; +} diff --git a/app/src/rc/dbio/rc_existsexc_dbio.h b/app/src/rc/dbio/rc_existsexc_dbio.h new file mode 100644 index 0000000..fce1ae2 --- /dev/null +++ b/app/src/rc/dbio/rc_existsexc_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_existsexc_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_existsexc_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_EXISTSEXC_DBIO +#define RC_EXISTSEXC_DBIO + +typedef struct { long purchase_id; int has_exc; } rc_existsexc_rec_t; + +int rcdb_has_open_exception(long pid); + +#endif /* RC_EXISTSEXC_DBIO */ diff --git a/app/src/rc/dbio/rc_existsexc_dbio.pgc b/app/src/rc/dbio/rc_existsexc_dbio.pgc new file mode 100644 index 0000000..4b97bcd --- /dev/null +++ b/app/src/rc/dbio/rc_existsexc_dbio.pgc @@ -0,0 +1,24 @@ +/* + * rc_existsexc_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_existsexc_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 매입에 미해소 예외 존재 여부 - EXISTS boolean. + */ +#include +#include +#include "rc_existsexc_dbio.h" + +int rcdb_has_open_exception(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid; + int h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT CASE WHEN EXISTS + (SELECT 1 FROM rc_exception WHERE purchase_id = :h_pid AND resolved = false) + THEN 1 ELSE 0 END; + EXEC SQL SELECT CASE WHEN EXISTS + (SELECT 1 FROM rc_exception WHERE purchase_id = :h_pid AND resolved = false) + THEN 1 ELSE 0 END INTO :h_v; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/rc/dbio/rc_instatus_dbio.h b/app/src/rc/dbio/rc_instatus_dbio.h new file mode 100644 index 0000000..301ef0f --- /dev/null +++ b/app/src/rc/dbio/rc_instatus_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_instatus_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_instatus_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_INSTATUS_DBIO +#define RC_INSTATUS_DBIO + +typedef struct { char biz_date[16]; long closed; } rc_instatus_rec_t; + +long rcdb_match_closed_count(const char *bizdate); + +#endif /* RC_INSTATUS_DBIO */ diff --git a/app/src/rc/dbio/rc_instatus_dbio.pgc b/app/src/rc/dbio/rc_instatus_dbio.pgc new file mode 100644 index 0000000..0716748 --- /dev/null +++ b/app/src/rc/dbio/rc_instatus_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_instatus_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_instatus_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 종료 상태 집합(CONFIRMED/RELEASED) IN 필터 건수. + */ +#include +#include +#include "rc_instatus_dbio.h" + +long rcdb_match_closed_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM rc_match_log + WHERE biz_date = :h_bizdate AND status IN ('CONFIRMED', 'RELEASED'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/rc/dbio/rc_joinappr_dbio.h b/app/src/rc/dbio/rc_joinappr_dbio.h new file mode 100644 index 0000000..f3c7984 --- /dev/null +++ b/app/src/rc/dbio/rc_joinappr_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_joinappr_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_joinappr_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_JOINAPPR_DBIO +#define RC_JOINAPPR_DBIO + +typedef struct { char biz_date[16]; long matched_net; } rc_joinappr_rec_t; + +long rcdb_matched_net_join(const char *bizdate); + +#endif /* RC_JOINAPPR_DBIO */ diff --git a/app/src/rc/dbio/rc_joinappr_dbio.pgc b/app/src/rc/dbio/rc_joinappr_dbio.pgc new file mode 100644 index 0000000..b75427c --- /dev/null +++ b/app/src/rc/dbio/rc_joinappr_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_joinappr_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_joinappr_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 매입-대사 로그 JOIN - MATCHED 건 순매출(net) 합계. + */ +#include +#include +#include "rc_joinappr_dbio.h" + +long rcdb_matched_net_join(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(p.net),0) INTO :h_v + FROM purchase p JOIN rc_match_log m ON m.purchase_id = p.purchase_id + WHERE p.biz_date = :h_bizdate AND m.status = 'MATCHED'; + if (sqlca.sqlcode < 0) { userlog("rcdb_matched_net_join FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_laststatus_dbio.h b/app/src/rc/dbio/rc_laststatus_dbio.h new file mode 100644 index 0000000..aa4e304 --- /dev/null +++ b/app/src/rc/dbio/rc_laststatus_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_laststatus_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_laststatus_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_LASTSTATUS_DBIO +#define RC_LASTSTATUS_DBIO + +typedef struct { long purchase_id; char status[16]; } rc_laststatus_rec_t; + +int rcdb_last_status(long pid, char *out); + +#endif /* RC_LASTSTATUS_DBIO */ diff --git a/app/src/rc/dbio/rc_laststatus_dbio.pgc b/app/src/rc/dbio/rc_laststatus_dbio.pgc new file mode 100644 index 0000000..a8b39ae --- /dev/null +++ b/app/src/rc/dbio/rc_laststatus_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_laststatus_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_laststatus_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 최근 대사 상태 1건 - ORDER BY LIMIT single fetch (문자열 out). + */ +#include +#include +#include "rc_laststatus_dbio.h" + +int rcdb_last_status(long pid, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid; + char h_status[16]; + EXEC SQL END DECLARE SECTION; + 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 == 100) { strcpy(out, "NONE"); return 0; } + if (sqlca.sqlcode < 0) { userlog("rcdb_last_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strncpy(out, h_status, 15); out[15] = 0; + return 0; +} diff --git a/app/src/rc/dbio/rc_ledgerfilter_dbio.h b/app/src/rc/dbio/rc_ledgerfilter_dbio.h new file mode 100644 index 0000000..f991b04 --- /dev/null +++ b/app/src/rc/dbio/rc_ledgerfilter_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_ledgerfilter_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_ledgerfilter_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_LEDGERFILTER_DBIO +#define RC_LEDGERFILTER_DBIO + +typedef struct { char biz_date[16]; long recon; long settle; } rc_ledgerfilter_rec_t; + +long rcdb_ledger_recon_filter(const char *bizdate); + +#endif /* RC_LEDGERFILTER_DBIO */ diff --git a/app/src/rc/dbio/rc_ledgerfilter_dbio.pgc b/app/src/rc/dbio/rc_ledgerfilter_dbio.pgc new file mode 100644 index 0000000..b898c76 --- /dev/null +++ b/app/src/rc/dbio/rc_ledgerfilter_dbio.pgc @@ -0,0 +1,24 @@ +/* + * rc_ledgerfilter_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_ledgerfilter_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 원장 RECON/SETTLE FILTER 분리 집계 - RECON 합 반환, SETTLE 는 로그. + */ +#include +#include +#include "rc_ledgerfilter_dbio.h" + +long rcdb_ledger_recon_filter(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_recon = 0, h_settle = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(amount) FILTER (WHERE entry_type = 'RECON'), 0), + coalesce(sum(amount) FILTER (WHERE entry_type = 'SETTLE'), 0) + INTO :h_recon, :h_settle FROM ledger WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + userlog("rcdb_ledger_recon_filter settle=%ld", h_settle); + return h_recon; +} diff --git a/app/src/rc/dbio/rc_matchmax_dbio.h b/app/src/rc/dbio/rc_matchmax_dbio.h new file mode 100644 index 0000000..4f5e00b --- /dev/null +++ b/app/src/rc/dbio/rc_matchmax_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_matchmax_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_matchmax_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_MATCHMAX_DBIO +#define RC_MATCHMAX_DBIO + +typedef struct { char biz_date[16]; long max_amt; } rc_matchmax_rec_t; + +long rcdb_match_max_amount(const char *bizdate); + +#endif /* RC_MATCHMAX_DBIO */ diff --git a/app/src/rc/dbio/rc_matchmax_dbio.pgc b/app/src/rc/dbio/rc_matchmax_dbio.pgc new file mode 100644 index 0000000..1efb20a --- /dev/null +++ b/app/src/rc/dbio/rc_matchmax_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_matchmax_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_matchmax_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 최대 대사 금액 - max() 스칼라. + */ +#include +#include +#include "rc_matchmax_dbio.h" + +long rcdb_match_max_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(amount),0) INTO :h_v FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/rc/dbio/rc_matchsum_dbio.h b/app/src/rc/dbio/rc_matchsum_dbio.h new file mode 100644 index 0000000..e4acb62 --- /dev/null +++ b/app/src/rc/dbio/rc_matchsum_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_matchsum_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_matchsum_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_MATCHSUM_DBIO +#define RC_MATCHSUM_DBIO + +typedef struct { char biz_date[16]; char status[16]; long amount; } rc_matchsum_rec_t; + +long rcdb_match_amount_sum(const char *bizdate, const char *status); + +#endif /* RC_MATCHSUM_DBIO */ diff --git a/app/src/rc/dbio/rc_matchsum_dbio.pgc b/app/src/rc/dbio/rc_matchsum_dbio.pgc new file mode 100644 index 0000000..a061b13 --- /dev/null +++ b/app/src/rc/dbio/rc_matchsum_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_matchsum_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_matchsum_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 대사 로그 금액 상태별 단순 합계. + */ +#include +#include +#include "rc_matchsum_dbio.h" + +long rcdb_match_amount_sum(const char *bizdate, const char *status) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_status[16]; + long h_v = 0; + 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 coalesce(sum(amount),0) INTO :h_v FROM rc_match_log + WHERE biz_date = :h_bizdate AND status = :h_status; + if (sqlca.sqlcode < 0) { userlog("rcdb_match_amount_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_nomatch_dbio.h b/app/src/rc/dbio/rc_nomatch_dbio.h new file mode 100644 index 0000000..c88d545 --- /dev/null +++ b/app/src/rc/dbio/rc_nomatch_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_nomatch_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_nomatch_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_NOMATCH_DBIO +#define RC_NOMATCH_DBIO + +typedef struct { char biz_date[16]; long cnt; } rc_nomatch_rec_t; + +long rcdb_purchase_no_matchlog(const char *bizdate); + +#endif /* RC_NOMATCH_DBIO */ diff --git a/app/src/rc/dbio/rc_nomatch_dbio.pgc b/app/src/rc/dbio/rc_nomatch_dbio.pgc new file mode 100644 index 0000000..0f45fad --- /dev/null +++ b/app/src/rc/dbio/rc_nomatch_dbio.pgc @@ -0,0 +1,22 @@ +/* + * rc_nomatch_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_nomatch_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 대사 로그가 없는 미대사 매입 건수 - NOT EXISTS 서브쿼리. + */ +#include +#include +#include "rc_nomatch_dbio.h" + +long rcdb_purchase_no_matchlog(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM rc_match_log m WHERE m.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) { userlog("rcdb_purchase_no_matchlog FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_purgeroll_dbio.h b/app/src/rc/dbio/rc_purgeroll_dbio.h new file mode 100644 index 0000000..ec189c0 --- /dev/null +++ b/app/src/rc/dbio/rc_purgeroll_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_purgeroll_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_purgeroll_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_PURGEROLL_DBIO +#define RC_PURGEROLL_DBIO + +typedef struct { char biz_date[16]; long deleted; } rc_purgeroll_rec_t; + +long rcdb_purge_rollback(const char *bizdate); + +#endif /* RC_PURGEROLL_DBIO */ diff --git a/app/src/rc/dbio/rc_purgeroll_dbio.pgc b/app/src/rc/dbio/rc_purgeroll_dbio.pgc new file mode 100644 index 0000000..cd587a7 --- /dev/null +++ b/app/src/rc/dbio/rc_purgeroll_dbio.pgc @@ -0,0 +1,19 @@ +/* + * rc_purgeroll_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_purgeroll_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 롤백 대사 로그 정리 - DELETE 후 삭제 행수 반환. + */ +#include +#include +#include "rc_purgeroll_dbio.h" + +long rcdb_purge_rollback(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 DELETE FROM rc_match_log WHERE biz_date = :h_bizdate AND status = 'ROLLBACK'; + if (sqlca.sqlcode < 0) { userlog("rcdb_purge_rollback FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long)sqlca.sqlerrd[2]; +} diff --git a/app/src/rc/dbio/rc_resolveall_dbio.h b/app/src/rc/dbio/rc_resolveall_dbio.h new file mode 100644 index 0000000..86b5dc5 --- /dev/null +++ b/app/src/rc/dbio/rc_resolveall_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_resolveall_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_resolveall_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_RESOLVEALL_DBIO +#define RC_RESOLVEALL_DBIO + +typedef struct { char biz_date[16]; long affected; } rc_resolveall_rec_t; + +long rcdb_resolve_all(const char *bizdate); + +#endif /* RC_RESOLVEALL_DBIO */ diff --git a/app/src/rc/dbio/rc_resolveall_dbio.pgc b/app/src/rc/dbio/rc_resolveall_dbio.pgc new file mode 100644 index 0000000..e73ed64 --- /dev/null +++ b/app/src/rc/dbio/rc_resolveall_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_resolveall_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_resolveall_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 예외 일괄 해소 - UPDATE 후 영향 행수(sqlerrd) 반환. + */ +#include +#include +#include "rc_resolveall_dbio.h" + +long rcdb_resolve_all(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE rc_exception SET resolved = true + WHERE biz_date = :h_bizdate AND resolved = false; + if (sqlca.sqlcode < 0) { userlog("rcdb_resolve_all FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long)sqlca.sqlerrd[2]; +} diff --git a/app/src/rc/dbio/rc_summaryratio_dbio.h b/app/src/rc/dbio/rc_summaryratio_dbio.h new file mode 100644 index 0000000..5b7edce --- /dev/null +++ b/app/src/rc/dbio/rc_summaryratio_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_summaryratio_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_summaryratio_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_SUMMARYRATIO_DBIO +#define RC_SUMMARYRATIO_DBIO + +typedef struct { char biz_date[16]; long pct; } rc_summaryratio_rec_t; + +long rcdb_summary_match_pct(const char *bizdate); + +#endif /* RC_SUMMARYRATIO_DBIO */ diff --git a/app/src/rc/dbio/rc_summaryratio_dbio.pgc b/app/src/rc/dbio/rc_summaryratio_dbio.pgc new file mode 100644 index 0000000..310d5fc --- /dev/null +++ b/app/src/rc/dbio/rc_summaryratio_dbio.pgc @@ -0,0 +1,23 @@ +/* + * rc_summaryratio_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_summaryratio_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 요약 대사율(%) - CASE 로 0 분모 방지. + */ +#include +#include +#include "rc_summaryratio_dbio.h" + +long rcdb_summary_match_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT CASE WHEN (matched_cnt + unmatched_cnt) > 0 + THEN matched_cnt * 100 / (matched_cnt + unmatched_cnt) ELSE 0 END + INTO :h_v FROM rc_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode == 100) return 0; + if (sqlca.sqlcode < 0) { userlog("rcdb_summary_match_pct FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_typegrp_dbio.h b/app/src/rc/dbio/rc_typegrp_dbio.h new file mode 100644 index 0000000..85b5421 --- /dev/null +++ b/app/src/rc/dbio/rc_typegrp_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_typegrp_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_typegrp_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_TYPEGRP_DBIO +#define RC_TYPEGRP_DBIO + +typedef struct { char match_type[24]; long cnt; } rc_typegrp_rec_t; + +long rcdb_match_type_kinds(const char *bizdate); + +#endif /* RC_TYPEGRP_DBIO */ diff --git a/app/src/rc/dbio/rc_typegrp_dbio.pgc b/app/src/rc/dbio/rc_typegrp_dbio.pgc new file mode 100644 index 0000000..1955f4a --- /dev/null +++ b/app/src/rc/dbio/rc_typegrp_dbio.pgc @@ -0,0 +1,30 @@ +/* + * rc_typegrp_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_typegrp_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 대사 유형별 GROUP BY 커서 순회 - 서로 다른 match_type 수 반환. + */ +#include +#include +#include "rc_typegrp_dbio.h" + +long rcdb_match_type_kinds(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[24]; + long h_cnt, kinds = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE tgc CURSOR FOR + SELECT match_type, count(*) FROM rc_match_log + WHERE biz_date = :h_bizdate GROUP BY match_type ORDER BY match_type; + EXEC SQL OPEN tgc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH tgc INTO :h_type, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE tgc; return -1; } + kinds++; + } + EXEC SQL CLOSE tgc; + return kinds; +} diff --git a/app/src/rc/dbio/rc_typehaving_dbio.h b/app/src/rc/dbio/rc_typehaving_dbio.h new file mode 100644 index 0000000..70e4558 --- /dev/null +++ b/app/src/rc/dbio/rc_typehaving_dbio.h @@ -0,0 +1,13 @@ +/* + * rc_typehaving_dbio.h - rc 모듈 DB 접근 계층 copybook (rc_typehaving_dbio, part of librcdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef RC_TYPEHAVING_DBIO +#define RC_TYPEHAVING_DBIO + +typedef struct { char match_type[24]; long cnt; } rc_typehaving_rec_t; + +long rcdb_frequent_types(const char *bizdate, long threshold); + +#endif /* RC_TYPEHAVING_DBIO */ diff --git a/app/src/rc/dbio/rc_typehaving_dbio.pgc b/app/src/rc/dbio/rc_typehaving_dbio.pgc new file mode 100644 index 0000000..260a684 --- /dev/null +++ b/app/src/rc/dbio/rc_typehaving_dbio.pgc @@ -0,0 +1,30 @@ +/* + * rc_typehaving_dbio.pgc - rc 모듈 DB 접근 함수 세트 (rc_typehaving_dbio), archived into librcdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * HAVING 필터 커서 - 임계 이상 반복된 대사 유형 수. + */ +#include +#include +#include "rc_typehaving_dbio.h" + +long rcdb_frequent_types(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[24]; + long h_thr = threshold, h_cnt, kinds = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE thc CURSOR FOR + SELECT match_type, count(*) FROM rc_match_log + WHERE biz_date = :h_bizdate GROUP BY match_type HAVING count(*) >= :h_thr; + EXEC SQL OPEN thc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH thc INTO :h_type, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE thc; return -1; } + kinds++; + } + EXEC SQL CLOSE thc; + return kinds; +} diff --git a/app/src/rc/rc_svr.pgc b/app/src/rc/rc_svr.pgc index 67923a9..15cf383 100644 --- a/app/src/rc/rc_svr.pgc +++ b/app/src/rc/rc_svr.pgc @@ -1,70 +1,71 @@ /* - * rc_svr.pgc - rc (대사) MODULE SERVER. + * rc_svr.pgc - rc (대사) MODULE SERVER (thin dispatcher). * - * 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). + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of rc's 30 online services now lives in its own file + * (app/src/rc/svc/.pgc) with its own copybook header + * (app/src/rc/svc/.h); those objects are archived into librcsvc.a and + * linked into this server. Here we only include every service copybook, tpopen() + * the ECPG XA RM, tpadvertise() all services from a {name, fn} table, and + * tpclose() at shutdown. * - * 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. + * rc_svr: ONE binary that tpadvertise()s ALL 30 online rc(대사) services. + * RECONCILE keeps the proven chain: distinct XA branch, own recon ledger row, + * then tpcall()s SETTLE inside the SAME global tx. */ -#include -#include -#include #include #include #include -#include -#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); } +/* every rc service's copybook header (declares its record struct + prototype) */ +#include "RECONCILE.h" +#include "RC_APPR_ACQ.h" +#include "RC_ACQ_DEP.h" +#include "RC_RETURN.h" +#include "RC_AMTDIFF.h" +#include "RC_CNTDIFF.h" +#include "RC_3WAY.h" +#include "RC_TOLERANCE.h" +#include "RC_REMATCH.h" +#include "RC_HOLD.h" +#include "RC_UNMATCH.h" +#include "RC_RELEASE.h" +#include "RC_EXCEPTION.h" +#include "RC_AUTOFIX.h" +#include "RC_BYISSUER.h" +#include "RC_BYDATE.h" +#include "RC_BYMERCH.h" +#include "RC_PARTIAL.h" +#include "RC_DIFFRPT.h" +#include "RC_STATS.h" +#include "RC_PROGRESS.h" +#include "RC_SUMMARY.h" +#include "RC_REPROC.h" +#include "RC_DELAY.h" +#include "RC_LEDGERLINK.h" +#include "RC_ALERT.h" +#include "RC_MANUAL.h" +#include "RC_ROLLBACK.h" +#include "RC_CONFIRM.h" +#include "RC_STATUS.h" -#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 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 --------------------------------------------------- */ +/* ----- advertise table: {service name, function} ---------------------- */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { - {"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}, + {"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} }; @@ -83,547 +84,4 @@ 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; - char bizdate[16]; - long rlen = 0; - 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; - - /* own-branch write: recon ledger evidence only */ - EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) - VALUES (nextval('ledger_seq'), :h_pid, 'RECON', :h_net, :h_bizdate); - if (sqlca.sqlcode < 0) { - userlog("RECONCILE INSERT ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); - FAIL(b); - } - Bchg(b, T_STATUS, 0, "M", 0L); - 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)); - FAIL(b); - } - OK(b); -} - -/* 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; - 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, 'REMATCH', :h_net, :h_bizdate); - if (sqlca.sqlcode < 0) FAIL(b); - OK(b); -} - -/* 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; - 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 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) FAIL(b); - { long v = h_cnt; Bchg(b, T_COUNT, 0, (char *)&v, 0L); } - OK(b); -} - -/* 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_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(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); -} - /* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/run/run_alert.sh b/app/src/rc/run/run_alert.sh new file mode 100755 index 0000000..dbaa834 --- /dev/null +++ b/app/src/rc/run/run_alert.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_alert.sh - rc_alert_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_alert.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_alert.sh] mod=rc BIZDATE=$BIZDATE -> rc_alert_batch" +exec /app/bin/rc_alert_batch "$BIZDATE" diff --git a/app/src/rc/run/run_byissuer.sh b/app/src/rc/run/run_byissuer.sh new file mode 100755 index 0000000..89da113 --- /dev/null +++ b/app/src/rc/run/run_byissuer.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_byissuer.sh - rc_byissuer_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_byissuer.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_byissuer.sh] mod=rc BIZDATE=$BIZDATE -> rc_byissuer_batch" +exec /app/bin/rc_byissuer_batch "$BIZDATE" diff --git a/app/src/rc/run/run_bymerch.sh b/app/src/rc/run/run_bymerch.sh new file mode 100755 index 0000000..926a7fe --- /dev/null +++ b/app/src/rc/run/run_bymerch.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_bymerch.sh - rc_bymerch_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_bymerch.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_bymerch.sh] mod=rc BIZDATE=$BIZDATE -> rc_bymerch_batch" +exec /app/bin/rc_bymerch_batch "$BIZDATE" diff --git a/app/src/rc/run/run_confirm.sh b/app/src/rc/run/run_confirm.sh new file mode 100755 index 0000000..84f72b4 --- /dev/null +++ b/app/src/rc/run/run_confirm.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_confirm.sh - rc_confirm_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_confirm.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_confirm.sh] mod=rc BIZDATE=$BIZDATE -> rc_confirm_batch" +exec /app/bin/rc_confirm_batch "$BIZDATE" diff --git a/app/src/rc/run/run_diff.sh b/app/src/rc/run/run_diff.sh new file mode 100755 index 0000000..5dc929e --- /dev/null +++ b/app/src/rc/run/run_diff.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_diff.sh - rc_diff_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_diff.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_diff.sh] mod=rc BIZDATE=$BIZDATE -> rc_diff_batch" +exec /app/bin/rc_diff_batch "$BIZDATE" diff --git a/app/src/rc/run/run_diffsum.sh b/app/src/rc/run/run_diffsum.sh new file mode 100755 index 0000000..ddca9dd --- /dev/null +++ b/app/src/rc/run/run_diffsum.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_diffsum.sh - rc_diffsum_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_diffsum.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_diffsum.sh] mod=rc BIZDATE=$BIZDATE -> rc_diffsum_batch" +exec /app/bin/rc_diffsum_batch "$BIZDATE" diff --git a/app/src/rc/run/run_exception.sh b/app/src/rc/run/run_exception.sh new file mode 100755 index 0000000..c187400 --- /dev/null +++ b/app/src/rc/run/run_exception.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_exception.sh - rc_exception_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_exception.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_exception.sh] mod=rc BIZDATE=$BIZDATE -> rc_exception_batch" +exec /app/bin/rc_exception_batch "$BIZDATE" diff --git a/app/src/rc/run/run_match.sh b/app/src/rc/run/run_match.sh new file mode 100755 index 0000000..8eb7a66 --- /dev/null +++ b/app/src/rc/run/run_match.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_match.sh - rc_match_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_match.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_match.sh] mod=rc BIZDATE=$BIZDATE -> rc_match_batch" +exec /app/bin/rc_match_batch "$BIZDATE" diff --git a/app/src/rc/run/run_progress.sh b/app/src/rc/run/run_progress.sh new file mode 100755 index 0000000..687a68e --- /dev/null +++ b/app/src/rc/run/run_progress.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_progress.sh - rc_progress_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_progress.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_progress.sh] mod=rc BIZDATE=$BIZDATE -> rc_progress_batch" +exec /app/bin/rc_progress_batch "$BIZDATE" diff --git a/app/src/rc/run/run_purge.sh b/app/src/rc/run/run_purge.sh new file mode 100755 index 0000000..807314e --- /dev/null +++ b/app/src/rc/run/run_purge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_purge.sh - rc_purge_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_purge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_purge.sh] mod=rc BIZDATE=$BIZDATE -> rc_purge_batch" +exec /app/bin/rc_purge_batch "$BIZDATE" diff --git a/app/src/rc/run/run_release.sh b/app/src/rc/run/run_release.sh new file mode 100755 index 0000000..c711a08 --- /dev/null +++ b/app/src/rc/run/run_release.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_release.sh - rc_release_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_release.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_release.sh] mod=rc BIZDATE=$BIZDATE -> rc_release_batch" +exec /app/bin/rc_release_batch "$BIZDATE" diff --git a/app/src/rc/run/run_resolve.sh b/app/src/rc/run/run_resolve.sh new file mode 100755 index 0000000..133cd77 --- /dev/null +++ b/app/src/rc/run/run_resolve.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_resolve.sh - rc_resolve_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_resolve.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_resolve.sh] mod=rc BIZDATE=$BIZDATE -> rc_resolve_batch" +exec /app/bin/rc_resolve_batch "$BIZDATE" diff --git a/app/src/rc/run/run_return.sh b/app/src/rc/run/run_return.sh new file mode 100755 index 0000000..89cb842 --- /dev/null +++ b/app/src/rc/run/run_return.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_return.sh - rc_return_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_return.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_return.sh] mod=rc BIZDATE=$BIZDATE -> rc_return_batch" +exec /app/bin/rc_return_batch "$BIZDATE" diff --git a/app/src/rc/run/run_rollup.sh b/app/src/rc/run/run_rollup.sh new file mode 100755 index 0000000..c22f981 --- /dev/null +++ b/app/src/rc/run/run_rollup.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_rollup.sh - rc_rollup_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_rollup.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_rollup.sh] mod=rc BIZDATE=$BIZDATE -> rc_rollup_batch" +exec /app/bin/rc_rollup_batch "$BIZDATE" diff --git a/app/src/rc/run/run_stats.sh b/app/src/rc/run/run_stats.sh new file mode 100755 index 0000000..4fed800 --- /dev/null +++ b/app/src/rc/run/run_stats.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_stats.sh - rc_stats_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_stats.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_stats.sh] mod=rc BIZDATE=$BIZDATE -> rc_stats_batch" +exec /app/bin/rc_stats_batch "$BIZDATE" diff --git a/app/src/rc/run/run_summary.sh b/app/src/rc/run/run_summary.sh new file mode 100755 index 0000000..2b5c0ce --- /dev/null +++ b/app/src/rc/run/run_summary.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_summary.sh - rc_summary_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_summary.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_summary.sh] mod=rc BIZDATE=$BIZDATE -> rc_summary_batch" +exec /app/bin/rc_summary_batch "$BIZDATE" diff --git a/app/src/rc/run/run_tolerance.sh b/app/src/rc/run/run_tolerance.sh new file mode 100755 index 0000000..0bca952 --- /dev/null +++ b/app/src/rc/run/run_tolerance.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_tolerance.sh - rc_tolerance_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 rc 배치를 실행. +## Usage: run_tolerance.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_tolerance.sh] mod=rc BIZDATE=$BIZDATE -> rc_tolerance_batch" +exec /app/bin/rc_tolerance_batch "$BIZDATE" diff --git a/app/src/rc/svc/RC_3WAY.h b/app/src/rc/svc/RC_3WAY.h new file mode 100644 index 0000000..46509a4 --- /dev/null +++ b/app/src/rc/svc/RC_3WAY.h @@ -0,0 +1,21 @@ +/* + * RC_3WAY.h - rc 서비스 RC_3WAY 카피북 (copybook / record header). + * 3-way 대사: 승인+매입+원장 모두 존재 시 MATCHED. + */ +#ifndef RC_3WAY_H +#define RC_3WAY_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_3way_rec_t; + +void RC_3WAY(TPSVCINFO *p); + +#endif /* RC_3WAY_H */ diff --git a/app/src/rc/svc/RC_3WAY.pgc b/app/src/rc/svc/RC_3WAY.pgc new file mode 100644 index 0000000..cd17b09 --- /dev/null +++ b/app/src/rc/svc/RC_3WAY.pgc @@ -0,0 +1,32 @@ +/* + * RC_3WAY.pgc - rc 모듈 서비스 RC_3WAY (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_3WAY.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ACQ_DEP.h b/app/src/rc/svc/RC_ACQ_DEP.h new file mode 100644 index 0000000..658b6b9 --- /dev/null +++ b/app/src/rc/svc/RC_ACQ_DEP.h @@ -0,0 +1,21 @@ +/* + * RC_ACQ_DEP.h - rc 서비스 RC_ACQ_DEP 카피북 (copybook / record header). + * 매입-입금(원장) 대사: 원장 합계와 net 비교. + */ +#ifndef RC_ACQ_DEP_H +#define RC_ACQ_DEP_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_acq_dep_rec_t; + +void RC_ACQ_DEP(TPSVCINFO *p); + +#endif /* RC_ACQ_DEP_H */ diff --git a/app/src/rc/svc/RC_ACQ_DEP.pgc b/app/src/rc/svc/RC_ACQ_DEP.pgc new file mode 100644 index 0000000..709148a --- /dev/null +++ b/app/src/rc/svc/RC_ACQ_DEP.pgc @@ -0,0 +1,32 @@ +/* + * RC_ACQ_DEP.pgc - rc 모듈 서비스 RC_ACQ_DEP (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_ACQ_DEP.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ALERT.h b/app/src/rc/svc/RC_ALERT.h new file mode 100644 index 0000000..ced6ee9 --- /dev/null +++ b/app/src/rc/svc/RC_ALERT.h @@ -0,0 +1,21 @@ +/* + * RC_ALERT.h - rc 서비스 RC_ALERT 카피북 (copybook / record header). + * 대사 경보 등록. + */ +#ifndef RC_ALERT_H +#define RC_ALERT_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_alert_rec_t; + +void RC_ALERT(TPSVCINFO *p); + +#endif /* RC_ALERT_H */ diff --git a/app/src/rc/svc/RC_ALERT.pgc b/app/src/rc/svc/RC_ALERT.pgc new file mode 100644 index 0000000..2592295 --- /dev/null +++ b/app/src/rc/svc/RC_ALERT.pgc @@ -0,0 +1,33 @@ +/* + * RC_ALERT.pgc - rc 모듈 서비스 RC_ALERT (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_ALERT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_AMTDIFF.h b/app/src/rc/svc/RC_AMTDIFF.h new file mode 100644 index 0000000..0e03efa --- /dev/null +++ b/app/src/rc/svc/RC_AMTDIFF.h @@ -0,0 +1,21 @@ +/* + * RC_AMTDIFF.h - rc 서비스 RC_AMTDIFF 카피북 (copybook / record header). + * 금액 차이 대사: 기대(net) vs 실제(원장합) 차이 기록. + */ +#ifndef RC_AMTDIFF_H +#define RC_AMTDIFF_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_amtdiff_rec_t; + +void RC_AMTDIFF(TPSVCINFO *p); + +#endif /* RC_AMTDIFF_H */ diff --git a/app/src/rc/svc/RC_AMTDIFF.pgc b/app/src/rc/svc/RC_AMTDIFF.pgc new file mode 100644 index 0000000..cfe542e --- /dev/null +++ b/app/src/rc/svc/RC_AMTDIFF.pgc @@ -0,0 +1,35 @@ +/* + * RC_AMTDIFF.pgc - rc 모듈 서비스 RC_AMTDIFF (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_AMTDIFF.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_APPR_ACQ.h b/app/src/rc/svc/RC_APPR_ACQ.h new file mode 100644 index 0000000..ca4e123 --- /dev/null +++ b/app/src/rc/svc/RC_APPR_ACQ.h @@ -0,0 +1,21 @@ +/* + * RC_APPR_ACQ.h - rc 서비스 RC_APPR_ACQ 카피북 (copybook / record header). + * 승인-매입 대사 (승인 존재 여부 확인 후 대사 로그). + */ +#ifndef RC_APPR_ACQ_H +#define RC_APPR_ACQ_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_appr_acq_rec_t; + +void RC_APPR_ACQ(TPSVCINFO *p); + +#endif /* RC_APPR_ACQ_H */ diff --git a/app/src/rc/svc/RC_APPR_ACQ.pgc b/app/src/rc/svc/RC_APPR_ACQ.pgc new file mode 100644 index 0000000..a5135bf --- /dev/null +++ b/app/src/rc/svc/RC_APPR_ACQ.pgc @@ -0,0 +1,33 @@ +/* + * RC_APPR_ACQ.pgc - rc 모듈 서비스 RC_APPR_ACQ (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_APPR_ACQ.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_AUTOFIX.h b/app/src/rc/svc/RC_AUTOFIX.h new file mode 100644 index 0000000..14e3048 --- /dev/null +++ b/app/src/rc/svc/RC_AUTOFIX.h @@ -0,0 +1,21 @@ +/* + * RC_AUTOFIX.h - rc 서비스 RC_AUTOFIX 카피북 (copybook / record header). + * 자동 보정: 예외 해소 + 보정 원장. + */ +#ifndef RC_AUTOFIX_H +#define RC_AUTOFIX_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_autofix_rec_t; + +void RC_AUTOFIX(TPSVCINFO *p); + +#endif /* RC_AUTOFIX_H */ diff --git a/app/src/rc/svc/RC_AUTOFIX.pgc b/app/src/rc/svc/RC_AUTOFIX.pgc new file mode 100644 index 0000000..229ae6d --- /dev/null +++ b/app/src/rc/svc/RC_AUTOFIX.pgc @@ -0,0 +1,21 @@ +/* + * RC_AUTOFIX.pgc - rc 모듈 서비스 RC_AUTOFIX (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_AUTOFIX.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYDATE.h b/app/src/rc/svc/RC_BYDATE.h new file mode 100644 index 0000000..7808ab2 --- /dev/null +++ b/app/src/rc/svc/RC_BYDATE.h @@ -0,0 +1,21 @@ +/* + * RC_BYDATE.h - rc 서비스 RC_BYDATE 카피북 (copybook / record header). + * 일자별 대사(MATCHED) 집계. + */ +#ifndef RC_BYDATE_H +#define RC_BYDATE_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_bydate_rec_t; + +void RC_BYDATE(TPSVCINFO *p); + +#endif /* RC_BYDATE_H */ diff --git a/app/src/rc/svc/RC_BYDATE.pgc b/app/src/rc/svc/RC_BYDATE.pgc new file mode 100644 index 0000000..17debe6 --- /dev/null +++ b/app/src/rc/svc/RC_BYDATE.pgc @@ -0,0 +1,27 @@ +/* + * RC_BYDATE.pgc - rc 모듈 서비스 RC_BYDATE (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_BYDATE.h" + +/* 16. RC_BYDATE - 일자별 대사(MATCHED) 집계. */ +void RC_BYDATE(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(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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYISSUER.h b/app/src/rc/svc/RC_BYISSUER.h new file mode 100644 index 0000000..26711af --- /dev/null +++ b/app/src/rc/svc/RC_BYISSUER.h @@ -0,0 +1,21 @@ +/* + * RC_BYISSUER.h - rc 서비스 RC_BYISSUER 카피북 (copybook / record header). + * 발급사별 매입 건수 집계. + */ +#ifndef RC_BYISSUER_H +#define RC_BYISSUER_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_byissuer_rec_t; + +void RC_BYISSUER(TPSVCINFO *p); + +#endif /* RC_BYISSUER_H */ diff --git a/app/src/rc/svc/RC_BYISSUER.pgc b/app/src/rc/svc/RC_BYISSUER.pgc new file mode 100644 index 0000000..d895b91 --- /dev/null +++ b/app/src/rc/svc/RC_BYISSUER.pgc @@ -0,0 +1,28 @@ +/* + * RC_BYISSUER.pgc - rc 모듈 서비스 RC_BYISSUER (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_BYISSUER.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYMERCH.h b/app/src/rc/svc/RC_BYMERCH.h new file mode 100644 index 0000000..632979f --- /dev/null +++ b/app/src/rc/svc/RC_BYMERCH.h @@ -0,0 +1,21 @@ +/* + * RC_BYMERCH.h - rc 서비스 RC_BYMERCH 카피북 (copybook / record header). + * 가맹점별 net 합계 조회. + */ +#ifndef RC_BYMERCH_H +#define RC_BYMERCH_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_bymerch_rec_t; + +void RC_BYMERCH(TPSVCINFO *p); + +#endif /* RC_BYMERCH_H */ diff --git a/app/src/rc/svc/RC_BYMERCH.pgc b/app/src/rc/svc/RC_BYMERCH.pgc new file mode 100644 index 0000000..6860ae4 --- /dev/null +++ b/app/src/rc/svc/RC_BYMERCH.pgc @@ -0,0 +1,28 @@ +/* + * RC_BYMERCH.pgc - rc 모듈 서비스 RC_BYMERCH (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_BYMERCH.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_CNTDIFF.h b/app/src/rc/svc/RC_CNTDIFF.h new file mode 100644 index 0000000..95b2fae --- /dev/null +++ b/app/src/rc/svc/RC_CNTDIFF.h @@ -0,0 +1,21 @@ +/* + * RC_CNTDIFF.h - rc 서비스 RC_CNTDIFF 카피북 (copybook / record header). + * 건수 차이 대사: 매입 건수 vs 대사 로그 건수. + */ +#ifndef RC_CNTDIFF_H +#define RC_CNTDIFF_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_cntdiff_rec_t; + +void RC_CNTDIFF(TPSVCINFO *p); + +#endif /* RC_CNTDIFF_H */ diff --git a/app/src/rc/svc/RC_CNTDIFF.pgc b/app/src/rc/svc/RC_CNTDIFF.pgc new file mode 100644 index 0000000..79eb3d4 --- /dev/null +++ b/app/src/rc/svc/RC_CNTDIFF.pgc @@ -0,0 +1,34 @@ +/* + * RC_CNTDIFF.pgc - rc 모듈 서비스 RC_CNTDIFF (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_CNTDIFF.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_CONFIRM.h b/app/src/rc/svc/RC_CONFIRM.h new file mode 100644 index 0000000..6ecead1 --- /dev/null +++ b/app/src/rc/svc/RC_CONFIRM.h @@ -0,0 +1,21 @@ +/* + * RC_CONFIRM.h - rc 서비스 RC_CONFIRM 카피북 (copybook / record header). + * 대사 확정 (MATCHED -> CONFIRMED). + */ +#ifndef RC_CONFIRM_H +#define RC_CONFIRM_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_confirm_rec_t; + +void RC_CONFIRM(TPSVCINFO *p); + +#endif /* RC_CONFIRM_H */ diff --git a/app/src/rc/svc/RC_CONFIRM.pgc b/app/src/rc/svc/RC_CONFIRM.pgc new file mode 100644 index 0000000..ca26d99 --- /dev/null +++ b/app/src/rc/svc/RC_CONFIRM.pgc @@ -0,0 +1,18 @@ +/* + * RC_CONFIRM.pgc - rc 모듈 서비스 RC_CONFIRM (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_CONFIRM.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DELAY.h b/app/src/rc/svc/RC_DELAY.h new file mode 100644 index 0000000..5d12d72 --- /dev/null +++ b/app/src/rc/svc/RC_DELAY.h @@ -0,0 +1,21 @@ +/* + * RC_DELAY.h - rc 서비스 RC_DELAY 카피북 (copybook / record header). + * 지연 대사 로그(익일 반영). + */ +#ifndef RC_DELAY_H +#define RC_DELAY_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_delay_rec_t; + +void RC_DELAY(TPSVCINFO *p); + +#endif /* RC_DELAY_H */ diff --git a/app/src/rc/svc/RC_DELAY.pgc b/app/src/rc/svc/RC_DELAY.pgc new file mode 100644 index 0000000..1984661 --- /dev/null +++ b/app/src/rc/svc/RC_DELAY.pgc @@ -0,0 +1,21 @@ +/* + * RC_DELAY.pgc - rc 모듈 서비스 RC_DELAY (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_DELAY.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DIFFRPT.h b/app/src/rc/svc/RC_DIFFRPT.h new file mode 100644 index 0000000..4d70d75 --- /dev/null +++ b/app/src/rc/svc/RC_DIFFRPT.h @@ -0,0 +1,21 @@ +/* + * RC_DIFFRPT.h - rc 서비스 RC_DIFFRPT 카피북 (copybook / record header). + * 당일 차이 라인 건수 조회. + */ +#ifndef RC_DIFFRPT_H +#define RC_DIFFRPT_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_diffrpt_rec_t; + +void RC_DIFFRPT(TPSVCINFO *p); + +#endif /* RC_DIFFRPT_H */ diff --git a/app/src/rc/svc/RC_DIFFRPT.pgc b/app/src/rc/svc/RC_DIFFRPT.pgc new file mode 100644 index 0000000..3cacc3c --- /dev/null +++ b/app/src/rc/svc/RC_DIFFRPT.pgc @@ -0,0 +1,27 @@ +/* + * RC_DIFFRPT.pgc - rc 모듈 서비스 RC_DIFFRPT (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_DIFFRPT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_EXCEPTION.h b/app/src/rc/svc/RC_EXCEPTION.h new file mode 100644 index 0000000..d65c7f8 --- /dev/null +++ b/app/src/rc/svc/RC_EXCEPTION.h @@ -0,0 +1,21 @@ +/* + * RC_EXCEPTION.h - rc 서비스 RC_EXCEPTION 카피북 (copybook / record header). + * 대사 예외 등록. + */ +#ifndef RC_EXCEPTION_H +#define RC_EXCEPTION_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_exception_rec_t; + +void RC_EXCEPTION(TPSVCINFO *p); + +#endif /* RC_EXCEPTION_H */ diff --git a/app/src/rc/svc/RC_EXCEPTION.pgc b/app/src/rc/svc/RC_EXCEPTION.pgc new file mode 100644 index 0000000..6772d85 --- /dev/null +++ b/app/src/rc/svc/RC_EXCEPTION.pgc @@ -0,0 +1,22 @@ +/* + * RC_EXCEPTION.pgc - rc 모듈 서비스 RC_EXCEPTION (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_EXCEPTION.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_HOLD.h b/app/src/rc/svc/RC_HOLD.h new file mode 100644 index 0000000..e26a681 --- /dev/null +++ b/app/src/rc/svc/RC_HOLD.h @@ -0,0 +1,21 @@ +/* + * RC_HOLD.h - rc 서비스 RC_HOLD 카피북 (copybook / record header). + * 대사 보류 원장 기록. (KEEP) + */ +#ifndef RC_HOLD_H +#define RC_HOLD_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_hold_rec_t; + +void RC_HOLD(TPSVCINFO *p); + +#endif /* RC_HOLD_H */ diff --git a/app/src/rc/svc/RC_HOLD.pgc b/app/src/rc/svc/RC_HOLD.pgc new file mode 100644 index 0000000..95fb3eb --- /dev/null +++ b/app/src/rc/svc/RC_HOLD.pgc @@ -0,0 +1,26 @@ +/* + * RC_HOLD.pgc - rc 모듈 서비스 RC_HOLD (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_HOLD.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_LEDGERLINK.h b/app/src/rc/svc/RC_LEDGERLINK.h new file mode 100644 index 0000000..8cd55ee --- /dev/null +++ b/app/src/rc/svc/RC_LEDGERLINK.h @@ -0,0 +1,21 @@ +/* + * RC_LEDGERLINK.h - rc 서비스 RC_LEDGERLINK 카피북 (copybook / record header). + * 대사 원장 연계 기록 (dbio). + */ +#ifndef RC_LEDGERLINK_H +#define RC_LEDGERLINK_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_ledgerlink_rec_t; + +void RC_LEDGERLINK(TPSVCINFO *p); + +#endif /* RC_LEDGERLINK_H */ diff --git a/app/src/rc/svc/RC_LEDGERLINK.pgc b/app/src/rc/svc/RC_LEDGERLINK.pgc new file mode 100644 index 0000000..5427165 --- /dev/null +++ b/app/src/rc/svc/RC_LEDGERLINK.pgc @@ -0,0 +1,19 @@ +/* + * RC_LEDGERLINK.pgc - rc 모듈 서비스 RC_LEDGERLINK (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_LEDGERLINK.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_MANUAL.h b/app/src/rc/svc/RC_MANUAL.h new file mode 100644 index 0000000..a81ee2f --- /dev/null +++ b/app/src/rc/svc/RC_MANUAL.h @@ -0,0 +1,21 @@ +/* + * RC_MANUAL.h - rc 서비스 RC_MANUAL 카피북 (copybook / record header). + * 수동 대사 로그. + */ +#ifndef RC_MANUAL_H +#define RC_MANUAL_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_manual_rec_t; + +void RC_MANUAL(TPSVCINFO *p); + +#endif /* RC_MANUAL_H */ diff --git a/app/src/rc/svc/RC_MANUAL.pgc b/app/src/rc/svc/RC_MANUAL.pgc new file mode 100644 index 0000000..8ac3ca1 --- /dev/null +++ b/app/src/rc/svc/RC_MANUAL.pgc @@ -0,0 +1,21 @@ +/* + * RC_MANUAL.pgc - rc 모듈 서비스 RC_MANUAL (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_MANUAL.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_PARTIAL.h b/app/src/rc/svc/RC_PARTIAL.h new file mode 100644 index 0000000..a487b32 --- /dev/null +++ b/app/src/rc/svc/RC_PARTIAL.h @@ -0,0 +1,21 @@ +/* + * RC_PARTIAL.h - rc 서비스 RC_PARTIAL 카피북 (copybook / record header). + * 부분 대사 로그. + */ +#ifndef RC_PARTIAL_H +#define RC_PARTIAL_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_partial_rec_t; + +void RC_PARTIAL(TPSVCINFO *p); + +#endif /* RC_PARTIAL_H */ diff --git a/app/src/rc/svc/RC_PARTIAL.pgc b/app/src/rc/svc/RC_PARTIAL.pgc new file mode 100644 index 0000000..c30d6ea --- /dev/null +++ b/app/src/rc/svc/RC_PARTIAL.pgc @@ -0,0 +1,21 @@ +/* + * RC_PARTIAL.pgc - rc 모듈 서비스 RC_PARTIAL (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_PARTIAL.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_PROGRESS.h b/app/src/rc/svc/RC_PROGRESS.h new file mode 100644 index 0000000..5030801 --- /dev/null +++ b/app/src/rc/svc/RC_PROGRESS.h @@ -0,0 +1,21 @@ +/* + * RC_PROGRESS.h - rc 서비스 RC_PROGRESS 카피북 (copybook / record header). + * 진행률(%) = MATCHED / 전체매입 * 100. + */ +#ifndef RC_PROGRESS_H +#define RC_PROGRESS_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_progress_rec_t; + +void RC_PROGRESS(TPSVCINFO *p); + +#endif /* RC_PROGRESS_H */ diff --git a/app/src/rc/svc/RC_PROGRESS.pgc b/app/src/rc/svc/RC_PROGRESS.pgc new file mode 100644 index 0000000..f9801e8 --- /dev/null +++ b/app/src/rc/svc/RC_PROGRESS.pgc @@ -0,0 +1,28 @@ +/* + * RC_PROGRESS.pgc - rc 모듈 서비스 RC_PROGRESS (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_PROGRESS.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RELEASE.h b/app/src/rc/svc/RC_RELEASE.h new file mode 100644 index 0000000..061273f --- /dev/null +++ b/app/src/rc/svc/RC_RELEASE.h @@ -0,0 +1,21 @@ +/* + * RC_RELEASE.h - rc 서비스 RC_RELEASE 카피북 (copybook / record header). + * 대사 보류 해제 (HOLD -> RELEASED). + */ +#ifndef RC_RELEASE_H +#define RC_RELEASE_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_release_rec_t; + +void RC_RELEASE(TPSVCINFO *p); + +#endif /* RC_RELEASE_H */ diff --git a/app/src/rc/svc/RC_RELEASE.pgc b/app/src/rc/svc/RC_RELEASE.pgc new file mode 100644 index 0000000..2e6041e --- /dev/null +++ b/app/src/rc/svc/RC_RELEASE.pgc @@ -0,0 +1,18 @@ +/* + * RC_RELEASE.pgc - rc 모듈 서비스 RC_RELEASE (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_RELEASE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_REMATCH.h b/app/src/rc/svc/RC_REMATCH.h new file mode 100644 index 0000000..e90b2c7 --- /dev/null +++ b/app/src/rc/svc/RC_REMATCH.h @@ -0,0 +1,21 @@ +/* + * RC_REMATCH.h - rc 서비스 RC_REMATCH 카피북 (copybook / record header). + * 재대사 원장 기록. (KEEP) + */ +#ifndef RC_REMATCH_H +#define RC_REMATCH_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_rematch_rec_t; + +void RC_REMATCH(TPSVCINFO *p); + +#endif /* RC_REMATCH_H */ diff --git a/app/src/rc/svc/RC_REMATCH.pgc b/app/src/rc/svc/RC_REMATCH.pgc new file mode 100644 index 0000000..f0a5da1 --- /dev/null +++ b/app/src/rc/svc/RC_REMATCH.pgc @@ -0,0 +1,26 @@ +/* + * RC_REMATCH.pgc - rc 모듈 서비스 RC_REMATCH (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_REMATCH.h" + +/* 9. RC_REMATCH - 재대사 원장 기록. (KEEP) */ +void RC_REMATCH(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, 'REMATCH', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_REPROC.h b/app/src/rc/svc/RC_REPROC.h new file mode 100644 index 0000000..62c4866 --- /dev/null +++ b/app/src/rc/svc/RC_REPROC.h @@ -0,0 +1,21 @@ +/* + * RC_REPROC.h - rc 서비스 RC_REPROC 카피북 (copybook / record header). + * 재처리: EXCEPTION 대사 로그를 MATCHED 로 되돌림. + */ +#ifndef RC_REPROC_H +#define RC_REPROC_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_reproc_rec_t; + +void RC_REPROC(TPSVCINFO *p); + +#endif /* RC_REPROC_H */ diff --git a/app/src/rc/svc/RC_REPROC.pgc b/app/src/rc/svc/RC_REPROC.pgc new file mode 100644 index 0000000..c016ce4 --- /dev/null +++ b/app/src/rc/svc/RC_REPROC.pgc @@ -0,0 +1,18 @@ +/* + * RC_REPROC.pgc - rc 모듈 서비스 RC_REPROC (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_REPROC.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RETURN.h b/app/src/rc/svc/RC_RETURN.h new file mode 100644 index 0000000..357406b --- /dev/null +++ b/app/src/rc/svc/RC_RETURN.h @@ -0,0 +1,21 @@ +/* + * RC_RETURN.h - rc 서비스 RC_RETURN 카피북 (copybook / record header). + * 반품 대사: 반품 원장 + 음수 대사 로그. + */ +#ifndef RC_RETURN_H +#define RC_RETURN_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_return_rec_t; + +void RC_RETURN(TPSVCINFO *p); + +#endif /* RC_RETURN_H */ diff --git a/app/src/rc/svc/RC_RETURN.pgc b/app/src/rc/svc/RC_RETURN.pgc new file mode 100644 index 0000000..5b2a44e --- /dev/null +++ b/app/src/rc/svc/RC_RETURN.pgc @@ -0,0 +1,22 @@ +/* + * RC_RETURN.pgc - rc 모듈 서비스 RC_RETURN (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_RETURN.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ROLLBACK.h b/app/src/rc/svc/RC_ROLLBACK.h new file mode 100644 index 0000000..667e63d --- /dev/null +++ b/app/src/rc/svc/RC_ROLLBACK.h @@ -0,0 +1,21 @@ +/* + * RC_ROLLBACK.h - rc 서비스 RC_ROLLBACK 카피북 (copybook / record header). + * 대사 롤백 (-> ROLLBACK 상태). + */ +#ifndef RC_ROLLBACK_H +#define RC_ROLLBACK_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_rollback_rec_t; + +void RC_ROLLBACK(TPSVCINFO *p); + +#endif /* RC_ROLLBACK_H */ diff --git a/app/src/rc/svc/RC_ROLLBACK.pgc b/app/src/rc/svc/RC_ROLLBACK.pgc new file mode 100644 index 0000000..f5ce309 --- /dev/null +++ b/app/src/rc/svc/RC_ROLLBACK.pgc @@ -0,0 +1,18 @@ +/* + * RC_ROLLBACK.pgc - rc 모듈 서비스 RC_ROLLBACK (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_ROLLBACK.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_STATS.h b/app/src/rc/svc/RC_STATS.h new file mode 100644 index 0000000..5ba57b3 --- /dev/null +++ b/app/src/rc/svc/RC_STATS.h @@ -0,0 +1,21 @@ +/* + * RC_STATS.h - rc 서비스 RC_STATS 카피북 (copybook / record header). + * MATCHED/EXCEPTION 통계 (dbio count). + */ +#ifndef RC_STATS_H +#define RC_STATS_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_stats_rec_t; + +void RC_STATS(TPSVCINFO *p); + +#endif /* RC_STATS_H */ diff --git a/app/src/rc/svc/RC_STATS.pgc b/app/src/rc/svc/RC_STATS.pgc new file mode 100644 index 0000000..b7b5bb9 --- /dev/null +++ b/app/src/rc/svc/RC_STATS.pgc @@ -0,0 +1,23 @@ +/* + * RC_STATS.pgc - rc 모듈 서비스 RC_STATS (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_STATS.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_STATUS.h b/app/src/rc/svc/RC_STATUS.h new file mode 100644 index 0000000..7beb61e --- /dev/null +++ b/app/src/rc/svc/RC_STATUS.h @@ -0,0 +1,21 @@ +/* + * RC_STATUS.h - rc 서비스 RC_STATUS 카피북 (copybook / record header). + * 최근 대사 상태 조회. + */ +#ifndef RC_STATUS_H +#define RC_STATUS_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_status_rec_t; + +void RC_STATUS(TPSVCINFO *p); + +#endif /* RC_STATUS_H */ diff --git a/app/src/rc/svc/RC_STATUS.pgc b/app/src/rc/svc/RC_STATUS.pgc new file mode 100644 index 0000000..fd3b24a --- /dev/null +++ b/app/src/rc/svc/RC_STATUS.pgc @@ -0,0 +1,25 @@ +/* + * RC_STATUS.pgc - rc 모듈 서비스 RC_STATUS (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_STATUS.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_SUMMARY.h b/app/src/rc/svc/RC_SUMMARY.h new file mode 100644 index 0000000..2878b8c --- /dev/null +++ b/app/src/rc/svc/RC_SUMMARY.h @@ -0,0 +1,21 @@ +/* + * RC_SUMMARY.h - rc 서비스 RC_SUMMARY 카피북 (copybook / record header). + * 대사 요약 upsert (dbio). + */ +#ifndef RC_SUMMARY_H +#define RC_SUMMARY_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_summary_rec_t; + +void RC_SUMMARY(TPSVCINFO *p); + +#endif /* RC_SUMMARY_H */ diff --git a/app/src/rc/svc/RC_SUMMARY.pgc b/app/src/rc/svc/RC_SUMMARY.pgc new file mode 100644 index 0000000..6476648 --- /dev/null +++ b/app/src/rc/svc/RC_SUMMARY.pgc @@ -0,0 +1,31 @@ +/* + * RC_SUMMARY.pgc - rc 모듈 서비스 RC_SUMMARY (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_SUMMARY.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_TOLERANCE.h b/app/src/rc/svc/RC_TOLERANCE.h new file mode 100644 index 0000000..921049c --- /dev/null +++ b/app/src/rc/svc/RC_TOLERANCE.h @@ -0,0 +1,21 @@ +/* + * RC_TOLERANCE.h - rc 서비스 RC_TOLERANCE 카피북 (copybook / record header). + * 허용오차 대사: |차이| <= 허용치면 MATCHED. + */ +#ifndef RC_TOLERANCE_H +#define RC_TOLERANCE_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_tolerance_rec_t; + +void RC_TOLERANCE(TPSVCINFO *p); + +#endif /* RC_TOLERANCE_H */ diff --git a/app/src/rc/svc/RC_TOLERANCE.pgc b/app/src/rc/svc/RC_TOLERANCE.pgc new file mode 100644 index 0000000..ac52a7e --- /dev/null +++ b/app/src/rc/svc/RC_TOLERANCE.pgc @@ -0,0 +1,26 @@ +/* + * RC_TOLERANCE.pgc - rc 모듈 서비스 RC_TOLERANCE (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_TOLERANCE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_UNMATCH.h b/app/src/rc/svc/RC_UNMATCH.h new file mode 100644 index 0000000..31921c8 --- /dev/null +++ b/app/src/rc/svc/RC_UNMATCH.h @@ -0,0 +1,21 @@ +/* + * RC_UNMATCH.h - rc 서비스 RC_UNMATCH 카피북 (copybook / record header). + * 미대사 건수 조회. (KEEP) + */ +#ifndef RC_UNMATCH_H +#define RC_UNMATCH_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_rc_unmatch_rec_t; + +void RC_UNMATCH(TPSVCINFO *p); + +#endif /* RC_UNMATCH_H */ diff --git a/app/src/rc/svc/RC_UNMATCH.pgc b/app/src/rc/svc/RC_UNMATCH.pgc new file mode 100644 index 0000000..d7f849e --- /dev/null +++ b/app/src/rc/svc/RC_UNMATCH.pgc @@ -0,0 +1,27 @@ +/* + * RC_UNMATCH.pgc - rc 모듈 서비스 RC_UNMATCH (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RC_UNMATCH.h" + +/* 11. RC_UNMATCH - 미대사 건수 조회. (KEEP) */ +void RC_UNMATCH(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 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) FAIL(b); + { long v = h_cnt; Bchg(b, T_COUNT, 0, (char *)&v, 0L); } + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RECONCILE.h b/app/src/rc/svc/RECONCILE.h new file mode 100644 index 0000000..0b2470f --- /dev/null +++ b/app/src/rc/svc/RECONCILE.h @@ -0,0 +1,21 @@ +/* + * RECONCILE.h - rc 서비스 RECONCILE 카피북 (copybook / record header). + * + */ +#ifndef RECONCILE_H +#define RECONCILE_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} rc_reconcile_rec_t; + +void RECONCILE(TPSVCINFO *p); + +#endif /* RECONCILE_H */ diff --git a/app/src/rc/svc/RECONCILE.pgc b/app/src/rc/svc/RECONCILE.pgc new file mode 100644 index 0000000..eb10c66 --- /dev/null +++ b/app/src/rc/svc/RECONCILE.pgc @@ -0,0 +1,41 @@ +/* + * RECONCILE.pgc - rc 모듈 서비스 RECONCILE (one service = one file). + * Moved verbatim from the former inline rc_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by rc_svr at tpsvrinit. + */ +#include "RECONCILE.h" + +/* ========================================================================= */ +void RECONCILE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rlen = 0; + 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; + + /* own-branch write: recon ledger evidence only */ + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'RECON', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) { + userlog("RECONCILE INSERT ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + FAIL(b); + } + Bchg(b, T_STATUS, 0, "M", 0L); + 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)); + FAIL(b); + } + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/rc_svc.h b/app/src/rc/svc/rc_svc.h new file mode 100644 index 0000000..9e8e537 --- /dev/null +++ b/app/src/rc/svc/rc_svc.h @@ -0,0 +1,32 @@ +/* + * rc_svc.h - rc (대사) 서비스 공통 카피북 (shared copybook for all rc svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids and the rc dbio layer (librcdbio.a), + * and provides the tiny UBF helpers + tpreturn macros every service uses. No + * business logic and no service function lives here. + */ +#ifndef RC_SVC_H +#define RC_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "rc_dbio.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* RC_SVC_H */ diff --git a/app/src/st/batch/st_adjust_batch.pgc b/app/src/st/batch/st_adjust_batch.pgc new file mode 100644 index 0000000..fe76dee --- /dev/null +++ b/app/src/st/batch/st_adjust_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_adjust_batch.pgc - 정산 조정 집계 (XA 배치). + * 당일 조정 delta 총합. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_adjust_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_delta; + 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 SELECT coalesce(sum(delta),0) INTO :h_delta FROM st_adjust WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_adjust_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_adjust_batch COMMIT: bizdate=%s delta=%ld\n", bizdate, h_delta); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_cancelpurge_batch.pgc b/app/src/st/batch/st_cancelpurge_batch.pgc new file mode 100644 index 0000000..074ba9b --- /dev/null +++ b/app/src/st/batch/st_cancelpurge_batch.pgc @@ -0,0 +1,34 @@ +/* + * st_cancelpurge_batch.pgc - 취소 지급 정리 (XA 배치). + * CANCELLED 지급 레코드를 삭제. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_cancelpurge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 DELETE FROM st_payment WHERE status='CANCELLED' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_cancelpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_cancelpurge_batch COMMIT: bizdate=%s purged rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_confirm_batch.pgc b/app/src/st/batch/st_confirm_batch.pgc new file mode 100644 index 0000000..0ce13a2 --- /dev/null +++ b/app/src/st/batch/st_confirm_batch.pgc @@ -0,0 +1,34 @@ +/* + * st_confirm_batch.pgc - 정산 일괄 확정 (XA 배치). + * SETTLED 정산을 CONFIRMED 로 일괄 확정. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_confirm_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE settlement SET status='CONFIRMED' WHERE status='SETTLED' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_confirm_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_confirm_batch COMMIT: bizdate=%s confirmed rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_delayint_batch.pgc b/app/src/st/batch/st_delayint_batch.pgc new file mode 100644 index 0000000..d8dd79e --- /dev/null +++ b/app/src/st/batch/st_delayint_batch.pgc @@ -0,0 +1,50 @@ +/* + * st_delayint_batch.pgc - 지연이자 정산 배치 (XA 배치). + * 미완결 지급을 커서로 순회하여 지연이자 정산 상세를 기록. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_delayint_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + long h_pid, h_net; + char h_merch[64]; + 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 st_delayint_batch_c CURSOR FOR + SELECT purchase_id, merchant_id, net FROM settlement WHERE biz_date=:h_bizdate AND status='SETTLED'; + EXEC SQL OPEN st_delayint_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH st_delayint_batch_c INTO :h_pid, :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 st_delayint_batch_c; tpabort(0); return 1; } + { + long intr = h_net * 15 / 10000; + if (stdb_insert_fee_dtl(0, h_pid, h_merch, "DELAYINT", intr, bizdate) < 0) { EXEC SQL CLOSE st_delayint_batch_c; tpabort(0); return 1; } + rows++; + } + } + EXEC SQL CLOSE st_delayint_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_delayint_batch COMMIT: bizdate=%s delayint_rows=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_feepct_batch.pgc b/app/src/st/batch/st_feepct_batch.pgc new file mode 100644 index 0000000..c9efa5f --- /dev/null +++ b/app/src/st/batch/st_feepct_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_feepct_batch.pgc - 수수료율 집계 (XA 배치). + * 매입 수수료/매입금액 비율(bps). + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_feepct_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee, h_gross; + 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 SELECT coalesce(sum(fee),0), coalesce(sum(amount),0) INTO :h_fee, :h_gross FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_feepct_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_feepct_batch COMMIT: bizdate=%s fee=%ld gross=%ld\n", bizdate, h_fee, h_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_hold_batch.pgc b/app/src/st/batch/st_hold_batch.pgc new file mode 100644 index 0000000..9c75fd4 --- /dev/null +++ b/app/src/st/batch/st_hold_batch.pgc @@ -0,0 +1,34 @@ +/* + * st_hold_batch.pgc - 지급 일괄 보류 (XA 배치). + * SCHEDULED 지급을 HELD 로 일괄 전환. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_hold_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE st_payment SET status='HELD' WHERE status='SCHEDULED' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_hold_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_hold_batch COMMIT: bizdate=%s held rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_mdr_sum_batch.pgc b/app/src/st/batch/st_mdr_sum_batch.pgc new file mode 100644 index 0000000..a4e31e4 --- /dev/null +++ b/app/src/st/batch/st_mdr_sum_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_mdr_sum_batch.pgc - MDR 수수료 집계 (XA 배치). + * MDR 수수료 상세 합계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_mdr_sum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_mdr; + 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 SELECT coalesce(sum(amount) FILTER (WHERE fee_type='MDR'),0) INTO :h_mdr FROM st_settle_dtl WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_mdr_sum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_mdr_sum_batch COMMIT: bizdate=%s mdr=%ld\n", bizdate, h_mdr); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_merchroll_batch.pgc b/app/src/st/batch/st_merchroll_batch.pgc new file mode 100644 index 0000000..4ed5946 --- /dev/null +++ b/app/src/st/batch/st_merchroll_batch.pgc @@ -0,0 +1,49 @@ +/* + * st_merchroll_batch.pgc - 가맹점 정산 롤업 (XA 배치). + * 가맹점별 매입/수수료/net 을 집계해 st_merch_settle 에 upsert. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_merchroll_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + long h_cnt, h_gross, h_fee, h_net; + char h_merch[64]; + 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 st_merchroll_batch_c 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 st_merchroll_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH st_merchroll_batch_c 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 st_merchroll_batch_c; tpabort(0); return 1; } + if (stdb_upsert_merch_settle(h_merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) { EXEC SQL CLOSE st_merchroll_batch_c; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE st_merchroll_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_merchroll_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_monthly_batch.pgc b/app/src/st/batch/st_monthly_batch.pgc new file mode 100644 index 0000000..4fccc59 --- /dev/null +++ b/app/src/st/batch/st_monthly_batch.pgc @@ -0,0 +1,36 @@ +/* + * st_monthly_batch.pgc - 월별 정산 집계 (XA 배치). + * 당월(YYYY-MM) 정산 net 합계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_monthly_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_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 SELECT coalesce(sum(net),0) INTO :h_sum FROM settlement + WHERE to_char(biz_date, 'YYYY-MM') = to_char(:h_bizdate::date, 'YYYY-MM'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_monthly_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_monthly_batch COMMIT: bizdate=%s monthly_net=%ld\n", bizdate, h_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_netting_batch.pgc b/app/src/st/batch/st_netting_batch.pgc new file mode 100644 index 0000000..4089ec8 --- /dev/null +++ b/app/src/st/batch/st_netting_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_netting_batch.pgc - 정산 네팅 집계 (XA 배치). + * 정산 건수/net/최대 정산 통계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_netting_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_net, h_max; + 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 SELECT count(*), coalesce(sum(net),0), coalesce(max(net),0) INTO :h_cnt, :h_net, :h_max FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_netting_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_netting_batch COMMIT: bizdate=%s cnt=%ld net=%ld max=%ld\n", bizdate, h_cnt, h_net, h_max); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_offset_batch.pgc b/app/src/st/batch/st_offset_batch.pgc new file mode 100644 index 0000000..df84fbf --- /dev/null +++ b/app/src/st/batch/st_offset_batch.pgc @@ -0,0 +1,53 @@ +/* + * st_offset_batch.pgc - 상계 정산 배치 (XA 배치). + * 정산 net vs 매입 net 차이를 커서로 순회하여 상계 조정 기록. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_offset_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + long h_pid, h_snet, h_pnet; + 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 st_offset_batch_c CURSOR FOR + SELECT s.purchase_id, coalesce(sum(s.net),0), coalesce(max(p.net),0) + FROM settlement s JOIN purchase p ON p.purchase_id=s.purchase_id + WHERE s.biz_date=:h_bizdate GROUP BY s.purchase_id; + EXEC SQL OPEN st_offset_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH st_offset_batch_c INTO :h_pid, :h_snet, :h_pnet; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE st_offset_batch_c; tpabort(0); return 1; } + { + long d = h_pnet - h_snet; + if (d != 0) { + if (stdb_insert_adjust(0, h_pid, d, "OFFSET", bizdate) < 0) { EXEC SQL CLOSE st_offset_batch_c; tpabort(0); return 1; } + rows++; + } + } + } + EXEC SQL CLOSE st_offset_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_offset_batch COMMIT: bizdate=%s offsets=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_payment_batch.pgc b/app/src/st/batch/st_payment_batch.pgc new file mode 100644 index 0000000..2373461 --- /dev/null +++ b/app/src/st/batch/st_payment_batch.pgc @@ -0,0 +1,47 @@ +/* + * st_payment_batch.pgc - 가맹점 지급 생성 (XA 배치). + * 가맹점별 정산 net 을 커서로 순회하여 지급(SCHEDULED) 레코드 생성. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_payment_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#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]; + long h_net; + char h_merch[64]; + 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 st_payment_batch_c CURSOR FOR + SELECT merchant_id, coalesce(sum(net),0) FROM settlement WHERE biz_date=:h_bizdate GROUP BY merchant_id ORDER BY merchant_id; + EXEC SQL OPEN st_payment_batch_c; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH st_payment_batch_c INTO :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 st_payment_batch_c; tpabort(0); return 1; } + if (stdb_insert_payment(h_merch, h_net, bizdate, "SCHEDULED", bizdate) < 0) { EXEC SQL CLOSE st_payment_batch_c; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE st_payment_batch_c; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_payment_batch COMMIT: bizdate=%s payments=%ld\n", bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_release_batch.pgc b/app/src/st/batch/st_release_batch.pgc new file mode 100644 index 0000000..c7f8355 --- /dev/null +++ b/app/src/st/batch/st_release_batch.pgc @@ -0,0 +1,34 @@ +/* + * st_release_batch.pgc - 지급 보류 일괄 해제 (XA 배치). + * HELD 지급을 SCHEDULED 로 일괄 복원. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_release_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + 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 UPDATE st_payment SET status='SCHEDULED' WHERE status='HELD' AND biz_date=:h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_release_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_release_batch COMMIT: bizdate=%s released rows=%d\n", bizdate, (int)sqlca.sqlerrd[2]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_report_batch.pgc b/app/src/st/batch/st_report_batch.pgc new file mode 100644 index 0000000..5c5c026 --- /dev/null +++ b/app/src/st/batch/st_report_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_report_batch.pgc - 정산 리포트 집계 (XA 배치). + * 당일 정산 건수/net 합계 리포트. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_report_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, 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 SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_net FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_report_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_report_batch COMMIT: bizdate=%s settlements=%ld net=%ld\n", bizdate, h_cnt, h_net); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_vat_sum_batch.pgc b/app/src/st/batch/st_vat_sum_batch.pgc new file mode 100644 index 0000000..2225aab --- /dev/null +++ b/app/src/st/batch/st_vat_sum_batch.pgc @@ -0,0 +1,35 @@ +/* + * st_vat_sum_batch.pgc - 부가세 집계 (XA 배치). + * VAT 수수료 상세 합계. + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_vat_sum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_vat; + 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 SELECT coalesce(sum(amount),0) INTO :h_vat FROM st_settle_dtl WHERE fee_type = 'VAT' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "st_vat_sum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> st_vat_sum_batch COMMIT: bizdate=%s vat=%ld\n", bizdate, h_vat); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/dbio/st_adjdelta_dbio.h b/app/src/st/dbio/st_adjdelta_dbio.h new file mode 100644 index 0000000..57b0c3f --- /dev/null +++ b/app/src/st/dbio/st_adjdelta_dbio.h @@ -0,0 +1,13 @@ +/* + * st_adjdelta_dbio.h - st 모듈 DB 접근 계층 copybook (st_adjdelta_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_ADJDELTA_DBIO +#define ST_ADJDELTA_DBIO + +typedef struct { char biz_date[16]; long delta; } st_adjdelta_rec_t; + +long stdb_adjust_delta_total(const char *bizdate); + +#endif /* ST_ADJDELTA_DBIO */ diff --git a/app/src/st/dbio/st_adjdelta_dbio.pgc b/app/src/st/dbio/st_adjdelta_dbio.pgc new file mode 100644 index 0000000..fd54f5a --- /dev/null +++ b/app/src/st/dbio/st_adjdelta_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_adjdelta_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_adjdelta_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 조정 증감(delta) 총합 - 음수 포함. + */ +#include +#include +#include "st_adjdelta_dbio.h" + +long stdb_adjust_delta_total(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(delta),0) INTO :h_v FROM st_adjust WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_adjust_delta_total FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_confirmall_dbio.h b/app/src/st/dbio/st_confirmall_dbio.h new file mode 100644 index 0000000..33fae70 --- /dev/null +++ b/app/src/st/dbio/st_confirmall_dbio.h @@ -0,0 +1,13 @@ +/* + * st_confirmall_dbio.h - st 모듈 DB 접근 계층 copybook (st_confirmall_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_CONFIRMALL_DBIO +#define ST_CONFIRMALL_DBIO + +typedef struct { char biz_date[16]; long affected; } st_confirmall_rec_t; + +long stdb_confirm_all(const char *bizdate); + +#endif /* ST_CONFIRMALL_DBIO */ diff --git a/app/src/st/dbio/st_confirmall_dbio.pgc b/app/src/st/dbio/st_confirmall_dbio.pgc new file mode 100644 index 0000000..695d79b --- /dev/null +++ b/app/src/st/dbio/st_confirmall_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_confirmall_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_confirmall_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 정산 일괄 확정 - UPDATE 후 영향 행수 반환. + */ +#include +#include +#include "st_confirmall_dbio.h" + +long stdb_confirm_all(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 UPDATE settlement SET status = 'CONFIRMED' + WHERE biz_date = :h_bizdate AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) { userlog("stdb_confirm_all FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long)sqlca.sqlerrd[2]; +} diff --git a/app/src/st/dbio/st_confirmratio_dbio.h b/app/src/st/dbio/st_confirmratio_dbio.h new file mode 100644 index 0000000..707773c --- /dev/null +++ b/app/src/st/dbio/st_confirmratio_dbio.h @@ -0,0 +1,13 @@ +/* + * st_confirmratio_dbio.h - st 모듈 DB 접근 계층 copybook (st_confirmratio_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_CONFIRMRATIO_DBIO +#define ST_CONFIRMRATIO_DBIO + +typedef struct { char biz_date[16]; long pct; } st_confirmratio_rec_t; + +long stdb_confirm_pct(const char *bizdate); + +#endif /* ST_CONFIRMRATIO_DBIO */ diff --git a/app/src/st/dbio/st_confirmratio_dbio.pgc b/app/src/st/dbio/st_confirmratio_dbio.pgc new file mode 100644 index 0000000..7e170a2 --- /dev/null +++ b/app/src/st/dbio/st_confirmratio_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_confirmratio_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_confirmratio_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 확정율(%) - CASE 로 SETTLED+CONFIRMED 분모 방지. + */ +#include +#include +#include "st_confirmratio_dbio.h" + +long stdb_confirm_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_conf = 0, h_all = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + count(*) FILTER (WHERE status = 'CONFIRMED'), count(*) + INTO :h_conf, :h_all FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_confirm_pct FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_all > 0 ? (h_conf * 100 / h_all) : 0; +} diff --git a/app/src/st/dbio/st_derived_dbio.h b/app/src/st/dbio/st_derived_dbio.h new file mode 100644 index 0000000..d92613d --- /dev/null +++ b/app/src/st/dbio/st_derived_dbio.h @@ -0,0 +1,13 @@ +/* + * st_derived_dbio.h - st 모듈 DB 접근 계층 copybook (st_derived_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_DERIVED_DBIO +#define ST_DERIVED_DBIO + +typedef struct { char biz_date[16]; long top_merch_net; } st_derived_rec_t; + +long stdb_max_merchant_net(const char *bizdate); + +#endif /* ST_DERIVED_DBIO */ diff --git a/app/src/st/dbio/st_derived_dbio.pgc b/app/src/st/dbio/st_derived_dbio.pgc new file mode 100644 index 0000000..7a7344e --- /dev/null +++ b/app/src/st/dbio/st_derived_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_derived_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_derived_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 파생 테이블(FROM 서브쿼리) - 가맹점별 net 합의 최대값. + */ +#include +#include +#include "st_derived_dbio.h" + +long stdb_max_merchant_net(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(mnet),0) INTO :h_v FROM ( + SELECT merchant_id, coalesce(sum(net),0) AS mnet FROM settlement + WHERE biz_date = :h_bizdate GROUP BY merchant_id) t; + if (sqlca.sqlcode < 0) { userlog("stdb_max_merchant_net FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_distinctmerch_dbio.h b/app/src/st/dbio/st_distinctmerch_dbio.h new file mode 100644 index 0000000..416f490 --- /dev/null +++ b/app/src/st/dbio/st_distinctmerch_dbio.h @@ -0,0 +1,13 @@ +/* + * st_distinctmerch_dbio.h - st 모듈 DB 접근 계층 copybook (st_distinctmerch_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_DISTINCTMERCH_DBIO +#define ST_DISTINCTMERCH_DBIO + +typedef struct { char biz_date[16]; long uniq; } st_distinctmerch_rec_t; + +long stdb_distinct_merchants(const char *bizdate); + +#endif /* ST_DISTINCTMERCH_DBIO */ diff --git a/app/src/st/dbio/st_distinctmerch_dbio.pgc b/app/src/st/dbio/st_distinctmerch_dbio.pgc new file mode 100644 index 0000000..68bdcd0 --- /dev/null +++ b/app/src/st/dbio/st_distinctmerch_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_distinctmerch_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_distinctmerch_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 정산 대상 고유 가맹점 수 - count(distinct). + */ +#include +#include +#include "st_distinctmerch_dbio.h" + +long stdb_distinct_merchants(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT merchant_id) INTO :h_v FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_dtlfeesum_dbio.h b/app/src/st/dbio/st_dtlfeesum_dbio.h new file mode 100644 index 0000000..d3e5d2c --- /dev/null +++ b/app/src/st/dbio/st_dtlfeesum_dbio.h @@ -0,0 +1,13 @@ +/* + * st_dtlfeesum_dbio.h - st 모듈 DB 접근 계층 copybook (st_dtlfeesum_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_DTLFEESUM_DBIO +#define ST_DTLFEESUM_DBIO + +typedef struct { char biz_date[16]; char fee_type[16]; long amount; } st_dtlfeesum_rec_t; + +long stdb_dtl_fee_sum(const char *bizdate, const char *fee_type); + +#endif /* ST_DTLFEESUM_DBIO */ diff --git a/app/src/st/dbio/st_dtlfeesum_dbio.pgc b/app/src/st/dbio/st_dtlfeesum_dbio.pgc new file mode 100644 index 0000000..5ea693d --- /dev/null +++ b/app/src/st/dbio/st_dtlfeesum_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_dtlfeesum_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_dtlfeesum_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 수수료 종류별 정산 상세 금액 합계. + */ +#include +#include +#include "st_dtlfeesum_dbio.h" + +long stdb_dtl_fee_sum(const char *bizdate, const char *fee_type) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_type, fee_type, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM st_settle_dtl + WHERE biz_date = :h_bizdate AND fee_type = :h_type; + if (sqlca.sqlcode < 0) { userlog("stdb_dtl_fee_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_dtlgroup_dbio.h b/app/src/st/dbio/st_dtlgroup_dbio.h new file mode 100644 index 0000000..85e26f7 --- /dev/null +++ b/app/src/st/dbio/st_dtlgroup_dbio.h @@ -0,0 +1,13 @@ +/* + * st_dtlgroup_dbio.h - st 모듈 DB 접근 계층 copybook (st_dtlgroup_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_DTLGROUP_DBIO +#define ST_DTLGROUP_DBIO + +typedef struct { char fee_type[16]; long amount; } st_dtlgroup_rec_t; + +long stdb_dtl_fee_kinds(const char *bizdate); + +#endif /* ST_DTLGROUP_DBIO */ diff --git a/app/src/st/dbio/st_dtlgroup_dbio.pgc b/app/src/st/dbio/st_dtlgroup_dbio.pgc new file mode 100644 index 0000000..f0264f2 --- /dev/null +++ b/app/src/st/dbio/st_dtlgroup_dbio.pgc @@ -0,0 +1,30 @@ +/* + * st_dtlgroup_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_dtlgroup_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 수수료 종류별 GROUP BY 커서 - 종류 수 반환. + */ +#include +#include +#include "st_dtlgroup_dbio.h" + +long stdb_dtl_fee_kinds(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_sum, kinds = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE dgc CURSOR FOR + SELECT fee_type, coalesce(sum(amount),0) FROM st_settle_dtl + WHERE biz_date = :h_bizdate GROUP BY fee_type ORDER BY fee_type; + EXEC SQL OPEN dgc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH dgc INTO :h_type, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dgc; return -1; } + kinds++; + } + EXEC SQL CLOSE dgc; + return kinds; +} diff --git a/app/src/st/dbio/st_existsadj_dbio.h b/app/src/st/dbio/st_existsadj_dbio.h new file mode 100644 index 0000000..b3451de --- /dev/null +++ b/app/src/st/dbio/st_existsadj_dbio.h @@ -0,0 +1,13 @@ +/* + * st_existsadj_dbio.h - st 모듈 DB 접근 계층 copybook (st_existsadj_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_EXISTSADJ_DBIO +#define ST_EXISTSADJ_DBIO + +typedef struct { long purchase_id; int has_adj; } st_existsadj_rec_t; + +int stdb_has_adjust(long pid); + +#endif /* ST_EXISTSADJ_DBIO */ diff --git a/app/src/st/dbio/st_existsadj_dbio.pgc b/app/src/st/dbio/st_existsadj_dbio.pgc new file mode 100644 index 0000000..8e4b41f --- /dev/null +++ b/app/src/st/dbio/st_existsadj_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_existsadj_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_existsadj_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 매입에 정산 조정 존재 여부 - EXISTS boolean. + */ +#include +#include +#include "st_existsadj_dbio.h" + +int stdb_has_adjust(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid; + int h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT CASE WHEN EXISTS + (SELECT 1 FROM st_adjust WHERE purchase_id = :h_pid) THEN 1 ELSE 0 END INTO :h_v; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_feefilter_dbio.h b/app/src/st/dbio/st_feefilter_dbio.h new file mode 100644 index 0000000..6148e6e --- /dev/null +++ b/app/src/st/dbio/st_feefilter_dbio.h @@ -0,0 +1,13 @@ +/* + * st_feefilter_dbio.h - st 모듈 DB 접근 계층 copybook (st_feefilter_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_FEEFILTER_DBIO +#define ST_FEEFILTER_DBIO + +typedef struct { char biz_date[16]; long mdr; long van; } st_feefilter_rec_t; + +long stdb_fee_mdr_filter(const char *bizdate); + +#endif /* ST_FEEFILTER_DBIO */ diff --git a/app/src/st/dbio/st_feefilter_dbio.pgc b/app/src/st/dbio/st_feefilter_dbio.pgc new file mode 100644 index 0000000..2530efc --- /dev/null +++ b/app/src/st/dbio/st_feefilter_dbio.pgc @@ -0,0 +1,24 @@ +/* + * st_feefilter_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_feefilter_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * MDR/VAN FILTER 분리 집계 - MDR 합 반환. + */ +#include +#include +#include "st_feefilter_dbio.h" + +long stdb_fee_mdr_filter(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_mdr = 0, h_van = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(amount) FILTER (WHERE fee_type = 'MDR'), 0), + coalesce(sum(amount) FILTER (WHERE fee_type = 'VAN'), 0) + INTO :h_mdr, :h_van FROM st_settle_dtl WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + userlog("stdb_fee_mdr_filter van=%ld", h_van); + return h_mdr; +} diff --git a/app/src/st/dbio/st_feepct_dbio.h b/app/src/st/dbio/st_feepct_dbio.h new file mode 100644 index 0000000..767f1c3 --- /dev/null +++ b/app/src/st/dbio/st_feepct_dbio.h @@ -0,0 +1,13 @@ +/* + * st_feepct_dbio.h - st 모듈 DB 접근 계층 copybook (st_feepct_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_FEEPCT_DBIO +#define ST_FEEPCT_DBIO + +typedef struct { char biz_date[16]; long pct; } st_feepct_rec_t; + +long stdb_fee_pct_of_gross(const char *bizdate); + +#endif /* ST_FEEPCT_DBIO */ diff --git a/app/src/st/dbio/st_feepct_dbio.pgc b/app/src/st/dbio/st_feepct_dbio.pgc new file mode 100644 index 0000000..600311a --- /dev/null +++ b/app/src/st/dbio/st_feepct_dbio.pgc @@ -0,0 +1,23 @@ +/* + * st_feepct_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_feepct_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 수수료율(%) = 수수료합/매입금액합 - 두 서브쿼리 비율. + */ +#include +#include +#include "st_feepct_dbio.h" + +long stdb_fee_pct_of_gross(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee = 0, h_gross = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + (SELECT coalesce(sum(fee),0) FROM purchase WHERE biz_date = :h_bizdate), + (SELECT coalesce(sum(amount),0) FROM purchase WHERE biz_date = :h_bizdate) + INTO :h_fee, :h_gross; + if (sqlca.sqlcode < 0) { userlog("stdb_fee_pct_of_gross FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_gross > 0 ? (h_fee * 10000 / h_gross) : 0; +} diff --git a/app/src/st/dbio/st_heldcursor_dbio.h b/app/src/st/dbio/st_heldcursor_dbio.h new file mode 100644 index 0000000..b7db40b --- /dev/null +++ b/app/src/st/dbio/st_heldcursor_dbio.h @@ -0,0 +1,13 @@ +/* + * st_heldcursor_dbio.h - st 모듈 DB 접근 계층 copybook (st_heldcursor_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_HELDCURSOR_DBIO +#define ST_HELDCURSOR_DBIO + +typedef struct { char biz_date[16]; long held; } st_heldcursor_rec_t; + +long stdb_held_payment_accum(const char *bizdate); + +#endif /* ST_HELDCURSOR_DBIO */ diff --git a/app/src/st/dbio/st_heldcursor_dbio.pgc b/app/src/st/dbio/st_heldcursor_dbio.pgc new file mode 100644 index 0000000..0ebb82c --- /dev/null +++ b/app/src/st/dbio/st_heldcursor_dbio.pgc @@ -0,0 +1,29 @@ +/* + * st_heldcursor_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_heldcursor_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 보류(HELD) 지급액 커서 누적 합계. + */ +#include +#include +#include "st_heldcursor_dbio.h" + +long stdb_held_payment_accum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_amt, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE hpc CURSOR FOR + SELECT net_amount FROM st_payment WHERE biz_date = :h_bizdate AND status = 'HELD'; + EXEC SQL OPEN hpc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH hpc INTO :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE hpc; return -1; } + total += h_amt; + } + EXEC SQL CLOSE hpc; + return total; +} diff --git a/app/src/st/dbio/st_joingross_dbio.h b/app/src/st/dbio/st_joingross_dbio.h new file mode 100644 index 0000000..b08f1a0 --- /dev/null +++ b/app/src/st/dbio/st_joingross_dbio.h @@ -0,0 +1,13 @@ +/* + * st_joingross_dbio.h - st 모듈 DB 접근 계층 copybook (st_joingross_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_JOINGROSS_DBIO +#define ST_JOINGROSS_DBIO + +typedef struct { char biz_date[16]; long gross; } st_joingross_rec_t; + +long stdb_settle_gross_join(const char *bizdate); + +#endif /* ST_JOINGROSS_DBIO */ diff --git a/app/src/st/dbio/st_joingross_dbio.pgc b/app/src/st/dbio/st_joingross_dbio.pgc new file mode 100644 index 0000000..8fb4bac --- /dev/null +++ b/app/src/st/dbio/st_joingross_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_joingross_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_joingross_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 정산-매입 JOIN 총 매입금액(gross) 합계. + */ +#include +#include +#include "st_joingross_dbio.h" + +long stdb_settle_gross_join(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(p.amount),0) INTO :h_v + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE s.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_settle_gross_join FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_laststatus_dbio.h b/app/src/st/dbio/st_laststatus_dbio.h new file mode 100644 index 0000000..68267e8 --- /dev/null +++ b/app/src/st/dbio/st_laststatus_dbio.h @@ -0,0 +1,13 @@ +/* + * st_laststatus_dbio.h - st 모듈 DB 접근 계층 copybook (st_laststatus_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_LASTSTATUS_DBIO +#define ST_LASTSTATUS_DBIO + +typedef struct { long purchase_id; char status[16]; } st_laststatus_rec_t; + +int stdb_last_status(long pid, char *out); + +#endif /* ST_LASTSTATUS_DBIO */ diff --git a/app/src/st/dbio/st_laststatus_dbio.pgc b/app/src/st/dbio/st_laststatus_dbio.pgc new file mode 100644 index 0000000..f890dca --- /dev/null +++ b/app/src/st/dbio/st_laststatus_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_laststatus_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_laststatus_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 최근 정산 상태 1건 - ORDER BY LIMIT single fetch (문자열 out). + */ +#include +#include +#include "st_laststatus_dbio.h" + +int stdb_last_status(long pid, char *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid; + char h_status[16]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT status INTO :h_status FROM settlement + WHERE purchase_id = :h_pid ORDER BY settlement_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { strcpy(out, "NONE"); return 0; } + if (sqlca.sqlcode < 0) { userlog("stdb_last_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strncpy(out, h_status, 15); out[15] = 0; + return 0; +} diff --git a/app/src/st/dbio/st_merchhaving_dbio.h b/app/src/st/dbio/st_merchhaving_dbio.h new file mode 100644 index 0000000..f4f2b09 --- /dev/null +++ b/app/src/st/dbio/st_merchhaving_dbio.h @@ -0,0 +1,13 @@ +/* + * st_merchhaving_dbio.h - st 모듈 DB 접근 계층 copybook (st_merchhaving_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_MERCHHAVING_DBIO +#define ST_MERCHHAVING_DBIO + +typedef struct { char merchant_id[64]; long net; } st_merchhaving_rec_t; + +long stdb_big_merchants(const char *bizdate, long threshold); + +#endif /* ST_MERCHHAVING_DBIO */ diff --git a/app/src/st/dbio/st_merchhaving_dbio.pgc b/app/src/st/dbio/st_merchhaving_dbio.pgc new file mode 100644 index 0000000..978d2df --- /dev/null +++ b/app/src/st/dbio/st_merchhaving_dbio.pgc @@ -0,0 +1,30 @@ +/* + * st_merchhaving_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_merchhaving_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * HAVING 커서 - 임계 net 이상 가맹점 수. + */ +#include +#include +#include "st_merchhaving_dbio.h" + +long stdb_big_merchants(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_thr = threshold, h_net, cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE mhc CURSOR FOR + SELECT merchant_id, coalesce(sum(net),0) FROM settlement + WHERE biz_date = :h_bizdate GROUP BY merchant_id HAVING coalesce(sum(net),0) >= :h_thr; + EXEC SQL OPEN mhc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mhc INTO :h_merch, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mhc; return -1; } + cnt++; + } + EXEC SQL CLOSE mhc; + return cnt; +} diff --git a/app/src/st/dbio/st_minband_dbio.h b/app/src/st/dbio/st_minband_dbio.h new file mode 100644 index 0000000..d37fff3 --- /dev/null +++ b/app/src/st/dbio/st_minband_dbio.h @@ -0,0 +1,13 @@ +/* + * st_minband_dbio.h - st 모듈 DB 접근 계층 copybook (st_minband_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_MINBAND_DBIO +#define ST_MINBAND_DBIO + +typedef struct { char fee_type[16]; long min_bps; } st_minband_rec_t; + +long stdb_min_band_rate(const char *fee_type); + +#endif /* ST_MINBAND_DBIO */ diff --git a/app/src/st/dbio/st_minband_dbio.pgc b/app/src/st/dbio/st_minband_dbio.pgc new file mode 100644 index 0000000..23db9a6 --- /dev/null +++ b/app/src/st/dbio/st_minband_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_minband_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_minband_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 수수료 유형의 최저 구간 요율(bps) - min() 스칼라. + */ +#include +#include +#include "st_minband_dbio.h" + +long stdb_min_band_rate(const char *fee_type) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, fee_type, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT coalesce(min(rate_bps),0) INTO :h_v FROM st_fee_rate WHERE fee_type = :h_type; + if (sqlca.sqlcode < 0) { userlog("stdb_min_band_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_netavg_dbio.h b/app/src/st/dbio/st_netavg_dbio.h new file mode 100644 index 0000000..b126cfb --- /dev/null +++ b/app/src/st/dbio/st_netavg_dbio.h @@ -0,0 +1,13 @@ +/* + * st_netavg_dbio.h - st 모듈 DB 접근 계층 copybook (st_netavg_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_NETAVG_DBIO +#define ST_NETAVG_DBIO + +typedef struct { char biz_date[16]; long avg_net; } st_netavg_rec_t; + +long stdb_settle_avg_net(const char *bizdate); + +#endif /* ST_NETAVG_DBIO */ diff --git a/app/src/st/dbio/st_netavg_dbio.pgc b/app/src/st/dbio/st_netavg_dbio.pgc new file mode 100644 index 0000000..0ab5122 --- /dev/null +++ b/app/src/st/dbio/st_netavg_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_netavg_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_netavg_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 정산 net 평균 - avg() 반올림. + */ +#include +#include +#include "st_netavg_dbio.h" + +long stdb_settle_avg_net(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(round(avg(net)),0) INTO :h_v FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_netmax_dbio.h b/app/src/st/dbio/st_netmax_dbio.h new file mode 100644 index 0000000..06216f1 --- /dev/null +++ b/app/src/st/dbio/st_netmax_dbio.h @@ -0,0 +1,13 @@ +/* + * st_netmax_dbio.h - st 모듈 DB 접근 계층 copybook (st_netmax_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_NETMAX_DBIO +#define ST_NETMAX_DBIO + +typedef struct { char biz_date[16]; long max_net; } st_netmax_rec_t; + +long stdb_settle_max_net(const char *bizdate); + +#endif /* ST_NETMAX_DBIO */ diff --git a/app/src/st/dbio/st_netmax_dbio.pgc b/app/src/st/dbio/st_netmax_dbio.pgc new file mode 100644 index 0000000..505c013 --- /dev/null +++ b/app/src/st/dbio/st_netmax_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_netmax_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_netmax_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 최대 정산 net - max() 스칼라. + */ +#include +#include +#include "st_netmax_dbio.h" + +long stdb_settle_max_net(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(net),0) INTO :h_v FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_netsum_dbio.h b/app/src/st/dbio/st_netsum_dbio.h new file mode 100644 index 0000000..4604fef --- /dev/null +++ b/app/src/st/dbio/st_netsum_dbio.h @@ -0,0 +1,13 @@ +/* + * st_netsum_dbio.h - st 모듈 DB 접근 계층 copybook (st_netsum_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_NETSUM_DBIO +#define ST_NETSUM_DBIO + +typedef struct { char biz_date[16]; long net; } st_netsum_rec_t; + +long stdb_settle_net_sum(const char *bizdate); + +#endif /* ST_NETSUM_DBIO */ diff --git a/app/src/st/dbio/st_netsum_dbio.pgc b/app/src/st/dbio/st_netsum_dbio.pgc new file mode 100644 index 0000000..6fee3a8 --- /dev/null +++ b/app/src/st/dbio/st_netsum_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_netsum_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_netsum_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 정산 net 단순 합계. + */ +#include +#include +#include "st_netsum_dbio.h" + +long stdb_settle_net_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_v FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_settle_net_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_nopay_dbio.h b/app/src/st/dbio/st_nopay_dbio.h new file mode 100644 index 0000000..2855935 --- /dev/null +++ b/app/src/st/dbio/st_nopay_dbio.h @@ -0,0 +1,13 @@ +/* + * st_nopay_dbio.h - st 모듈 DB 접근 계층 copybook (st_nopay_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_NOPAY_DBIO +#define ST_NOPAY_DBIO + +typedef struct { char biz_date[16]; long cnt; } st_nopay_rec_t; + +long stdb_settle_no_payment(const char *bizdate); + +#endif /* ST_NOPAY_DBIO */ diff --git a/app/src/st/dbio/st_nopay_dbio.pgc b/app/src/st/dbio/st_nopay_dbio.pgc new file mode 100644 index 0000000..55a6787 --- /dev/null +++ b/app/src/st/dbio/st_nopay_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_nopay_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_nopay_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 지급 레코드가 없는 정산 건수 - NOT EXISTS 서브쿼리. + */ +#include +#include +#include "st_nopay_dbio.h" + +long stdb_settle_no_payment(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM settlement s + WHERE s.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM st_payment p WHERE p.merchant_id = s.merchant_id AND p.biz_date = s.biz_date); + if (sqlca.sqlcode < 0) { userlog("stdb_settle_no_payment FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_payinstatus_dbio.h b/app/src/st/dbio/st_payinstatus_dbio.h new file mode 100644 index 0000000..60fb51e --- /dev/null +++ b/app/src/st/dbio/st_payinstatus_dbio.h @@ -0,0 +1,13 @@ +/* + * st_payinstatus_dbio.h - st 모듈 DB 접근 계층 copybook (st_payinstatus_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_PAYINSTATUS_DBIO +#define ST_PAYINSTATUS_DBIO + +typedef struct { char biz_date[16]; long pending; } st_payinstatus_rec_t; + +long stdb_payment_pending_count(const char *bizdate); + +#endif /* ST_PAYINSTATUS_DBIO */ diff --git a/app/src/st/dbio/st_payinstatus_dbio.pgc b/app/src/st/dbio/st_payinstatus_dbio.pgc new file mode 100644 index 0000000..0c5af1c --- /dev/null +++ b/app/src/st/dbio/st_payinstatus_dbio.pgc @@ -0,0 +1,21 @@ +/* + * st_payinstatus_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_payinstatus_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 미완결 지급 상태 집합(SCHEDULED/HELD) IN 필터 건수. + */ +#include +#include +#include "st_payinstatus_dbio.h" + +long stdb_payment_pending_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + 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_v FROM st_payment + WHERE biz_date = :h_bizdate AND status IN ('SCHEDULED', 'HELD'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_payrank_dbio.h b/app/src/st/dbio/st_payrank_dbio.h new file mode 100644 index 0000000..7516d1b --- /dev/null +++ b/app/src/st/dbio/st_payrank_dbio.h @@ -0,0 +1,13 @@ +/* + * st_payrank_dbio.h - st 모듈 DB 접근 계층 copybook (st_payrank_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_PAYRANK_DBIO +#define ST_PAYRANK_DBIO + +typedef struct { long pay_id; long rnk; long net; } st_payrank_rec_t; + +long stdb_top_payment_sum(const char *bizdate, long topn); + +#endif /* ST_PAYRANK_DBIO */ diff --git a/app/src/st/dbio/st_payrank_dbio.pgc b/app/src/st/dbio/st_payrank_dbio.pgc new file mode 100644 index 0000000..12a90ca --- /dev/null +++ b/app/src/st/dbio/st_payrank_dbio.pgc @@ -0,0 +1,30 @@ +/* + * st_payrank_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_payrank_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 윈도우(rank) 커서 - 상위 N 지급 net 합계. + */ +#include +#include +#include "st_payrank_dbio.h" + +long stdb_top_payment_sum(const char *bizdate, long topn) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_top = topn, h_rnk, h_net, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE prc CURSOR FOR + SELECT net_amount, rank() OVER (ORDER BY net_amount DESC) AS rnk + FROM st_payment WHERE biz_date = :h_bizdate; + EXEC SQL OPEN prc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH prc INTO :h_net, :h_rnk; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE prc; return -1; } + if (h_rnk <= h_top) total += h_net; + } + EXEC SQL CLOSE prc; + return total; +} diff --git a/app/src/st/dbio/st_paystatuscnt_dbio.h b/app/src/st/dbio/st_paystatuscnt_dbio.h new file mode 100644 index 0000000..949aead --- /dev/null +++ b/app/src/st/dbio/st_paystatuscnt_dbio.h @@ -0,0 +1,13 @@ +/* + * st_paystatuscnt_dbio.h - st 모듈 DB 접근 계층 copybook (st_paystatuscnt_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_PAYSTATUSCNT_DBIO +#define ST_PAYSTATUSCNT_DBIO + +typedef struct { char biz_date[16]; char status[16]; long cnt; } st_paystatuscnt_rec_t; + +long stdb_payment_status_count(const char *bizdate, const char *status); + +#endif /* ST_PAYSTATUSCNT_DBIO */ diff --git a/app/src/st/dbio/st_paystatuscnt_dbio.pgc b/app/src/st/dbio/st_paystatuscnt_dbio.pgc new file mode 100644 index 0000000..9a15cb7 --- /dev/null +++ b/app/src/st/dbio/st_paystatuscnt_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_paystatuscnt_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_paystatuscnt_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 지급 상태별 건수 조회. + */ +#include +#include +#include "st_paystatuscnt_dbio.h" + +long stdb_payment_status_count(const char *bizdate, const char *status) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_status[16]; + long h_v = 0; + 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_v FROM st_payment + WHERE biz_date = :h_bizdate AND status = :h_status; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/st/dbio/st_purgecancel_dbio.h b/app/src/st/dbio/st_purgecancel_dbio.h new file mode 100644 index 0000000..cd2c228 --- /dev/null +++ b/app/src/st/dbio/st_purgecancel_dbio.h @@ -0,0 +1,13 @@ +/* + * st_purgecancel_dbio.h - st 모듈 DB 접근 계층 copybook (st_purgecancel_dbio, part of libstdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef ST_PURGECANCEL_DBIO +#define ST_PURGECANCEL_DBIO + +typedef struct { char biz_date[16]; long deleted; } st_purgecancel_rec_t; + +long stdb_purge_cancelled_pay(const char *bizdate); + +#endif /* ST_PURGECANCEL_DBIO */ diff --git a/app/src/st/dbio/st_purgecancel_dbio.pgc b/app/src/st/dbio/st_purgecancel_dbio.pgc new file mode 100644 index 0000000..d91fb1a --- /dev/null +++ b/app/src/st/dbio/st_purgecancel_dbio.pgc @@ -0,0 +1,19 @@ +/* + * st_purgecancel_dbio.pgc - st 모듈 DB 접근 함수 세트 (st_purgecancel_dbio), archived into libstdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 취소 지급 레코드 정리 - DELETE 후 삭제 행수 반환. + */ +#include +#include +#include "st_purgecancel_dbio.h" + +long stdb_purge_cancelled_pay(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + 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 DELETE FROM st_payment WHERE biz_date = :h_bizdate AND status = 'CANCELLED'; + if (sqlca.sqlcode < 0) { userlog("stdb_purge_cancelled_pay FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long)sqlca.sqlerrd[2]; +} diff --git a/app/src/st/run/run_adjubatch.sh b/app/src/st/run/run_adjubatch.sh new file mode 100755 index 0000000..d6661c8 --- /dev/null +++ b/app/src/st/run/run_adjubatch.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_adjubatch.sh - st_adjust_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_adjubatch.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_adjubatch.sh] mod=st BIZDATE=$BIZDATE -> st_adjust_batch" +exec /app/bin/st_adjust_batch "$BIZDATE" diff --git a/app/src/st/run/run_cancelpurge.sh b/app/src/st/run/run_cancelpurge.sh new file mode 100755 index 0000000..ea4f774 --- /dev/null +++ b/app/src/st/run/run_cancelpurge.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_cancelpurge.sh - st_cancelpurge_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_cancelpurge.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_cancelpurge.sh] mod=st BIZDATE=$BIZDATE -> st_cancelpurge_batch" +exec /app/bin/st_cancelpurge_batch "$BIZDATE" diff --git a/app/src/st/run/run_confirm.sh b/app/src/st/run/run_confirm.sh new file mode 100755 index 0000000..65bb73c --- /dev/null +++ b/app/src/st/run/run_confirm.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_confirm.sh - st_confirm_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_confirm.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_confirm.sh] mod=st BIZDATE=$BIZDATE -> st_confirm_batch" +exec /app/bin/st_confirm_batch "$BIZDATE" diff --git a/app/src/st/run/run_delayint.sh b/app/src/st/run/run_delayint.sh new file mode 100755 index 0000000..c7e7d8c --- /dev/null +++ b/app/src/st/run/run_delayint.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_delayint.sh - st_delayint_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_delayint.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_delayint.sh] mod=st BIZDATE=$BIZDATE -> st_delayint_batch" +exec /app/bin/st_delayint_batch "$BIZDATE" diff --git a/app/src/st/run/run_feecalc.sh b/app/src/st/run/run_feecalc.sh new file mode 100755 index 0000000..cd6b0b9 --- /dev/null +++ b/app/src/st/run/run_feecalc.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_feecalc.sh - st_feecalc_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_feecalc.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_feecalc.sh] mod=st BIZDATE=$BIZDATE -> st_feecalc_batch" +exec /app/bin/st_feecalc_batch "$BIZDATE" diff --git a/app/src/st/run/run_feepct.sh b/app/src/st/run/run_feepct.sh new file mode 100755 index 0000000..ea3cb62 --- /dev/null +++ b/app/src/st/run/run_feepct.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_feepct.sh - st_feepct_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_feepct.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_feepct.sh] mod=st BIZDATE=$BIZDATE -> st_feepct_batch" +exec /app/bin/st_feepct_batch "$BIZDATE" diff --git a/app/src/st/run/run_hold.sh b/app/src/st/run/run_hold.sh new file mode 100755 index 0000000..312df6c --- /dev/null +++ b/app/src/st/run/run_hold.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_hold.sh - st_hold_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_hold.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_hold.sh] mod=st BIZDATE=$BIZDATE -> st_hold_batch" +exec /app/bin/st_hold_batch "$BIZDATE" diff --git a/app/src/st/run/run_mdr_sum.sh b/app/src/st/run/run_mdr_sum.sh new file mode 100755 index 0000000..a26778b --- /dev/null +++ b/app/src/st/run/run_mdr_sum.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_mdr_sum.sh - st_mdr_sum_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_mdr_sum.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_mdr_sum.sh] mod=st BIZDATE=$BIZDATE -> st_mdr_sum_batch" +exec /app/bin/st_mdr_sum_batch "$BIZDATE" diff --git a/app/src/st/run/run_merchroll.sh b/app/src/st/run/run_merchroll.sh new file mode 100755 index 0000000..cb2439c --- /dev/null +++ b/app/src/st/run/run_merchroll.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_merchroll.sh - st_merchroll_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_merchroll.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_merchroll.sh] mod=st BIZDATE=$BIZDATE -> st_merchroll_batch" +exec /app/bin/st_merchroll_batch "$BIZDATE" diff --git a/app/src/st/run/run_monthly.sh b/app/src/st/run/run_monthly.sh new file mode 100755 index 0000000..6d5a847 --- /dev/null +++ b/app/src/st/run/run_monthly.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_monthly.sh - st_monthly_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_monthly.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_monthly.sh] mod=st BIZDATE=$BIZDATE -> st_monthly_batch" +exec /app/bin/st_monthly_batch "$BIZDATE" diff --git a/app/src/st/run/run_netting.sh b/app/src/st/run/run_netting.sh new file mode 100755 index 0000000..0c4d963 --- /dev/null +++ b/app/src/st/run/run_netting.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_netting.sh - st_netting_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_netting.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_netting.sh] mod=st BIZDATE=$BIZDATE -> st_netting_batch" +exec /app/bin/st_netting_batch "$BIZDATE" diff --git a/app/src/st/run/run_offset.sh b/app/src/st/run/run_offset.sh new file mode 100755 index 0000000..2324416 --- /dev/null +++ b/app/src/st/run/run_offset.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_offset.sh - st_offset_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_offset.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_offset.sh] mod=st BIZDATE=$BIZDATE -> st_offset_batch" +exec /app/bin/st_offset_batch "$BIZDATE" diff --git a/app/src/st/run/run_payment.sh b/app/src/st/run/run_payment.sh new file mode 100755 index 0000000..a36b4ea --- /dev/null +++ b/app/src/st/run/run_payment.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_payment.sh - st_payment_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_payment.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_payment.sh] mod=st BIZDATE=$BIZDATE -> st_payment_batch" +exec /app/bin/st_payment_batch "$BIZDATE" diff --git a/app/src/st/run/run_release.sh b/app/src/st/run/run_release.sh new file mode 100755 index 0000000..21a5fc5 --- /dev/null +++ b/app/src/st/run/run_release.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_release.sh - st_release_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_release.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_release.sh] mod=st BIZDATE=$BIZDATE -> st_release_batch" +exec /app/bin/st_release_batch "$BIZDATE" diff --git a/app/src/st/run/run_report.sh b/app/src/st/run/run_report.sh new file mode 100755 index 0000000..74c80bf --- /dev/null +++ b/app/src/st/run/run_report.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_report.sh - st_report_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_report.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_report.sh] mod=st BIZDATE=$BIZDATE -> st_report_batch" +exec /app/bin/st_report_batch "$BIZDATE" diff --git a/app/src/st/run/run_settle.sh b/app/src/st/run/run_settle.sh new file mode 100755 index 0000000..6152cf7 --- /dev/null +++ b/app/src/st/run/run_settle.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_settle.sh - st_settle_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_settle.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_settle.sh] mod=st BIZDATE=$BIZDATE -> st_settle_batch" +exec /app/bin/st_settle_batch "$BIZDATE" diff --git a/app/src/st/run/run_vat_sum.sh b/app/src/st/run/run_vat_sum.sh new file mode 100755 index 0000000..7b39b52 --- /dev/null +++ b/app/src/st/run/run_vat_sum.sh @@ -0,0 +1,13 @@ +#!/bin/bash +## +## run_vat_sum.sh - st_vat_sum_batch 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 st 배치를 실행. +## Usage: run_vat_sum.sh [BIZDATE] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" + +echo "[run_vat_sum.sh] mod=st BIZDATE=$BIZDATE -> st_vat_sum_batch" +exec /app/bin/st_vat_sum_batch "$BIZDATE" diff --git a/app/src/st/st_svr.pgc b/app/src/st/st_svr.pgc index c771e4b..ca5c742 100644 --- a/app/src/st/st_svr.pgc +++ b/app/src/st/st_svr.pgc @@ -1,71 +1,71 @@ /* - * st_svr.pgc - st (정산/수수료) MODULE SERVER. + * st_svr.pgc - st (정산/수수료) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of st's 30 online services now lives in its own file + * (app/src/st/svc/.pgc) with its own copybook header + * (app/src/st/svc/.h); those objects are archived into libstsvc.a and + * linked into this server. Here we only include every service copybook, tpopen() + * the ECPG XA RM, tpadvertise() all services from a {name, fn} table, and + * tpclose() at shutdown. * - * 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. + * st_svr: ONE binary that tpadvertise()s ALL 30 online st(정산/수수료) services. + * SETTLE keeps the proven behavior: its own XA branch writes the settlement + + * settle ledger rows. Terminal service of the 매입 chain. */ -#include -#include -#include #include #include #include -#include -#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); } -static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); } +/* every st service's copybook header (declares its record struct + prototype) */ +#include "SETTLE.h" +#include "ST_MDR.h" +#include "ST_NETTING.h" +#include "ST_VAT.h" +#include "ST_VANFEE.h" +#include "ST_RELAYFEE.h" +#include "ST_WHT.h" +#include "ST_ADJUST.h" +#include "ST_ADVANCE.h" +#include "ST_DELAYINT.h" +#include "ST_HOLD.h" +#include "ST_RELEASE.h" +#include "ST_SPLIT.h" +#include "ST_PAYDATE.h" +#include "ST_BYMERCH.h" +#include "ST_MONTHLY.h" +#include "ST_OFFSET.h" +#include "ST_DIFFCHK.h" +#include "ST_RECALC.h" +#include "ST_RATEAPPLY.h" +#include "ST_REPORT.h" +#include "ST_XFERLINK.h" +#include "ST_STATS.h" +#include "ST_CANCEL.h" +#include "ST_LEDGERLINK.h" +#include "ST_BANDRATE.h" +#include "ST_CAP.h" +#include "ST_FXCONV.h" +#include "ST_CONFIRM.h" +#include "ST_STATUS.h" -#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 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 --------------------------------------------------- */ +/* ----- advertise table: {service name, function} ---------------------- */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { - {"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}, + {"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} }; @@ -84,545 +84,4 @@ 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; - char merch[64], bizdate[16]; - EXEC SQL BEGIN DECLARE SECTION; - long h_pid, h_sid, h_net; - char h_merch[64], h_bizdate[16]; - EXEC SQL END DECLARE SECTION; - - h_pid = getl(b, T_PURCHASE_ID); - h_net = getl(b, T_NET); - 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 nextval('settlement_seq') INTO :h_sid; - if (sqlca.sqlcode < 0) FAIL(b); - EXEC SQL INSERT INTO settlement (settlement_id, purchase_id, merchant_id, net, biz_date, status) - VALUES (:h_sid, :h_pid, :h_merch, :h_net, :h_bizdate, 'SETTLED'); - if (sqlca.sqlcode < 0) { userlog("SETTLE INSERT settlement FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } - EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) - VALUES (nextval('ledger_seq'), :h_pid, 'SETTLE', :h_net, :h_bizdate); - if (sqlca.sqlcode < 0) FAIL(b); - - setl(b, T_SETTLE_ID, h_sid); - Bchg(b, T_STATUS, 0, "S", 0L); - userlog("SETTLE pid=%ld settlement_id=%ld net=%ld", h_pid, h_sid, h_net); - OK(b); -} - -/* 2. ST_MDR - MDR 수수료 원장 기록. (KEEP) */ -void ST_MDR(TPSVCINFO *p) -{ - UBFH *b = (UBFH *)p->data; - char bizdate[16]; - EXEC SQL BEGIN DECLARE SECTION; - long h_pid, h_fee; - char h_bizdate[16]; - EXEC SQL END DECLARE SECTION; - h_pid = getl(b, T_PURCHASE_ID); h_fee = getl(b, T_FEE); - 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, 'MDR', :h_fee, :h_bizdate); - if (sqlca.sqlcode < 0) FAIL(b); - OK(b); -} - -/* 3. ST_NETTING - 당일 net 합계 조회. (KEEP) */ -void ST_NETTING(TPSVCINFO *p) -{ - UBFH *b = (UBFH *)p->data; - char merch[64], bizdate[16]; - EXEC SQL BEGIN DECLARE SECTION; - long h_sum; - 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_sum FROM settlement - WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; - if (sqlca.sqlcode < 0) FAIL(b); - setl(b, T_GROSS, h_sum); - OK(b); -} - -/* 4. ST_VAT - 정산 부가세 원장 기록. (KEEP) */ -void ST_VAT(TPSVCINFO *p) -{ - UBFH *b = (UBFH *)p->data; - char bizdate[16]; - EXEC SQL BEGIN DECLARE SECTION; - long h_pid, h_vat; - char h_bizdate[16]; - EXEC SQL END DECLARE SECTION; - h_pid = getl(b, T_PURCHASE_ID); h_vat = getl(b, T_FEE) / 10; - 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, 'VAT', :h_vat, :h_bizdate); - if (sqlca.sqlcode < 0) FAIL(b); - setl(b, T_TAX, h_vat); - 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: */ diff --git a/app/src/st/svc/SETTLE.h b/app/src/st/svc/SETTLE.h new file mode 100644 index 0000000..09c2c6b --- /dev/null +++ b/app/src/st/svc/SETTLE.h @@ -0,0 +1,21 @@ +/* + * SETTLE.h - st 서비스 SETTLE 카피북 (copybook / record header). + * + */ +#ifndef SETTLE_H +#define SETTLE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_settle_rec_t; + +void SETTLE(TPSVCINFO *p); + +#endif /* SETTLE_H */ diff --git a/app/src/st/svc/SETTLE.pgc b/app/src/st/svc/SETTLE.pgc new file mode 100644 index 0000000..ba7931c --- /dev/null +++ b/app/src/st/svc/SETTLE.pgc @@ -0,0 +1,40 @@ +/* + * SETTLE.pgc - st 모듈 서비스 SETTLE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "SETTLE.h" + +/* ========================================================================= */ +void SETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_sid, h_net; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_net = getl(b, T_NET); + 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 nextval('settlement_seq') INTO :h_sid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO settlement (settlement_id, purchase_id, merchant_id, net, biz_date, status) + VALUES (:h_sid, :h_pid, :h_merch, :h_net, :h_bizdate, 'SETTLED'); + if (sqlca.sqlcode < 0) { userlog("SETTLE INSERT settlement FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'SETTLE', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_SETTLE_ID, h_sid); + Bchg(b, T_STATUS, 0, "S", 0L); + userlog("SETTLE pid=%ld settlement_id=%ld net=%ld", h_pid, h_sid, h_net); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_ADJUST.h b/app/src/st/svc/ST_ADJUST.h new file mode 100644 index 0000000..eb19624 --- /dev/null +++ b/app/src/st/svc/ST_ADJUST.h @@ -0,0 +1,21 @@ +/* + * ST_ADJUST.h - st 서비스 ST_ADJUST 카피북 (copybook / record header). + * 정산 조정 (가감) 기록 (dbio). + */ +#ifndef ST_ADJUST_H +#define ST_ADJUST_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_adjust_rec_t; + +void ST_ADJUST(TPSVCINFO *p); + +#endif /* ST_ADJUST_H */ diff --git a/app/src/st/svc/ST_ADJUST.pgc b/app/src/st/svc/ST_ADJUST.pgc new file mode 100644 index 0000000..a59b8a1 --- /dev/null +++ b/app/src/st/svc/ST_ADJUST.pgc @@ -0,0 +1,23 @@ +/* + * ST_ADJUST.pgc - st 모듈 서비스 ST_ADJUST (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_ADJUST.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_ADVANCE.h b/app/src/st/svc/ST_ADVANCE.h new file mode 100644 index 0000000..5fa0c07 --- /dev/null +++ b/app/src/st/svc/ST_ADVANCE.h @@ -0,0 +1,21 @@ +/* + * ST_ADVANCE.h - st 서비스 ST_ADVANCE 카피북 (copybook / record header). + * 선지급 예약: 지급 레코드(SCHEDULED) 생성 (dbio). + */ +#ifndef ST_ADVANCE_H +#define ST_ADVANCE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_advance_rec_t; + +void ST_ADVANCE(TPSVCINFO *p); + +#endif /* ST_ADVANCE_H */ diff --git a/app/src/st/svc/ST_ADVANCE.pgc b/app/src/st/svc/ST_ADVANCE.pgc new file mode 100644 index 0000000..aa1d961 --- /dev/null +++ b/app/src/st/svc/ST_ADVANCE.pgc @@ -0,0 +1,24 @@ +/* + * ST_ADVANCE.pgc - st 모듈 서비스 ST_ADVANCE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_ADVANCE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_BANDRATE.h b/app/src/st/svc/ST_BANDRATE.h new file mode 100644 index 0000000..95f8828 --- /dev/null +++ b/app/src/st/svc/ST_BANDRATE.h @@ -0,0 +1,21 @@ +/* + * ST_BANDRATE.h - st 서비스 ST_BANDRATE 카피북 (copybook / record header). + * 금액 구간 요율(bps) 조회 (dbio). + */ +#ifndef ST_BANDRATE_H +#define ST_BANDRATE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_bandrate_rec_t; + +void ST_BANDRATE(TPSVCINFO *p); + +#endif /* ST_BANDRATE_H */ diff --git a/app/src/st/svc/ST_BANDRATE.pgc b/app/src/st/svc/ST_BANDRATE.pgc new file mode 100644 index 0000000..3d8b11e --- /dev/null +++ b/app/src/st/svc/ST_BANDRATE.pgc @@ -0,0 +1,21 @@ +/* + * ST_BANDRATE.pgc - st 모듈 서비스 ST_BANDRATE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_BANDRATE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_BYMERCH.h b/app/src/st/svc/ST_BYMERCH.h new file mode 100644 index 0000000..c8c6140 --- /dev/null +++ b/app/src/st/svc/ST_BYMERCH.h @@ -0,0 +1,21 @@ +/* + * ST_BYMERCH.h - st 서비스 ST_BYMERCH 카피북 (copybook / record header). + * 가맹점별 정산 집계 upsert (dbio). + */ +#ifndef ST_BYMERCH_H +#define ST_BYMERCH_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_bymerch_rec_t; + +void ST_BYMERCH(TPSVCINFO *p); + +#endif /* ST_BYMERCH_H */ diff --git a/app/src/st/svc/ST_BYMERCH.pgc b/app/src/st/svc/ST_BYMERCH.pgc new file mode 100644 index 0000000..b49daa3 --- /dev/null +++ b/app/src/st/svc/ST_BYMERCH.pgc @@ -0,0 +1,31 @@ +/* + * ST_BYMERCH.pgc - st 모듈 서비스 ST_BYMERCH (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_BYMERCH.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CANCEL.h b/app/src/st/svc/ST_CANCEL.h new file mode 100644 index 0000000..98eef64 --- /dev/null +++ b/app/src/st/svc/ST_CANCEL.h @@ -0,0 +1,21 @@ +/* + * ST_CANCEL.h - st 서비스 ST_CANCEL 카피북 (copybook / record header). + * 정산 취소: 상태 CANCELLED + 취소 조정 (dbio). + */ +#ifndef ST_CANCEL_H +#define ST_CANCEL_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_cancel_rec_t; + +void ST_CANCEL(TPSVCINFO *p); + +#endif /* ST_CANCEL_H */ diff --git a/app/src/st/svc/ST_CANCEL.pgc b/app/src/st/svc/ST_CANCEL.pgc new file mode 100644 index 0000000..c654fd9 --- /dev/null +++ b/app/src/st/svc/ST_CANCEL.pgc @@ -0,0 +1,29 @@ +/* + * ST_CANCEL.pgc - st 모듈 서비스 ST_CANCEL (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_CANCEL.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CAP.h b/app/src/st/svc/ST_CAP.h new file mode 100644 index 0000000..ca56db2 --- /dev/null +++ b/app/src/st/svc/ST_CAP.h @@ -0,0 +1,21 @@ +/* + * ST_CAP.h - st 서비스 ST_CAP 카피북 (copybook / record header). + * 수수료 상한 적용: 상한 초과분을 감액 조정 (dbio). + */ +#ifndef ST_CAP_H +#define ST_CAP_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_cap_rec_t; + +void ST_CAP(TPSVCINFO *p); + +#endif /* ST_CAP_H */ diff --git a/app/src/st/svc/ST_CAP.pgc b/app/src/st/svc/ST_CAP.pgc new file mode 100644 index 0000000..c19a92c --- /dev/null +++ b/app/src/st/svc/ST_CAP.pgc @@ -0,0 +1,29 @@ +/* + * ST_CAP.pgc - st 모듈 서비스 ST_CAP (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_CAP.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CONFIRM.h b/app/src/st/svc/ST_CONFIRM.h new file mode 100644 index 0000000..20b04b3 --- /dev/null +++ b/app/src/st/svc/ST_CONFIRM.h @@ -0,0 +1,21 @@ +/* + * ST_CONFIRM.h - st 서비스 ST_CONFIRM 카피북 (copybook / record header). + * 정산 확정 (SETTLED -> CONFIRMED). + */ +#ifndef ST_CONFIRM_H +#define ST_CONFIRM_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_confirm_rec_t; + +void ST_CONFIRM(TPSVCINFO *p); + +#endif /* ST_CONFIRM_H */ diff --git a/app/src/st/svc/ST_CONFIRM.pgc b/app/src/st/svc/ST_CONFIRM.pgc new file mode 100644 index 0000000..cb3bab3 --- /dev/null +++ b/app/src/st/svc/ST_CONFIRM.pgc @@ -0,0 +1,23 @@ +/* + * ST_CONFIRM.pgc - st 모듈 서비스 ST_CONFIRM (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_CONFIRM.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DELAYINT.h b/app/src/st/svc/ST_DELAYINT.h new file mode 100644 index 0000000..b80d023 --- /dev/null +++ b/app/src/st/svc/ST_DELAYINT.h @@ -0,0 +1,21 @@ +/* + * ST_DELAYINT.h - st 서비스 ST_DELAYINT 카피북 (copybook / record header). + * 지연이자(net * bps/10000 * 일수) 정산 상세 기록 (dbio). + */ +#ifndef ST_DELAYINT_H +#define ST_DELAYINT_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_delayint_rec_t; + +void ST_DELAYINT(TPSVCINFO *p); + +#endif /* ST_DELAYINT_H */ diff --git a/app/src/st/svc/ST_DELAYINT.pgc b/app/src/st/svc/ST_DELAYINT.pgc new file mode 100644 index 0000000..8bb6a92 --- /dev/null +++ b/app/src/st/svc/ST_DELAYINT.pgc @@ -0,0 +1,26 @@ +/* + * ST_DELAYINT.pgc - st 모듈 서비스 ST_DELAYINT (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_DELAYINT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DIFFCHK.h b/app/src/st/svc/ST_DIFFCHK.h new file mode 100644 index 0000000..6bb4fdf --- /dev/null +++ b/app/src/st/svc/ST_DIFFCHK.h @@ -0,0 +1,21 @@ +/* + * ST_DIFFCHK.h - st 서비스 ST_DIFFCHK 카피북 (copybook / record header). + * 정산 net vs 매입 net 차이 점검. + */ +#ifndef ST_DIFFCHK_H +#define ST_DIFFCHK_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_diffchk_rec_t; + +void ST_DIFFCHK(TPSVCINFO *p); + +#endif /* ST_DIFFCHK_H */ diff --git a/app/src/st/svc/ST_DIFFCHK.pgc b/app/src/st/svc/ST_DIFFCHK.pgc new file mode 100644 index 0000000..9b5b290 --- /dev/null +++ b/app/src/st/svc/ST_DIFFCHK.pgc @@ -0,0 +1,26 @@ +/* + * ST_DIFFCHK.pgc - st 모듈 서비스 ST_DIFFCHK (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_DIFFCHK.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_FXCONV.h b/app/src/st/svc/ST_FXCONV.h new file mode 100644 index 0000000..7bbcf1d --- /dev/null +++ b/app/src/st/svc/ST_FXCONV.h @@ -0,0 +1,21 @@ +/* + * ST_FXCONV.h - st 서비스 ST_FXCONV 카피북 (copybook / record header). + * 외화 환산: fx * rate/100 = KRW. + */ +#ifndef ST_FXCONV_H +#define ST_FXCONV_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_fxconv_rec_t; + +void ST_FXCONV(TPSVCINFO *p); + +#endif /* ST_FXCONV_H */ diff --git a/app/src/st/svc/ST_FXCONV.pgc b/app/src/st/svc/ST_FXCONV.pgc new file mode 100644 index 0000000..32b42e3 --- /dev/null +++ b/app/src/st/svc/ST_FXCONV.pgc @@ -0,0 +1,19 @@ +/* + * ST_FXCONV.pgc - st 모듈 서비스 ST_FXCONV (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_FXCONV.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_HOLD.h b/app/src/st/svc/ST_HOLD.h new file mode 100644 index 0000000..a9ecdd6 --- /dev/null +++ b/app/src/st/svc/ST_HOLD.h @@ -0,0 +1,21 @@ +/* + * ST_HOLD.h - st 서비스 ST_HOLD 카피북 (copybook / record header). + * 가맹점 지급 보류 (SCHEDULED -> HELD) (dbio). + */ +#ifndef ST_HOLD_H +#define ST_HOLD_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_hold_rec_t; + +void ST_HOLD(TPSVCINFO *p); + +#endif /* ST_HOLD_H */ diff --git a/app/src/st/svc/ST_HOLD.pgc b/app/src/st/svc/ST_HOLD.pgc new file mode 100644 index 0000000..b7d12ac --- /dev/null +++ b/app/src/st/svc/ST_HOLD.pgc @@ -0,0 +1,20 @@ +/* + * ST_HOLD.pgc - st 모듈 서비스 ST_HOLD (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_HOLD.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_LEDGERLINK.h b/app/src/st/svc/ST_LEDGERLINK.h new file mode 100644 index 0000000..2a9abe7 --- /dev/null +++ b/app/src/st/svc/ST_LEDGERLINK.h @@ -0,0 +1,21 @@ +/* + * ST_LEDGERLINK.h - st 서비스 ST_LEDGERLINK 카피북 (copybook / record header). + * 정산 연계 원장 기록. + */ +#ifndef ST_LEDGERLINK_H +#define ST_LEDGERLINK_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_ledgerlink_rec_t; + +void ST_LEDGERLINK(TPSVCINFO *p); + +#endif /* ST_LEDGERLINK_H */ diff --git a/app/src/st/svc/ST_LEDGERLINK.pgc b/app/src/st/svc/ST_LEDGERLINK.pgc new file mode 100644 index 0000000..aa38776 --- /dev/null +++ b/app/src/st/svc/ST_LEDGERLINK.pgc @@ -0,0 +1,26 @@ +/* + * ST_LEDGERLINK.pgc - st 모듈 서비스 ST_LEDGERLINK (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_LEDGERLINK.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_MDR.h b/app/src/st/svc/ST_MDR.h new file mode 100644 index 0000000..735558d --- /dev/null +++ b/app/src/st/svc/ST_MDR.h @@ -0,0 +1,21 @@ +/* + * ST_MDR.h - st 서비스 ST_MDR 카피북 (copybook / record header). + * MDR 수수료 원장 기록. (KEEP) + */ +#ifndef ST_MDR_H +#define ST_MDR_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_mdr_rec_t; + +void ST_MDR(TPSVCINFO *p); + +#endif /* ST_MDR_H */ diff --git a/app/src/st/svc/ST_MDR.pgc b/app/src/st/svc/ST_MDR.pgc new file mode 100644 index 0000000..ea3fa64 --- /dev/null +++ b/app/src/st/svc/ST_MDR.pgc @@ -0,0 +1,26 @@ +/* + * ST_MDR.pgc - st 모듈 서비스 ST_MDR (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_MDR.h" + +/* 2. ST_MDR - MDR 수수료 원장 기록. (KEEP) */ +void ST_MDR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_fee; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); h_fee = getl(b, T_FEE); + 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, 'MDR', :h_fee, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_MONTHLY.h b/app/src/st/svc/ST_MONTHLY.h new file mode 100644 index 0000000..e4ef1bf --- /dev/null +++ b/app/src/st/svc/ST_MONTHLY.h @@ -0,0 +1,21 @@ +/* + * ST_MONTHLY.h - st 서비스 ST_MONTHLY 카피북 (copybook / record header). + * 월별 정산 net 합계 (biz_date LIKE 'YYYY-MM%'). + */ +#ifndef ST_MONTHLY_H +#define ST_MONTHLY_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_monthly_rec_t; + +void ST_MONTHLY(TPSVCINFO *p); + +#endif /* ST_MONTHLY_H */ diff --git a/app/src/st/svc/ST_MONTHLY.pgc b/app/src/st/svc/ST_MONTHLY.pgc new file mode 100644 index 0000000..0e1ab59 --- /dev/null +++ b/app/src/st/svc/ST_MONTHLY.pgc @@ -0,0 +1,29 @@ +/* + * ST_MONTHLY.pgc - st 모듈 서비스 ST_MONTHLY (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_MONTHLY.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_NETTING.h b/app/src/st/svc/ST_NETTING.h new file mode 100644 index 0000000..ad26385 --- /dev/null +++ b/app/src/st/svc/ST_NETTING.h @@ -0,0 +1,21 @@ +/* + * ST_NETTING.h - st 서비스 ST_NETTING 카피북 (copybook / record header). + * 당일 net 합계 조회. (KEEP) + */ +#ifndef ST_NETTING_H +#define ST_NETTING_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_netting_rec_t; + +void ST_NETTING(TPSVCINFO *p); + +#endif /* ST_NETTING_H */ diff --git a/app/src/st/svc/ST_NETTING.pgc b/app/src/st/svc/ST_NETTING.pgc new file mode 100644 index 0000000..18b8fa7 --- /dev/null +++ b/app/src/st/svc/ST_NETTING.pgc @@ -0,0 +1,28 @@ +/* + * ST_NETTING.pgc - st 모듈 서비스 ST_NETTING (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_NETTING.h" + +/* 3. ST_NETTING - 당일 net 합계 조회. (KEEP) */ +void ST_NETTING(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_sum; + 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_sum FROM settlement + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_OFFSET.h b/app/src/st/svc/ST_OFFSET.h new file mode 100644 index 0000000..6a9f906 --- /dev/null +++ b/app/src/st/svc/ST_OFFSET.h @@ -0,0 +1,21 @@ +/* + * ST_OFFSET.h - st 서비스 ST_OFFSET 카피북 (copybook / record header). + * 상계: 음수 조정으로 채권 상계 기록 (dbio). + */ +#ifndef ST_OFFSET_H +#define ST_OFFSET_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_offset_rec_t; + +void ST_OFFSET(TPSVCINFO *p); + +#endif /* ST_OFFSET_H */ diff --git a/app/src/st/svc/ST_OFFSET.pgc b/app/src/st/svc/ST_OFFSET.pgc new file mode 100644 index 0000000..e7152a1 --- /dev/null +++ b/app/src/st/svc/ST_OFFSET.pgc @@ -0,0 +1,22 @@ +/* + * ST_OFFSET.pgc - st 모듈 서비스 ST_OFFSET (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_OFFSET.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_PAYDATE.h b/app/src/st/svc/ST_PAYDATE.h new file mode 100644 index 0000000..358f181 --- /dev/null +++ b/app/src/st/svc/ST_PAYDATE.h @@ -0,0 +1,21 @@ +/* + * ST_PAYDATE.h - st 서비스 ST_PAYDATE 카피북 (copybook / record header). + * 지급일 산정(익영업일) 후 지급일 갱신. + */ +#ifndef ST_PAYDATE_H +#define ST_PAYDATE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_paydate_rec_t; + +void ST_PAYDATE(TPSVCINFO *p); + +#endif /* ST_PAYDATE_H */ diff --git a/app/src/st/svc/ST_PAYDATE.pgc b/app/src/st/svc/ST_PAYDATE.pgc new file mode 100644 index 0000000..e0a1c08 --- /dev/null +++ b/app/src/st/svc/ST_PAYDATE.pgc @@ -0,0 +1,29 @@ +/* + * ST_PAYDATE.pgc - st 모듈 서비스 ST_PAYDATE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_PAYDATE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RATEAPPLY.h b/app/src/st/svc/ST_RATEAPPLY.h new file mode 100644 index 0000000..6222cd0 --- /dev/null +++ b/app/src/st/svc/ST_RATEAPPLY.h @@ -0,0 +1,21 @@ +/* + * ST_RATEAPPLY.h - st 서비스 ST_RATEAPPLY 카피북 (copybook / record header). + * 구간요율 적용 수수료 계산 + 정산 상세 기록 (dbio). + */ +#ifndef ST_RATEAPPLY_H +#define ST_RATEAPPLY_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_rateapply_rec_t; + +void ST_RATEAPPLY(TPSVCINFO *p); + +#endif /* ST_RATEAPPLY_H */ diff --git a/app/src/st/svc/ST_RATEAPPLY.pgc b/app/src/st/svc/ST_RATEAPPLY.pgc new file mode 100644 index 0000000..c110a7a --- /dev/null +++ b/app/src/st/svc/ST_RATEAPPLY.pgc @@ -0,0 +1,28 @@ +/* + * ST_RATEAPPLY.pgc - st 모듈 서비스 ST_RATEAPPLY (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_RATEAPPLY.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RECALC.h b/app/src/st/svc/ST_RECALC.h new file mode 100644 index 0000000..4b9d279 --- /dev/null +++ b/app/src/st/svc/ST_RECALC.h @@ -0,0 +1,21 @@ +/* + * ST_RECALC.h - st 서비스 ST_RECALC 카피북 (copybook / record header). + * 매입 수수료 기준 정산 net 재계산 + 갱신. + */ +#ifndef ST_RECALC_H +#define ST_RECALC_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_recalc_rec_t; + +void ST_RECALC(TPSVCINFO *p); + +#endif /* ST_RECALC_H */ diff --git a/app/src/st/svc/ST_RECALC.pgc b/app/src/st/svc/ST_RECALC.pgc new file mode 100644 index 0000000..f74af0e --- /dev/null +++ b/app/src/st/svc/ST_RECALC.pgc @@ -0,0 +1,25 @@ +/* + * ST_RECALC.pgc - st 모듈 서비스 ST_RECALC (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_RECALC.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RELAYFEE.h b/app/src/st/svc/ST_RELAYFEE.h new file mode 100644 index 0000000..629d3b2 --- /dev/null +++ b/app/src/st/svc/ST_RELAYFEE.h @@ -0,0 +1,21 @@ +/* + * ST_RELAYFEE.h - st 서비스 ST_RELAYFEE 카피북 (copybook / record header). + * 중계 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). + */ +#ifndef ST_RELAYFEE_H +#define ST_RELAYFEE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_relayfee_rec_t; + +void ST_RELAYFEE(TPSVCINFO *p); + +#endif /* ST_RELAYFEE_H */ diff --git a/app/src/st/svc/ST_RELAYFEE.pgc b/app/src/st/svc/ST_RELAYFEE.pgc new file mode 100644 index 0000000..d1a732e --- /dev/null +++ b/app/src/st/svc/ST_RELAYFEE.pgc @@ -0,0 +1,26 @@ +/* + * ST_RELAYFEE.pgc - st 모듈 서비스 ST_RELAYFEE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_RELAYFEE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RELEASE.h b/app/src/st/svc/ST_RELEASE.h new file mode 100644 index 0000000..a51e6fc --- /dev/null +++ b/app/src/st/svc/ST_RELEASE.h @@ -0,0 +1,21 @@ +/* + * ST_RELEASE.h - st 서비스 ST_RELEASE 카피북 (copybook / record header). + * 지급 보류 해제 (HELD -> SCHEDULED) (dbio). + */ +#ifndef ST_RELEASE_H +#define ST_RELEASE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_release_rec_t; + +void ST_RELEASE(TPSVCINFO *p); + +#endif /* ST_RELEASE_H */ diff --git a/app/src/st/svc/ST_RELEASE.pgc b/app/src/st/svc/ST_RELEASE.pgc new file mode 100644 index 0000000..b3e7861 --- /dev/null +++ b/app/src/st/svc/ST_RELEASE.pgc @@ -0,0 +1,20 @@ +/* + * ST_RELEASE.pgc - st 모듈 서비스 ST_RELEASE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_RELEASE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_REPORT.h b/app/src/st/svc/ST_REPORT.h new file mode 100644 index 0000000..5006a99 --- /dev/null +++ b/app/src/st/svc/ST_REPORT.h @@ -0,0 +1,21 @@ +/* + * ST_REPORT.h - st 서비스 ST_REPORT 카피북 (copybook / record header). + * 당일 정산 건수/합계 리포트. + */ +#ifndef ST_REPORT_H +#define ST_REPORT_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_report_rec_t; + +void ST_REPORT(TPSVCINFO *p); + +#endif /* ST_REPORT_H */ diff --git a/app/src/st/svc/ST_REPORT.pgc b/app/src/st/svc/ST_REPORT.pgc new file mode 100644 index 0000000..732a580 --- /dev/null +++ b/app/src/st/svc/ST_REPORT.pgc @@ -0,0 +1,27 @@ +/* + * ST_REPORT.pgc - st 모듈 서비스 ST_REPORT (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_REPORT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_SPLIT.h b/app/src/st/svc/ST_SPLIT.h new file mode 100644 index 0000000..effba9c --- /dev/null +++ b/app/src/st/svc/ST_SPLIT.h @@ -0,0 +1,21 @@ +/* + * ST_SPLIT.h - st 서비스 ST_SPLIT 카피북 (copybook / record header). + * 분할 지급: net 을 n 회로 나눠 지급 레코드 생성 (dbio 루프). + */ +#ifndef ST_SPLIT_H +#define ST_SPLIT_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_split_rec_t; + +void ST_SPLIT(TPSVCINFO *p); + +#endif /* ST_SPLIT_H */ diff --git a/app/src/st/svc/ST_SPLIT.pgc b/app/src/st/svc/ST_SPLIT.pgc new file mode 100644 index 0000000..4e7c963 --- /dev/null +++ b/app/src/st/svc/ST_SPLIT.pgc @@ -0,0 +1,26 @@ +/* + * ST_SPLIT.pgc - st 모듈 서비스 ST_SPLIT (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_SPLIT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_STATS.h b/app/src/st/svc/ST_STATS.h new file mode 100644 index 0000000..2660b29 --- /dev/null +++ b/app/src/st/svc/ST_STATS.h @@ -0,0 +1,21 @@ +/* + * ST_STATS.h - st 서비스 ST_STATS 카피북 (copybook / record header). + * 정산 통계(건수/net 합계) 조회. + */ +#ifndef ST_STATS_H +#define ST_STATS_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_stats_rec_t; + +void ST_STATS(TPSVCINFO *p); + +#endif /* ST_STATS_H */ diff --git a/app/src/st/svc/ST_STATS.pgc b/app/src/st/svc/ST_STATS.pgc new file mode 100644 index 0000000..ba7a5c0 --- /dev/null +++ b/app/src/st/svc/ST_STATS.pgc @@ -0,0 +1,28 @@ +/* + * ST_STATS.pgc - st 모듈 서비스 ST_STATS (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_STATS.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_STATUS.h b/app/src/st/svc/ST_STATUS.h new file mode 100644 index 0000000..f8348be --- /dev/null +++ b/app/src/st/svc/ST_STATUS.h @@ -0,0 +1,21 @@ +/* + * ST_STATUS.h - st 서비스 ST_STATUS 카피북 (copybook / record header). + * 정산 상태 조회. + */ +#ifndef ST_STATUS_H +#define ST_STATUS_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_status_rec_t; + +void ST_STATUS(TPSVCINFO *p); + +#endif /* ST_STATUS_H */ diff --git a/app/src/st/svc/ST_STATUS.pgc b/app/src/st/svc/ST_STATUS.pgc new file mode 100644 index 0000000..6d6b3b2 --- /dev/null +++ b/app/src/st/svc/ST_STATUS.pgc @@ -0,0 +1,25 @@ +/* + * ST_STATUS.pgc - st 모듈 서비스 ST_STATUS (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_STATUS.h" + +/* 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: */ diff --git a/app/src/st/svc/ST_VANFEE.h b/app/src/st/svc/ST_VANFEE.h new file mode 100644 index 0000000..afb522f --- /dev/null +++ b/app/src/st/svc/ST_VANFEE.h @@ -0,0 +1,21 @@ +/* + * ST_VANFEE.h - st 서비스 ST_VANFEE 카피북 (copybook / record header). + * VAN 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). + */ +#ifndef ST_VANFEE_H +#define ST_VANFEE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_vanfee_rec_t; + +void ST_VANFEE(TPSVCINFO *p); + +#endif /* ST_VANFEE_H */ diff --git a/app/src/st/svc/ST_VANFEE.pgc b/app/src/st/svc/ST_VANFEE.pgc new file mode 100644 index 0000000..d2cdc7b --- /dev/null +++ b/app/src/st/svc/ST_VANFEE.pgc @@ -0,0 +1,26 @@ +/* + * ST_VANFEE.pgc - st 모듈 서비스 ST_VANFEE (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_VANFEE.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_VAT.h b/app/src/st/svc/ST_VAT.h new file mode 100644 index 0000000..3512d2b --- /dev/null +++ b/app/src/st/svc/ST_VAT.h @@ -0,0 +1,21 @@ +/* + * ST_VAT.h - st 서비스 ST_VAT 카피북 (copybook / record header). + * 정산 부가세 원장 기록. (KEEP) + */ +#ifndef ST_VAT_H +#define ST_VAT_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_vat_rec_t; + +void ST_VAT(TPSVCINFO *p); + +#endif /* ST_VAT_H */ diff --git a/app/src/st/svc/ST_VAT.pgc b/app/src/st/svc/ST_VAT.pgc new file mode 100644 index 0000000..4a9baba --- /dev/null +++ b/app/src/st/svc/ST_VAT.pgc @@ -0,0 +1,27 @@ +/* + * ST_VAT.pgc - st 모듈 서비스 ST_VAT (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_VAT.h" + +/* 4. ST_VAT - 정산 부가세 원장 기록. (KEEP) */ +void ST_VAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_vat; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); h_vat = getl(b, T_FEE) / 10; + 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, 'VAT', :h_vat, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_TAX, h_vat); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_WHT.h b/app/src/st/svc/ST_WHT.h new file mode 100644 index 0000000..718e5b5 --- /dev/null +++ b/app/src/st/svc/ST_WHT.h @@ -0,0 +1,21 @@ +/* + * ST_WHT.h - st 서비스 ST_WHT 카피북 (copybook / record header). + * 원천징수(수수료의 22%) 정산 상세 기록 (dbio). + */ +#ifndef ST_WHT_H +#define ST_WHT_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_wht_rec_t; + +void ST_WHT(TPSVCINFO *p); + +#endif /* ST_WHT_H */ diff --git a/app/src/st/svc/ST_WHT.pgc b/app/src/st/svc/ST_WHT.pgc new file mode 100644 index 0000000..5bc0866 --- /dev/null +++ b/app/src/st/svc/ST_WHT.pgc @@ -0,0 +1,24 @@ +/* + * ST_WHT.pgc - st 모듈 서비스 ST_WHT (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_WHT.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_XFERLINK.h b/app/src/st/svc/ST_XFERLINK.h new file mode 100644 index 0000000..9a36c2f --- /dev/null +++ b/app/src/st/svc/ST_XFERLINK.h @@ -0,0 +1,21 @@ +/* + * ST_XFERLINK.h - st 서비스 ST_XFERLINK 카피북 (copybook / record header). + * 이체 연계 원장 기록. + */ +#ifndef ST_XFERLINK_H +#define ST_XFERLINK_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long amount; + long net; + char status[8]; + char biz_date[16]; +} st_st_xferlink_rec_t; + +void ST_XFERLINK(TPSVCINFO *p); + +#endif /* ST_XFERLINK_H */ diff --git a/app/src/st/svc/ST_XFERLINK.pgc b/app/src/st/svc/ST_XFERLINK.pgc new file mode 100644 index 0000000..70c14bc --- /dev/null +++ b/app/src/st/svc/ST_XFERLINK.pgc @@ -0,0 +1,26 @@ +/* + * ST_XFERLINK.pgc - st 모듈 서비스 ST_XFERLINK (one service = one file). + * Moved verbatim from the former inline st_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by st_svr at tpsvrinit. + */ +#include "ST_XFERLINK.h" + +/* 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); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/st_svc.h b/app/src/st/svc/st_svc.h new file mode 100644 index 0000000..a4a66ce --- /dev/null +++ b/app/src/st/svc/st_svc.h @@ -0,0 +1,33 @@ +/* + * st_svc.h - st (정산/수수료) 서비스 공통 카피북 (shared copybook for all st svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids and the st dbio layer (libstdbio.a), + * and provides the tiny UBF helpers + tpreturn macros every service uses. No + * business logic and no service function lives here. + */ +#ifndef ST_SVC_H +#define ST_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "acq_common.h" +#include "st_dbio.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* ST_SVC_H */ diff --git a/app/src/vl/batch/vl_appr_audit_batch.pgc b/app/src/vl/batch/vl_appr_audit_batch.pgc new file mode 100644 index 0000000..dc12583 --- /dev/null +++ b/app/src/vl/batch/vl_appr_audit_batch.pgc @@ -0,0 +1,37 @@ +/* + * vl_appr_audit_batch.pgc - 승인 누락 매입 감사 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_appr_audit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_appr_audit_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_appr_audit_batch COMMIT: bizdate=%s 승인누락=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_balance_batch.pgc b/app/src/vl/batch/vl_balance_batch.pgc new file mode 100644 index 0000000..2384b6c --- /dev/null +++ b/app/src/vl/batch/vl_balance_batch.pgc @@ -0,0 +1,39 @@ +/* + * vl_balance_batch.pgc - 가맹점 대차 불일치 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_balance_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_pnet, h_snet; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SEL1 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement + WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_balance_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_balance_batch COMMIT: bizdate=%s 매입=%ld 정산=%ld delta=%ld\n", h_bizdate, h_pnet, h_snet, h_pnet - h_snet); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_bin_check_batch.pgc b/app/src/vl/batch/vl_bin_check_batch.pgc new file mode 100644 index 0000000..03ae2a3 --- /dev/null +++ b/app/src/vl/batch/vl_bin_check_batch.pgc @@ -0,0 +1,35 @@ +/* + * vl_bin_check_batch.pgc - 미등록 BIN 사용 매입 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_bin_check_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM mm_bin WHERE status <> 'ACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_bin_check_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_bin_check_batch COMMIT: 비활성BIN=%ld\n", h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_check_sweep_batch.pgc b/app/src/vl/batch/vl_check_sweep_batch.pgc new file mode 100644 index 0000000..b0c123e --- /dev/null +++ b/app/src/vl/batch/vl_check_sweep_batch.pgc @@ -0,0 +1,36 @@ +/* + * vl_check_sweep_batch.pgc - 당일 검증로그 실패 스윕 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_check_sweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM vl_check_log + WHERE biz_date = :h_bizdate AND result_code <> 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_check_sweep_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_check_sweep_batch COMMIT: bizdate=%s 실패=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_cross_batch.pgc b/app/src/vl/batch/vl_cross_batch.pgc new file mode 100644 index 0000000..5266cb2 --- /dev/null +++ b/app/src/vl/batch/vl_cross_batch.pgc @@ -0,0 +1,38 @@ +/* + * vl_cross_batch.pgc - 교차검증(매입+승인+가맹점) 결측 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_cross_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM purchase p + WHERE p.biz_date = :h_bizdate + AND (NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id) + OR NOT EXISTS (SELECT 1 FROM merchant m WHERE m.merchant_id = p.merchant_id)); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_cross_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_cross_batch COMMIT: bizdate=%s 결측=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_dup_scan_batch.pgc b/app/src/vl/batch/vl_dup_scan_batch.pgc new file mode 100644 index 0000000..2ceb976 --- /dev/null +++ b/app/src/vl/batch/vl_dup_scan_batch.pgc @@ -0,0 +1,48 @@ +/* + * vl_dup_scan_batch.pgc - 중복 매입 스캔 배치 (커서 + 의심적재) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_dup_scan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; long h_amt, h_cnt; long sid; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE dsc CURSOR FOR + SELECT merchant_id, amount, count(*) FROM purchase + WHERE biz_date = :h_bizdate GROUP BY merchant_id, amount HAVING count(*) > 1; + EXEC SQL OPEN dsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH dsc INTO :h_merch, :h_amt, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dsc; tpabort(0); return 1; } + sid = vldb_insert_suspect(0, "DUP", "WARN", "중복 매입 의심", h_bizdate); + if (sid < 0) { EXEC SQL CLOSE dsc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE dsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_dup_scan_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_dup_scan_batch COMMIT: bizdate=%s 중복그룹=%ld\n", h_bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_expiry_batch.pgc b/app/src/vl/batch/vl_expiry_batch.pgc new file mode 100644 index 0000000..5551dcc --- /dev/null +++ b/app/src/vl/batch/vl_expiry_batch.pgc @@ -0,0 +1,36 @@ +/* + * vl_expiry_batch.pgc - 카드 만료 의심거래 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_expiry_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM vl_suspect + WHERE rule_code = 'EXPIRY' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_expiry_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_expiry_batch COMMIT: bizdate=%s 만료의심=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_format_batch.pgc b/app/src/vl/batch/vl_format_batch.pgc new file mode 100644 index 0000000..a8ab6a3 --- /dev/null +++ b/app/src/vl/batch/vl_format_batch.pgc @@ -0,0 +1,35 @@ +/* + * vl_format_batch.pgc - 가맹점ID 형식 위반 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_format_batch + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + 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; } + + EXEC SQL SELECT count(*) INTO :h_cnt FROM merchant + WHERE length(merchant_id) < 4 OR length(merchant_id) > 16; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_format_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_format_batch COMMIT: 형식위반=%ld\n", h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_ledger_recon_batch.pgc b/app/src/vl/batch/vl_ledger_recon_batch.pgc new file mode 100644 index 0000000..4fed1c9 --- /dev/null +++ b/app/src/vl/batch/vl_ledger_recon_batch.pgc @@ -0,0 +1,39 @@ +/* + * vl_ledger_recon_batch.pgc - 원장-매입 순액 대사 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_ledger_recon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_pnet, h_lsum; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SEL1 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_lsum FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'SETTLELINK'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_ledger_recon_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_ledger_recon_batch COMMIT: bizdate=%s 매입순액=%ld 원장합=%ld delta=%ld\n", h_bizdate, h_pnet, h_lsum, h_pnet - h_lsum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_limit_scan_batch.pgc b/app/src/vl/batch/vl_limit_scan_batch.pgc new file mode 100644 index 0000000..84f5967 --- /dev/null +++ b/app/src/vl/batch/vl_limit_scan_batch.pgc @@ -0,0 +1,37 @@ +/* + * vl_limit_scan_batch.pgc - 한도 초과 매입 스캔 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_limit_scan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM purchase p + JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.biz_date = :h_bizdate AND p.amount > m.daily_limit; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_limit_scan_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_limit_scan_batch COMMIT: bizdate=%s 한도초과=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_refinteg_batch.pgc b/app/src/vl/batch/vl_refinteg_batch.pgc new file mode 100644 index 0000000..2d19c5c --- /dev/null +++ b/app/src/vl/batch/vl_refinteg_batch.pgc @@ -0,0 +1,35 @@ +/* + * vl_refinteg_batch.pgc - 참조무결성(가맹점 orphan) 검사 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_refinteg_batch + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + 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; } + + EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase p + WHERE NOT EXISTS (SELECT 1 FROM merchant m WHERE m.merchant_id = p.merchant_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_refinteg_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_refinteg_batch COMMIT: orphan=%ld\n", h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_report_batch.pgc b/app/src/vl/batch/vl_report_batch.pgc new file mode 100644 index 0000000..acfa248 --- /dev/null +++ b/app/src/vl/batch/vl_report_batch.pgc @@ -0,0 +1,46 @@ +/* + * vl_report_batch.pgc - 심각도별 의심거래 리포트 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_report_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_sev[8]; long h_cnt; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 rpc CURSOR FOR + SELECT severity, count(*) FROM vl_suspect + WHERE biz_date = :h_bizdate GROUP BY severity ORDER BY severity; + EXEC SQL OPEN rpc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rpc INTO :h_sev, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rpc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE rpc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_report_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_report_batch COMMIT: bizdate=%s 심각도구간=%ld\n", h_bizdate, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_settle_recon_batch.pgc b/app/src/vl/batch/vl_settle_recon_batch.pgc new file mode 100644 index 0000000..d3fb9de --- /dev/null +++ b/app/src/vl/batch/vl_settle_recon_batch.pgc @@ -0,0 +1,38 @@ +/* + * vl_settle_recon_batch.pgc - 정산 불일치 대사 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_settle_recon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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 SELECT count(*) INTO :h_cnt FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status = 'S' + AND p.net <> coalesce((SELECT sum(s.net) FROM settlement s + WHERE s.purchase_id = p.purchase_id), 0); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_settle_recon_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_settle_recon_batch COMMIT: bizdate=%s 정산불일치=%ld\n", h_bizdate, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_suspect_close_batch.pgc b/app/src/vl/batch/vl_suspect_close_batch.pgc new file mode 100644 index 0000000..d0effc2 --- /dev/null +++ b/app/src/vl/batch/vl_suspect_close_batch.pgc @@ -0,0 +1,46 @@ +/* + * vl_suspect_close_batch.pgc - 노후 의심거래 자동 종결 배치 (커서) (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_suspect_close_batch [DAYS] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_sid, h_days = (argc > 1) ? atol(argv[1]) : 30; + 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; } + + EXEC SQL DECLARE scc CURSOR FOR + SELECT suspect_id FROM vl_suspect + WHERE status = 'OPEN' AND created_at < now() - (:h_days || ' days')::interval; + EXEC SQL OPEN scc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH scc INTO :h_sid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE scc; tpabort(0); return 1; } + if (vldb_close_suspect(h_sid) < 0) { EXEC SQL CLOSE scc; tpabort(0); return 1; } + rows++; + } + EXEC SQL CLOSE scc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_suspect_close_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_suspect_close_batch COMMIT: days=%ld 종결=%ld\n", h_days, rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_threshold_batch.pgc b/app/src/vl/batch/vl_threshold_batch.pgc new file mode 100644 index 0000000..0b89235 --- /dev/null +++ b/app/src/vl/batch/vl_threshold_batch.pgc @@ -0,0 +1,37 @@ +/* + * vl_threshold_batch.pgc - 임계 초과 매입 규칙 점검 배치 (XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; the 검증 SELECT/집계 runs on that branch, then tpcommit drives XA 2PC. + * Usage: vl_threshold_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" + +int main(int argc, char **argv) +{ + long rows = 0; (void)rows; (void)argc; (void)argv; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, thr = 50000000; char h_bizdate[16], sev[8] = "ERROR"; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, (argc > 1) ? argv[1] : "2026-07-19", 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; } + + if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) { thr = 50000000; } + EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase + WHERE biz_date = :h_bizdate AND amount > :thr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_threshold_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_threshold_batch COMMIT: bizdate=%s 임계=%ld 초과=%ld\n", h_bizdate, thr, h_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/dbio/vl_appr_dbio.h b/app/src/vl/dbio/vl_appr_dbio.h new file mode 100644 index 0000000..08f9110 --- /dev/null +++ b/app/src/vl/dbio/vl_appr_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_appr_dbio.h - 승인 건수 조회 (매입 대비) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_APPR_DBIO_H +#define VL_APPR_DBIO_H + +long vldb_appr_count(long pid); + +#endif /* VL_APPR_DBIO_H */ diff --git a/app/src/vl/dbio/vl_appr_dbio.pgc b/app/src/vl/dbio/vl_appr_dbio.pgc new file mode 100644 index 0000000..fe51776 --- /dev/null +++ b/app/src/vl/dbio/vl_appr_dbio.pgc @@ -0,0 +1,17 @@ +/* + * vl_appr_dbio.pgc - 승인 건수 조회 (매입 대비) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_appr_dbio.h" + +long vldb_appr_count(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM approval WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("vldb_appr_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_balance_dbio.h b/app/src/vl/dbio/vl_balance_dbio.h new file mode 100644 index 0000000..ab3dfbb --- /dev/null +++ b/app/src/vl/dbio/vl_balance_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_balance_dbio.h - 가맹점 매입 순액 합계 (정산완료) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_BALANCE_DBIO_H +#define VL_BALANCE_DBIO_H + +long vldb_merch_net(const char *merch, const char *bizdate); + +#endif /* VL_BALANCE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_balance_dbio.pgc b/app/src/vl/dbio/vl_balance_dbio.pgc new file mode 100644 index 0000000..f55064a --- /dev/null +++ b/app/src/vl/dbio/vl_balance_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_balance_dbio.pgc - 가맹점 매입 순액 합계 (정산완료) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_balance_dbio.h" + +long vldb_merch_net(const char *merch, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_net; 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 SELECT coalesce(sum(net),0) INTO :h_net FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { userlog("vldb_merch_net FAIL [%d]", sqlca.sqlcode); return -1; } + return h_net; +} diff --git a/app/src/vl/dbio/vl_batchchk_dbio.h b/app/src/vl/dbio/vl_batchchk_dbio.h new file mode 100644 index 0000000..f2c772f --- /dev/null +++ b/app/src/vl/dbio/vl_batchchk_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_batchchk_dbio.h - 영업일 매입 건수 (배치 정합용) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_BATCHCHK_DBIO_H +#define VL_BATCHCHK_DBIO_H + +long vldb_purchase_day_count(const char *bizdate); + +#endif /* VL_BATCHCHK_DBIO_H */ diff --git a/app/src/vl/dbio/vl_batchchk_dbio.pgc b/app/src/vl/dbio/vl_batchchk_dbio.pgc new file mode 100644 index 0000000..cd57d21 --- /dev/null +++ b/app/src/vl/dbio/vl_batchchk_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_batchchk_dbio.pgc - 영업일 매입 건수 (배치 정합용) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_batchchk_dbio.h" + +long vldb_purchase_day_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 purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("vldb_purchase_day_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_bin_query_dbio.h b/app/src/vl/dbio/vl_bin_query_dbio.h new file mode 100644 index 0000000..7d1b144 --- /dev/null +++ b/app/src/vl/dbio/vl_bin_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_bin_query_dbio.h - BIN 등록/활성 여부 검증 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_BIN_QUERY_DBIO_H +#define VL_BIN_QUERY_DBIO_H + +int vldb_bin_active(const char *bin); + +#endif /* VL_BIN_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_bin_query_dbio.pgc b/app/src/vl/dbio/vl_bin_query_dbio.pgc new file mode 100644 index 0000000..c8474bb --- /dev/null +++ b/app/src/vl/dbio/vl_bin_query_dbio.pgc @@ -0,0 +1,21 @@ +/* + * vl_bin_query_dbio.pgc - BIN 등록/활성 여부 검증 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_bin_query_dbio.h" + +int vldb_bin_active(const char *bin) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_active; char h_bin[16]; + EXEC SQL END DECLARE SECTION; + 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) { userlog("vldb_bin_active FAIL [%d]", sqlca.sqlcode); return -1; } + if (h_cnt == 0) return 1; + if (h_active == 0) return 2; + return 0; +} diff --git a/app/src/vl/dbio/vl_checklog_query_dbio.h b/app/src/vl/dbio/vl_checklog_query_dbio.h new file mode 100644 index 0000000..41314bc --- /dev/null +++ b/app/src/vl/dbio/vl_checklog_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_checklog_query_dbio.h - 검증 로그 실패 건수 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_CHECKLOG_QUERY_DBIO_H +#define VL_CHECKLOG_QUERY_DBIO_H + +long vldb_checklog_fail(const char *check_type, const char *bizdate); + +#endif /* VL_CHECKLOG_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_checklog_query_dbio.pgc b/app/src/vl/dbio/vl_checklog_query_dbio.pgc new file mode 100644 index 0000000..360f8f0 --- /dev/null +++ b/app/src/vl/dbio/vl_checklog_query_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_checklog_query_dbio.pgc - 검증 로그 실패 건수 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_checklog_query_dbio.h" + +long vldb_checklog_fail(const char *check_type, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_type[32], 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_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_check_log + WHERE check_type = :h_type AND result_code <> 0 AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("vldb_checklog_fail FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_close_dbio.h b/app/src/vl/dbio/vl_close_dbio.h new file mode 100644 index 0000000..852a608 --- /dev/null +++ b/app/src/vl/dbio/vl_close_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_close_dbio.h - 의심거래 종결 처리 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_CLOSE_DBIO_H +#define VL_CLOSE_DBIO_H + +int vldb_close_suspect(long sid); + +#endif /* VL_CLOSE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_close_dbio.pgc b/app/src/vl/dbio/vl_close_dbio.pgc new file mode 100644 index 0000000..00b8077 --- /dev/null +++ b/app/src/vl/dbio/vl_close_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_close_dbio.pgc - 의심거래 종결 처리 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_close_dbio.h" + +int vldb_close_suspect(long sid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_sid = sid; + EXEC SQL END DECLARE SECTION; + EXEC SQL UPDATE vl_suspect SET status = 'CLOSED' + WHERE suspect_id = :h_sid AND status = 'OPEN'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_close_suspect FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/vl/dbio/vl_codeval_dbio.h b/app/src/vl/dbio/vl_codeval_dbio.h new file mode 100644 index 0000000..9ced76e --- /dev/null +++ b/app/src/vl/dbio/vl_codeval_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_codeval_dbio.h - 공통코드 유효성 (mm_code 활성) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_CODEVAL_DBIO_H +#define VL_CODEVAL_DBIO_H + +int vldb_code_active(const char *grp, const char *code); + +#endif /* VL_CODEVAL_DBIO_H */ diff --git a/app/src/vl/dbio/vl_codeval_dbio.pgc b/app/src/vl/dbio/vl_codeval_dbio.pgc new file mode 100644 index 0000000..cf8daec --- /dev/null +++ b/app/src/vl/dbio/vl_codeval_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_codeval_dbio.pgc - 공통코드 유효성 (mm_code 활성) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_codeval_dbio.h" + +int vldb_code_active(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 mm_code + WHERE code_group = :h_grp AND code = :h_code AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("vldb_code_active FAIL [%d]", sqlca.sqlcode); return -1; } + return (h_cnt > 0) ? 0 : 1; +} diff --git a/app/src/vl/dbio/vl_cross_dbio.h b/app/src/vl/dbio/vl_cross_dbio.h new file mode 100644 index 0000000..a7f06da --- /dev/null +++ b/app/src/vl/dbio/vl_cross_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_cross_dbio.h - 교차검증 존재비트 (매입/가맹점/승인) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_CROSS_DBIO_H +#define VL_CROSS_DBIO_H + +long vldb_cross_bits(long pid); + +#endif /* VL_CROSS_DBIO_H */ diff --git a/app/src/vl/dbio/vl_cross_dbio.pgc b/app/src/vl/dbio/vl_cross_dbio.pgc new file mode 100644 index 0000000..e2a9a48 --- /dev/null +++ b/app/src/vl/dbio/vl_cross_dbio.pgc @@ -0,0 +1,26 @@ +/* + * vl_cross_dbio.pgc - 교차검증 존재비트 (매입/가맹점/승인) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_cross_dbio.h" + +long vldb_cross_bits(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_p, h_m, h_a; + EXEC SQL END DECLARE SECTION; + long rc = 0; + EXEC SQL SELECT count(*) INTO :h_p FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_m FROM purchase p + JOIN merchant m ON m.merchant_id = p.merchant_id WHERE p.purchase_id = :h_pid; + if (sqlca.sqlcode < 0) return -1; + EXEC SQL SELECT count(*) INTO :h_a FROM approval WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) return -1; + if (h_p == 0) rc |= 1; + if (h_m == 0) rc |= 2; + if (h_a == 0) rc |= 4; + return rc; +} diff --git a/app/src/vl/dbio/vl_dup_query_dbio.h b/app/src/vl/dbio/vl_dup_query_dbio.h new file mode 100644 index 0000000..5bc9dc8 --- /dev/null +++ b/app/src/vl/dbio/vl_dup_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_dup_query_dbio.h - 중복 매입 건수 (가맹점/금액/영업일) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_DUP_QUERY_DBIO_H +#define VL_DUP_QUERY_DBIO_H + +long vldb_dup_count(const char *merch, long amt, const char *bizdate); + +#endif /* VL_DUP_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_dup_query_dbio.pgc b/app/src/vl/dbio/vl_dup_query_dbio.pgc new file mode 100644 index 0000000..857870c --- /dev/null +++ b/app/src/vl/dbio/vl_dup_query_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_dup_query_dbio.pgc - 중복 매입 건수 (가맹점/금액/영업일) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_dup_query_dbio.h" + +long vldb_dup_count(const char *merch, long amt, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_amt = amt, h_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 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) { userlog("vldb_dup_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_fee_query_dbio.h b/app/src/vl/dbio/vl_fee_query_dbio.h new file mode 100644 index 0000000..1439669 --- /dev/null +++ b/app/src/vl/dbio/vl_fee_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_fee_query_dbio.h - 매입 수수료/금액/가맹점 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_FEE_QUERY_DBIO_H +#define VL_FEE_QUERY_DBIO_H + +int vldb_purchase_fee(long pid, long *amt_out, long *fee_out, char *merch_out /*>=64*/); + +#endif /* VL_FEE_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_fee_query_dbio.pgc b/app/src/vl/dbio/vl_fee_query_dbio.pgc new file mode 100644 index 0000000..17a5900 --- /dev/null +++ b/app/src/vl/dbio/vl_fee_query_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_fee_query_dbio.pgc - 매입 수수료/금액/가맹점 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_fee_query_dbio.h" + +int vldb_purchase_fee(long pid, long *amt_out, long *fee_out, char *merch_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_amt, h_fee; char h_merch[64]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT amount, fee, merchant_id INTO :h_amt, :h_fee, :h_merch + FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_purchase_fee FAIL [%d]", sqlca.sqlcode); return -1; } + *amt_out = h_amt; *fee_out = h_fee; + strncpy(merch_out, h_merch, 63); merch_out[63] = 0; + return 0; +} diff --git a/app/src/vl/dbio/vl_format_dbio.h b/app/src/vl/dbio/vl_format_dbio.h new file mode 100644 index 0000000..aa1e7d4 --- /dev/null +++ b/app/src/vl/dbio/vl_format_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_format_dbio.h - 가맹점ID 형식 위반 건수 (길이) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_FORMAT_DBIO_H +#define VL_FORMAT_DBIO_H + +long vldb_bad_format_count(void); + +#endif /* VL_FORMAT_DBIO_H */ diff --git a/app/src/vl/dbio/vl_format_dbio.pgc b/app/src/vl/dbio/vl_format_dbio.pgc new file mode 100644 index 0000000..2f11514 --- /dev/null +++ b/app/src/vl/dbio/vl_format_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_format_dbio.pgc - 가맹점ID 형식 위반 건수 (길이) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_format_dbio.h" + +long vldb_bad_format_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM merchant + WHERE length(merchant_id) < 4 OR length(merchant_id) > 16; + if (sqlca.sqlcode < 0) { userlog("vldb_bad_format_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_ledger_query_dbio.h b/app/src/vl/dbio/vl_ledger_query_dbio.h new file mode 100644 index 0000000..f8261ed --- /dev/null +++ b/app/src/vl/dbio/vl_ledger_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_ledger_query_dbio.h - 원장 합계 조회 (SETTLELINK) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_LEDGER_QUERY_DBIO_H +#define VL_LEDGER_QUERY_DBIO_H + +long vldb_ledger_sum(long pid); + +#endif /* VL_LEDGER_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_ledger_query_dbio.pgc b/app/src/vl/dbio/vl_ledger_query_dbio.pgc new file mode 100644 index 0000000..a919488 --- /dev/null +++ b/app/src/vl/dbio/vl_ledger_query_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_ledger_query_dbio.pgc - 원장 합계 조회 (SETTLELINK) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_ledger_query_dbio.h" + +long vldb_ledger_sum(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_sum; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_sum FROM ledger + WHERE purchase_id = :h_pid AND entry_type = 'SETTLELINK'; + if (sqlca.sqlcode < 0) { userlog("vldb_ledger_sum FAIL [%d]", sqlca.sqlcode); return -1; } + return h_sum; +} diff --git a/app/src/vl/dbio/vl_merch_query_dbio.h b/app/src/vl/dbio/vl_merch_query_dbio.h new file mode 100644 index 0000000..e306656 --- /dev/null +++ b/app/src/vl/dbio/vl_merch_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_merch_query_dbio.h - 가맹점 상태 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_MERCH_QUERY_DBIO_H +#define VL_MERCH_QUERY_DBIO_H + +int vldb_merch_status(const char *merch, char *status_out /*>=16*/); + +#endif /* VL_MERCH_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_merch_query_dbio.pgc b/app/src/vl/dbio/vl_merch_query_dbio.pgc new file mode 100644 index 0000000..6a9b536 --- /dev/null +++ b/app/src/vl/dbio/vl_merch_query_dbio.pgc @@ -0,0 +1,19 @@ +/* + * vl_merch_query_dbio.pgc - 가맹점 상태 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_merch_query_dbio.h" + +int vldb_merch_status(const char *merch, char *status_out) +{ + 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; + EXEC SQL SELECT status INTO :h_status FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_merch_status FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(status_out, h_status, 15); status_out[15] = 0; + return 0; +} diff --git a/app/src/vl/dbio/vl_open_dbio.h b/app/src/vl/dbio/vl_open_dbio.h new file mode 100644 index 0000000..1e9b41c --- /dev/null +++ b/app/src/vl/dbio/vl_open_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_open_dbio.h - 미결(OPEN) 의심거래 총수 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_OPEN_DBIO_H +#define VL_OPEN_DBIO_H + +long vldb_open_count(void); + +#endif /* VL_OPEN_DBIO_H */ diff --git a/app/src/vl/dbio/vl_open_dbio.pgc b/app/src/vl/dbio/vl_open_dbio.pgc new file mode 100644 index 0000000..cf1b2a5 --- /dev/null +++ b/app/src/vl/dbio/vl_open_dbio.pgc @@ -0,0 +1,17 @@ +/* + * vl_open_dbio.pgc - 미결(OPEN) 의심거래 총수 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_open_dbio.h" + +long vldb_open_count(void) +{ + 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) { userlog("vldb_open_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_purchase_dbio.h b/app/src/vl/dbio/vl_purchase_dbio.h new file mode 100644 index 0000000..9cd8a31 --- /dev/null +++ b/app/src/vl/dbio/vl_purchase_dbio.h @@ -0,0 +1,12 @@ +/* + * vl_purchase_dbio.h - 매입 원거래 존재/금액 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_PURCHASE_DBIO_H +#define VL_PURCHASE_DBIO_H + +int vldb_purchase_exists(long pid); +long vldb_purchase_amount(long pid); + +#endif /* VL_PURCHASE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_purchase_dbio.pgc b/app/src/vl/dbio/vl_purchase_dbio.pgc new file mode 100644 index 0000000..8dd4776 --- /dev/null +++ b/app/src/vl/dbio/vl_purchase_dbio.pgc @@ -0,0 +1,27 @@ +/* + * vl_purchase_dbio.pgc - 매입 원거래 존재/금액 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_purchase_dbio.h" + +int vldb_purchase_exists(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("vldb_purchase_exists FAIL [%d]", sqlca.sqlcode); return -1; } + return (h_cnt > 0) ? 1 : 0; +} + +long vldb_purchase_amount(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_amt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_purchase_amount FAIL [%d]", sqlca.sqlcode); return -1; } + return h_amt; +} diff --git a/app/src/vl/dbio/vl_refinteg_dbio.h b/app/src/vl/dbio/vl_refinteg_dbio.h new file mode 100644 index 0000000..2169577 --- /dev/null +++ b/app/src/vl/dbio/vl_refinteg_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_refinteg_dbio.h - 참조무결성 위반 건수 (가맹점 orphan) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_REFINTEG_DBIO_H +#define VL_REFINTEG_DBIO_H + +long vldb_orphan_count(long pid); + +#endif /* VL_REFINTEG_DBIO_H */ diff --git a/app/src/vl/dbio/vl_refinteg_dbio.pgc b/app/src/vl/dbio/vl_refinteg_dbio.pgc new file mode 100644 index 0000000..bd50c5f --- /dev/null +++ b/app/src/vl/dbio/vl_refinteg_dbio.pgc @@ -0,0 +1,19 @@ +/* + * vl_refinteg_dbio.pgc - 참조무결성 위반 건수 (가맹점 orphan) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_refinteg_dbio.h" + +long vldb_orphan_count(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_cnt; + EXEC SQL END DECLARE SECTION; + 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) { userlog("vldb_orphan_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_report_dbio.h b/app/src/vl/dbio/vl_report_dbio.h new file mode 100644 index 0000000..8036c54 --- /dev/null +++ b/app/src/vl/dbio/vl_report_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_report_dbio.h - 심각도별 의심거래 리포트 (전기간) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_REPORT_DBIO_H +#define VL_REPORT_DBIO_H + +long vldb_report_by_sev(const char *sev); + +#endif /* VL_REPORT_DBIO_H */ diff --git a/app/src/vl/dbio/vl_report_dbio.pgc b/app/src/vl/dbio/vl_report_dbio.pgc new file mode 100644 index 0000000..2cb49c2 --- /dev/null +++ b/app/src/vl/dbio/vl_report_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_report_dbio.pgc - 심각도별 의심거래 리포트 (전기간) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_report_dbio.h" + +long vldb_report_by_sev(const char *sev) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_sev[8]; + EXEC SQL END DECLARE SECTION; + strncpy(h_sev, sev, sizeof(h_sev)-1); h_sev[sizeof(h_sev)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect WHERE severity = :h_sev; + if (sqlca.sqlcode < 0) { userlog("vldb_report_by_sev FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_rule_list_dbio.h b/app/src/vl/dbio/vl_rule_list_dbio.h new file mode 100644 index 0000000..a1e04b9 --- /dev/null +++ b/app/src/vl/dbio/vl_rule_list_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_rule_list_dbio.h - 활성 검증규칙 수 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_RULE_LIST_DBIO_H +#define VL_RULE_LIST_DBIO_H + +long vldb_rule_count(void); + +#endif /* VL_RULE_LIST_DBIO_H */ diff --git a/app/src/vl/dbio/vl_rule_list_dbio.pgc b/app/src/vl/dbio/vl_rule_list_dbio.pgc new file mode 100644 index 0000000..e9f9c1e --- /dev/null +++ b/app/src/vl/dbio/vl_rule_list_dbio.pgc @@ -0,0 +1,17 @@ +/* + * vl_rule_list_dbio.pgc - 활성 검증규칙 수 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_rule_list_dbio.h" + +long vldb_rule_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_rule WHERE status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("vldb_rule_count FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_settle_query_dbio.h b/app/src/vl/dbio/vl_settle_query_dbio.h new file mode 100644 index 0000000..07f81d2 --- /dev/null +++ b/app/src/vl/dbio/vl_settle_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_settle_query_dbio.h - 정산 순액 합계 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_SETTLE_QUERY_DBIO_H +#define VL_SETTLE_QUERY_DBIO_H + +long vldb_settle_net(long pid); + +#endif /* VL_SETTLE_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_settle_query_dbio.pgc b/app/src/vl/dbio/vl_settle_query_dbio.pgc new file mode 100644 index 0000000..9c9e035 --- /dev/null +++ b/app/src/vl/dbio/vl_settle_query_dbio.pgc @@ -0,0 +1,17 @@ +/* + * vl_settle_query_dbio.pgc - 정산 순액 합계 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_settle_query_dbio.h" + +long vldb_settle_net(long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid, h_net; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_net FROM settlement WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("vldb_settle_net FAIL [%d]", sqlca.sqlcode); return -1; } + return h_net; +} diff --git a/app/src/vl/dbio/vl_status_query_dbio.h b/app/src/vl/dbio/vl_status_query_dbio.h new file mode 100644 index 0000000..3f3bdfd --- /dev/null +++ b/app/src/vl/dbio/vl_status_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_status_query_dbio.h - 매입 상태값 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_STATUS_QUERY_DBIO_H +#define VL_STATUS_QUERY_DBIO_H + +int vldb_purchase_status(long pid, char *status_out /*>=8*/); + +#endif /* VL_STATUS_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_status_query_dbio.pgc b/app/src/vl/dbio/vl_status_query_dbio.pgc new file mode 100644 index 0000000..0c46024 --- /dev/null +++ b/app/src/vl/dbio/vl_status_query_dbio.pgc @@ -0,0 +1,18 @@ +/* + * vl_status_query_dbio.pgc - 매입 상태값 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_status_query_dbio.h" + +int vldb_purchase_status(long pid, char *status_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_pid = pid; char h_status[8]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT status INTO :h_status FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_purchase_status FAIL [%d]", sqlca.sqlcode); return -1; } + strncpy(status_out, h_status, 7); status_out[7] = 0; + return 0; +} diff --git a/app/src/vl/dbio/vl_suspect_query_dbio.h b/app/src/vl/dbio/vl_suspect_query_dbio.h new file mode 100644 index 0000000..57f555d --- /dev/null +++ b/app/src/vl/dbio/vl_suspect_query_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_suspect_query_dbio.h - 심각도별 의심거래 건수 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_SUSPECT_QUERY_DBIO_H +#define VL_SUSPECT_QUERY_DBIO_H + +long vldb_suspect_by_sev(const char *sev, const char *bizdate); + +#endif /* VL_SUSPECT_QUERY_DBIO_H */ diff --git a/app/src/vl/dbio/vl_suspect_query_dbio.pgc b/app/src/vl/dbio/vl_suspect_query_dbio.pgc new file mode 100644 index 0000000..943561b --- /dev/null +++ b/app/src/vl/dbio/vl_suspect_query_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_suspect_query_dbio.pgc - 심각도별 의심거래 건수 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_suspect_query_dbio.h" + +long vldb_suspect_by_sev(const char *sev, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; char h_sev[8], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + 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) { userlog("vldb_suspect_by_sev FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/dbio/vl_threshold_dbio.h b/app/src/vl/dbio/vl_threshold_dbio.h new file mode 100644 index 0000000..f28572c --- /dev/null +++ b/app/src/vl/dbio/vl_threshold_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_threshold_dbio.h - 규칙 임계치 조회 copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_THRESHOLD_DBIO_H +#define VL_THRESHOLD_DBIO_H + +long vldb_rule_threshold(const char *rule_code); + +#endif /* VL_THRESHOLD_DBIO_H */ diff --git a/app/src/vl/dbio/vl_threshold_dbio.pgc b/app/src/vl/dbio/vl_threshold_dbio.pgc new file mode 100644 index 0000000..3084e6a --- /dev/null +++ b/app/src/vl/dbio/vl_threshold_dbio.pgc @@ -0,0 +1,19 @@ +/* + * vl_threshold_dbio.pgc - 규칙 임계치 조회 (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_threshold_dbio.h" + +long vldb_rule_threshold(const char *rule_code) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_thr; char h_rule[32]; + 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 INTO :h_thr FROM vl_rule + WHERE rule_code = :h_rule AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { userlog("vldb_rule_threshold FAIL [%d]", sqlca.sqlcode); return -1; } + return h_thr; +} diff --git a/app/src/vl/dbio/vl_tolerance_dbio.h b/app/src/vl/dbio/vl_tolerance_dbio.h new file mode 100644 index 0000000..80c1157 --- /dev/null +++ b/app/src/vl/dbio/vl_tolerance_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_tolerance_dbio.h - 금액 허용오차 위반 건수 (amount != fee+net) copybook (part of libvldbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_TOLERANCE_DBIO_H +#define VL_TOLERANCE_DBIO_H + +long vldb_tolerance_break(const char *bizdate, long tol); + +#endif /* VL_TOLERANCE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_tolerance_dbio.pgc b/app/src/vl/dbio/vl_tolerance_dbio.pgc new file mode 100644 index 0000000..0a2a906 --- /dev/null +++ b/app/src/vl/dbio/vl_tolerance_dbio.pgc @@ -0,0 +1,19 @@ +/* + * vl_tolerance_dbio.pgc - 금액 허용오차 위반 건수 (amount != fee+net) (libvldbio.a). + * ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "vl_tolerance_dbio.h" + +long vldb_tolerance_break(const char *bizdate, long tol) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_tol = tol; 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 + WHERE biz_date = :h_bizdate AND abs(amount - (fee + net)) > :h_tol; + if (sqlca.sqlcode < 0) { userlog("vldb_tolerance_break FAIL [%d]", sqlca.sqlcode); return -1; } + return h_cnt; +} diff --git a/app/src/vl/run/run_anomaly.sh b/app/src/vl/run/run_anomaly.sh new file mode 100755 index 0000000..b292666 --- /dev/null +++ b/app/src/vl/run/run_anomaly.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_anomaly.sh - 이상거래 탐지 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_anomaly.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_anomaly.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_anomaly_batch "$BIZDATE" diff --git a/app/src/vl/run/run_appr_audit.sh b/app/src/vl/run/run_appr_audit.sh new file mode 100755 index 0000000..728173d --- /dev/null +++ b/app/src/vl/run/run_appr_audit.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_appr_audit.sh - 승인 누락 감사 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_appr_audit.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_appr_audit.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_appr_audit_batch "$BIZDATE" diff --git a/app/src/vl/run/run_balance.sh b/app/src/vl/run/run_balance.sh new file mode 100755 index 0000000..434927b --- /dev/null +++ b/app/src/vl/run/run_balance.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_balance.sh - 가맹점 대차 불일치 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_balance.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_balance.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_balance_batch "$BIZDATE" diff --git a/app/src/vl/run/run_bin_check.sh b/app/src/vl/run/run_bin_check.sh new file mode 100755 index 0000000..6ca47e1 --- /dev/null +++ b/app/src/vl/run/run_bin_check.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_bin_check.sh - 미등록 BIN 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_bin_check.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_bin_check.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_bin_check_batch "$BIZDATE" diff --git a/app/src/vl/run/run_check_sweep.sh b/app/src/vl/run/run_check_sweep.sh new file mode 100755 index 0000000..79e566f --- /dev/null +++ b/app/src/vl/run/run_check_sweep.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_check_sweep.sh - 검증로그 실패 스윕 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_check_sweep.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_check_sweep.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_check_sweep_batch "$BIZDATE" diff --git a/app/src/vl/run/run_cross.sh b/app/src/vl/run/run_cross.sh new file mode 100755 index 0000000..6a726ac --- /dev/null +++ b/app/src/vl/run/run_cross.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_cross.sh - 교차검증 결측 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_cross.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_cross.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_cross_batch "$BIZDATE" diff --git a/app/src/vl/run/run_dup_scan.sh b/app/src/vl/run/run_dup_scan.sh new file mode 100755 index 0000000..aee9731 --- /dev/null +++ b/app/src/vl/run/run_dup_scan.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_dup_scan.sh - 중복 매입 스캔 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_dup_scan.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_dup_scan.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_dup_scan_batch "$BIZDATE" diff --git a/app/src/vl/run/run_expiry.sh b/app/src/vl/run/run_expiry.sh new file mode 100755 index 0000000..d68d56b --- /dev/null +++ b/app/src/vl/run/run_expiry.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_expiry.sh - 카드 만료 의심 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_expiry.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_expiry.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_expiry_batch "$BIZDATE" diff --git a/app/src/vl/run/run_format.sh b/app/src/vl/run/run_format.sh new file mode 100755 index 0000000..ac7e6d6 --- /dev/null +++ b/app/src/vl/run/run_format.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_format.sh - 가맹점ID 형식 위반 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_format.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_format.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_format_batch diff --git a/app/src/vl/run/run_ledger_recon.sh b/app/src/vl/run/run_ledger_recon.sh new file mode 100755 index 0000000..220c255 --- /dev/null +++ b/app/src/vl/run/run_ledger_recon.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_ledger_recon.sh - 원장-매입 순액 대사 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_ledger_recon.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_ledger_recon.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_ledger_recon_batch "$BIZDATE" diff --git a/app/src/vl/run/run_limit_scan.sh b/app/src/vl/run/run_limit_scan.sh new file mode 100755 index 0000000..5358252 --- /dev/null +++ b/app/src/vl/run/run_limit_scan.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_limit_scan.sh - 한도 초과 매입 스캔 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_limit_scan.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_limit_scan.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_limit_scan_batch "$BIZDATE" diff --git a/app/src/vl/run/run_refinteg.sh b/app/src/vl/run/run_refinteg.sh new file mode 100755 index 0000000..f12f8b4 --- /dev/null +++ b/app/src/vl/run/run_refinteg.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_refinteg.sh - 참조무결성 검사 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_refinteg.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_refinteg.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_refinteg_batch diff --git a/app/src/vl/run/run_report.sh b/app/src/vl/run/run_report.sh new file mode 100755 index 0000000..9215ae1 --- /dev/null +++ b/app/src/vl/run/run_report.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_report.sh - 심각도별 의심거래 리포트 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_report.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_report.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_report_batch "$BIZDATE" diff --git a/app/src/vl/run/run_sales.sh b/app/src/vl/run/run_sales.sh new file mode 100755 index 0000000..ef6d50c --- /dev/null +++ b/app/src/vl/run/run_sales.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_sales.sh - 매입 원거래 검증 배치 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_sales.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_sales.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_sales_batch "$BIZDATE" diff --git a/app/src/vl/run/run_settle_recon.sh b/app/src/vl/run/run_settle_recon.sh new file mode 100755 index 0000000..2a08f34 --- /dev/null +++ b/app/src/vl/run/run_settle_recon.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_settle_recon.sh - 정산 불일치 대사 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_settle_recon.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_settle_recon.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_settle_recon_batch "$BIZDATE" diff --git a/app/src/vl/run/run_suspect_close.sh b/app/src/vl/run/run_suspect_close.sh new file mode 100755 index 0000000..2278938 --- /dev/null +++ b/app/src/vl/run/run_suspect_close.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_suspect_close.sh - 노후 의심거래 자동 종결 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_suspect_close.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_suspect_close.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_suspect_close_batch "$ARG" diff --git a/app/src/vl/run/run_threshold.sh b/app/src/vl/run/run_threshold.sh new file mode 100755 index 0000000..d4cdace --- /dev/null +++ b/app/src/vl/run/run_threshold.sh @@ -0,0 +1,14 @@ +#!/bin/bash +## +## run_threshold.sh - 임계 초과 규칙 점검 실행. +## 운영 스크립트: Enduro/X + ECPG/XA 런타임 환경을 설정하고 vl 검증 배치를 실행. +## Usage: run_threshold.sh [BIZDATE] [ARG] +## +set -e +. /app/conf/setapp.sh + +BIZDATE="${1:-2026-07-19}" +ARG="${2:-}" + +echo "[run_threshold.sh] BIZDATE=$BIZDATE ARG=$ARG" +exec /app/bin/vl_threshold_batch "$BIZDATE" diff --git a/app/src/vl/svc/VL_AMOUNT.h b/app/src/vl/svc/VL_AMOUNT.h new file mode 100644 index 0000000..b454aa2 --- /dev/null +++ b/app/src/vl/svc/VL_AMOUNT.h @@ -0,0 +1,12 @@ +/* + * VL_AMOUNT.h - vl 서비스 VL_AMOUNT 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_ARG1, T_RC + */ +#ifndef VL_AMOUNT_H +#define VL_AMOUNT_H + +#include "vl_svc.h" + +void VL_AMOUNT(TPSVCINFO *p); + +#endif /* VL_AMOUNT_H */ diff --git a/app/src/vl/svc/VL_AMOUNT.pgc b/app/src/vl/svc/VL_AMOUNT.pgc new file mode 100644 index 0000000..4539ad9 --- /dev/null +++ b/app/src/vl/svc/VL_AMOUNT.pgc @@ -0,0 +1,21 @@ +/* + * VL_AMOUNT.pgc - vl 모듈 서비스 VL_AMOUNT (one service = one file). + * 기능: 금액 유효성(0data; + 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); +} diff --git a/app/src/vl/svc/VL_ANOMALY.h b/app/src/vl/svc/VL_ANOMALY.h new file mode 100644 index 0000000..1c36c11 --- /dev/null +++ b/app/src/vl/svc/VL_ANOMALY.h @@ -0,0 +1,12 @@ +/* + * VL_ANOMALY.h - vl 서비스 VL_ANOMALY 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_ID1, T_PURCHASE_ID, T_RC + */ +#ifndef VL_ANOMALY_H +#define VL_ANOMALY_H + +#include "vl_svc.h" + +void VL_ANOMALY(TPSVCINFO *p); + +#endif /* VL_ANOMALY_H */ diff --git a/app/src/vl/svc/VL_ANOMALY.pgc b/app/src/vl/svc/VL_ANOMALY.pgc new file mode 100644 index 0000000..673d691 --- /dev/null +++ b/app/src/vl/svc/VL_ANOMALY.pgc @@ -0,0 +1,31 @@ +/* + * VL_ANOMALY.pgc - vl 모듈 서비스 VL_ANOMALY (one service = one file). + * 기능: 이상거래 탐지 후 의심거래 적재 (dbio). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_ANOMALY.h" + +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); +} diff --git a/app/src/vl/svc/VL_APPR_ACQ.h b/app/src/vl/svc/VL_APPR_ACQ.h new file mode 100644 index 0000000..c65951f --- /dev/null +++ b/app/src/vl/svc/VL_APPR_ACQ.h @@ -0,0 +1,12 @@ +/* + * VL_APPR_ACQ.h - vl 서비스 VL_APPR_ACQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_PURCHASE_ID, T_RC + */ +#ifndef VL_APPR_ACQ_H +#define VL_APPR_ACQ_H + +#include "vl_svc.h" + +void VL_APPR_ACQ(TPSVCINFO *p); + +#endif /* VL_APPR_ACQ_H */ diff --git a/app/src/vl/svc/VL_APPR_ACQ.pgc b/app/src/vl/svc/VL_APPR_ACQ.pgc new file mode 100644 index 0000000..4f7337e --- /dev/null +++ b/app/src/vl/svc/VL_APPR_ACQ.pgc @@ -0,0 +1,24 @@ +/* + * VL_APPR_ACQ.pgc - vl 모듈 서비스 VL_APPR_ACQ (one service = one file). + * 기능: 승인-매입 매칭 검증 (RC 1=승인 없음). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_APPR_ACQ.h" + +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); +} diff --git a/app/src/vl/svc/VL_BALANCE.h b/app/src/vl/svc/VL_BALANCE.h new file mode 100644 index 0000000..dcfa837 --- /dev/null +++ b/app/src/vl/svc/VL_BALANCE.h @@ -0,0 +1,12 @@ +/* + * VL_BALANCE.h - vl 서비스 VL_BALANCE 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_DELTA, T_GROSS, T_MERCHANT, T_NET, T_RC + */ +#ifndef VL_BALANCE_H +#define VL_BALANCE_H + +#include "vl_svc.h" + +void VL_BALANCE(TPSVCINFO *p); + +#endif /* VL_BALANCE_H */ diff --git a/app/src/vl/svc/VL_BALANCE.pgc b/app/src/vl/svc/VL_BALANCE.pgc new file mode 100644 index 0000000..37cc717 --- /dev/null +++ b/app/src/vl/svc/VL_BALANCE.pgc @@ -0,0 +1,34 @@ +/* + * VL_BALANCE.pgc - vl 모듈 서비스 VL_BALANCE (one service = one file). + * 기능: 가맹점 순액 대차 검증 (sum(purchase.net) vs sum(settlement.net)). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_BALANCE.h" + +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); +} diff --git a/app/src/vl/svc/VL_BATCH_CHK.h b/app/src/vl/svc/VL_BATCH_CHK.h new file mode 100644 index 0000000..25c5a15 --- /dev/null +++ b/app/src/vl/svc/VL_BATCH_CHK.h @@ -0,0 +1,12 @@ +/* + * VL_BATCH_CHK.h - vl 서비스 VL_BATCH_CHK 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_GROSS, T_RC + */ +#ifndef VL_BATCH_CHK_H +#define VL_BATCH_CHK_H + +#include "vl_svc.h" + +void VL_BATCH_CHK(TPSVCINFO *p); + +#endif /* VL_BATCH_CHK_H */ diff --git a/app/src/vl/svc/VL_BATCH_CHK.pgc b/app/src/vl/svc/VL_BATCH_CHK.pgc new file mode 100644 index 0000000..5a93525 --- /dev/null +++ b/app/src/vl/svc/VL_BATCH_CHK.pgc @@ -0,0 +1,30 @@ +/* + * VL_BATCH_CHK.pgc - vl 모듈 서비스 VL_BATCH_CHK (one service = one file). + * 기능: 배치 정합(purchase 건수 vs merchant_summary.txn_count). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_BATCH_CHK.h" + +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); +} diff --git a/app/src/vl/svc/VL_BIN.h b/app/src/vl/svc/VL_BIN.h new file mode 100644 index 0000000..ff7438e --- /dev/null +++ b/app/src/vl/svc/VL_BIN.h @@ -0,0 +1,12 @@ +/* + * VL_BIN.h - vl 서비스 VL_BIN 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_RC + */ +#ifndef VL_BIN_H +#define VL_BIN_H + +#include "vl_svc.h" + +void VL_BIN(TPSVCINFO *p); + +#endif /* VL_BIN_H */ diff --git a/app/src/vl/svc/VL_BIN.pgc b/app/src/vl/svc/VL_BIN.pgc new file mode 100644 index 0000000..7993e87 --- /dev/null +++ b/app/src/vl/svc/VL_BIN.pgc @@ -0,0 +1,27 @@ +/* + * VL_BIN.pgc - vl 모듈 서비스 VL_BIN (one service = one file). + * 기능: BIN 유효성 검증 vs mm_bin (RC 1=미등록,2=비활성). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_BIN.h" + +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); +} diff --git a/app/src/vl/svc/VL_CARD.h b/app/src/vl/svc/VL_CARD.h new file mode 100644 index 0000000..72602fb --- /dev/null +++ b/app/src/vl/svc/VL_CARD.h @@ -0,0 +1,12 @@ +/* + * VL_CARD.h - vl 서비스 VL_CARD 카피북 (copybook / record header). + * 사용 UBF 필드: T_RC, T_STR1 + */ +#ifndef VL_CARD_H +#define VL_CARD_H + +#include "vl_svc.h" + +void VL_CARD(TPSVCINFO *p); + +#endif /* VL_CARD_H */ diff --git a/app/src/vl/svc/VL_CARD.pgc b/app/src/vl/svc/VL_CARD.pgc new file mode 100644 index 0000000..fdf74b7 --- /dev/null +++ b/app/src/vl/svc/VL_CARD.pgc @@ -0,0 +1,19 @@ +/* + * VL_CARD.pgc - vl 모듈 서비스 VL_CARD (one service = one file). + * 기능: 카드번호 Luhn 검증 (RC 1=체크섬 불일치). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_CARD.h" + +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); +} diff --git a/app/src/vl/svc/VL_CODEVAL.h b/app/src/vl/svc/VL_CODEVAL.h new file mode 100644 index 0000000..5a76629 --- /dev/null +++ b/app/src/vl/svc/VL_CODEVAL.h @@ -0,0 +1,12 @@ +/* + * VL_CODEVAL.h - vl 서비스 VL_CODEVAL 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_RC, T_STR3 + */ +#ifndef VL_CODEVAL_H +#define VL_CODEVAL_H + +#include "vl_svc.h" + +void VL_CODEVAL(TPSVCINFO *p); + +#endif /* VL_CODEVAL_H */ diff --git a/app/src/vl/svc/VL_CODEVAL.pgc b/app/src/vl/svc/VL_CODEVAL.pgc new file mode 100644 index 0000000..89c7549 --- /dev/null +++ b/app/src/vl/svc/VL_CODEVAL.pgc @@ -0,0 +1,28 @@ +/* + * VL_CODEVAL.pgc - vl 모듈 서비스 VL_CODEVAL (one service = one file). + * 기능: 공통코드 유효성 검증 vs mm_code (RC 1=미존재). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_CODEVAL.h" + +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); +} diff --git a/app/src/vl/svc/VL_CONFIRM.h b/app/src/vl/svc/VL_CONFIRM.h new file mode 100644 index 0000000..1014f8f --- /dev/null +++ b/app/src/vl/svc/VL_CONFIRM.h @@ -0,0 +1,12 @@ +/* + * VL_CONFIRM.h - vl 서비스 VL_CONFIRM 카피북 (copybook / record header). + * 사용 UBF 필드: T_ID1, T_RC + */ +#ifndef VL_CONFIRM_H +#define VL_CONFIRM_H + +#include "vl_svc.h" + +void VL_CONFIRM(TPSVCINFO *p); + +#endif /* VL_CONFIRM_H */ diff --git a/app/src/vl/svc/VL_CONFIRM.pgc b/app/src/vl/svc/VL_CONFIRM.pgc new file mode 100644 index 0000000..2b36cbf --- /dev/null +++ b/app/src/vl/svc/VL_CONFIRM.pgc @@ -0,0 +1,21 @@ +/* + * VL_CONFIRM.pgc - vl 모듈 서비스 VL_CONFIRM (one service = one file). + * 기능: 의심거래 종결 확정 (status CLOSED by suspect_id). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_CONFIRM.h" + +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); +} diff --git a/app/src/vl/svc/VL_CROSSCHK.h b/app/src/vl/svc/VL_CROSSCHK.h new file mode 100644 index 0000000..d41b74d --- /dev/null +++ b/app/src/vl/svc/VL_CROSSCHK.h @@ -0,0 +1,12 @@ +/* + * VL_CROSSCHK.h - vl 서비스 VL_CROSSCHK 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_RC + */ +#ifndef VL_CROSSCHK_H +#define VL_CROSSCHK_H + +#include "vl_svc.h" + +void VL_CROSSCHK(TPSVCINFO *p); + +#endif /* VL_CROSSCHK_H */ diff --git a/app/src/vl/svc/VL_CROSSCHK.pgc b/app/src/vl/svc/VL_CROSSCHK.pgc new file mode 100644 index 0000000..7ac8936 --- /dev/null +++ b/app/src/vl/svc/VL_CROSSCHK.pgc @@ -0,0 +1,29 @@ +/* + * VL_CROSSCHK.pgc - vl 모듈 서비스 VL_CROSSCHK (one service = one file). + * 기능: 교차검증(매입+가맹점+승인 동시 존재, RC 비트합). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_CROSSCHK.h" + +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); +} diff --git a/app/src/vl/svc/VL_DATE.h b/app/src/vl/svc/VL_DATE.h new file mode 100644 index 0000000..a31009b --- /dev/null +++ b/app/src/vl/svc/VL_DATE.h @@ -0,0 +1,12 @@ +/* + * VL_DATE.h - vl 서비스 VL_DATE 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_RC + */ +#ifndef VL_DATE_H +#define VL_DATE_H + +#include "vl_svc.h" + +void VL_DATE(TPSVCINFO *p); + +#endif /* VL_DATE_H */ diff --git a/app/src/vl/svc/VL_DATE.pgc b/app/src/vl/svc/VL_DATE.pgc new file mode 100644 index 0000000..3859d9f --- /dev/null +++ b/app/src/vl/svc/VL_DATE.pgc @@ -0,0 +1,26 @@ +/* + * VL_DATE.pgc - vl 모듈 서비스 VL_DATE (one service = one file). + * 기능: 영업일 유효성(미래일자 금지) 검증 (RC 1=미래). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DATE.h" + +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); +} diff --git a/app/src/vl/svc/VL_DUP.h b/app/src/vl/svc/VL_DUP.h new file mode 100644 index 0000000..c70e864 --- /dev/null +++ b/app/src/vl/svc/VL_DUP.h @@ -0,0 +1,12 @@ +/* + * VL_DUP.h - vl 서비스 VL_DUP 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_BIZDATE, T_COUNT, T_MERCHANT, T_RC + */ +#ifndef VL_DUP_H +#define VL_DUP_H + +#include "vl_svc.h" + +void VL_DUP(TPSVCINFO *p); + +#endif /* VL_DUP_H */ diff --git a/app/src/vl/svc/VL_DUP.pgc b/app/src/vl/svc/VL_DUP.pgc new file mode 100644 index 0000000..d909c18 --- /dev/null +++ b/app/src/vl/svc/VL_DUP.pgc @@ -0,0 +1,30 @@ +/* + * VL_DUP.pgc - vl 모듈 서비스 VL_DUP (one service = one file). + * 기능: 중복 매입 검증 (동일 가맹점/금액/영업일 2건 이상 → RC 1). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DUP.h" + +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); +} diff --git a/app/src/vl/svc/VL_DUPAUTH.h b/app/src/vl/svc/VL_DUPAUTH.h new file mode 100644 index 0000000..1f781e2 --- /dev/null +++ b/app/src/vl/svc/VL_DUPAUTH.h @@ -0,0 +1,12 @@ +/* + * VL_DUPAUTH.h - vl 서비스 VL_DUPAUTH 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_PURCHASE_ID, T_RC + */ +#ifndef VL_DUPAUTH_H +#define VL_DUPAUTH_H + +#include "vl_svc.h" + +void VL_DUPAUTH(TPSVCINFO *p); + +#endif /* VL_DUPAUTH_H */ diff --git a/app/src/vl/svc/VL_DUPAUTH.pgc b/app/src/vl/svc/VL_DUPAUTH.pgc new file mode 100644 index 0000000..1717687 --- /dev/null +++ b/app/src/vl/svc/VL_DUPAUTH.pgc @@ -0,0 +1,23 @@ +/* + * VL_DUPAUTH.pgc - vl 모듈 서비스 VL_DUPAUTH (one service = one file). + * 기능: 중복 승인 검증 (approval 2건 이상 → RC 1). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DUPAUTH.h" + +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); +} diff --git a/app/src/vl/svc/VL_EXPIRY.h b/app/src/vl/svc/VL_EXPIRY.h new file mode 100644 index 0000000..2890494 --- /dev/null +++ b/app/src/vl/svc/VL_EXPIRY.h @@ -0,0 +1,12 @@ +/* + * VL_EXPIRY.h - vl 서비스 VL_EXPIRY 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_RC, T_STR2 + */ +#ifndef VL_EXPIRY_H +#define VL_EXPIRY_H + +#include "vl_svc.h" + +void VL_EXPIRY(TPSVCINFO *p); + +#endif /* VL_EXPIRY_H */ diff --git a/app/src/vl/svc/VL_EXPIRY.pgc b/app/src/vl/svc/VL_EXPIRY.pgc new file mode 100644 index 0000000..6ee3903 --- /dev/null +++ b/app/src/vl/svc/VL_EXPIRY.pgc @@ -0,0 +1,24 @@ +/* + * VL_EXPIRY.pgc - vl 모듈 서비스 VL_EXPIRY (one service = one file). + * 기능: 카드 유효기간(YYYYMM) vs 영업일 검증 (RC 1=만료). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_EXPIRY.h" + +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); +} diff --git a/app/src/vl/svc/VL_FEE.h b/app/src/vl/svc/VL_FEE.h new file mode 100644 index 0000000..80e99e8 --- /dev/null +++ b/app/src/vl/svc/VL_FEE.h @@ -0,0 +1,12 @@ +/* + * VL_FEE.h - vl 서비스 VL_FEE 카피북 (copybook / record header). + * 사용 UBF 필드: T_DELTA, T_FEE, T_PURCHASE_ID, T_RC + */ +#ifndef VL_FEE_H +#define VL_FEE_H + +#include "vl_svc.h" + +void VL_FEE(TPSVCINFO *p); + +#endif /* VL_FEE_H */ diff --git a/app/src/vl/svc/VL_FEE.pgc b/app/src/vl/svc/VL_FEE.pgc new file mode 100644 index 0000000..fa29480 --- /dev/null +++ b/app/src/vl/svc/VL_FEE.pgc @@ -0,0 +1,29 @@ +/* + * VL_FEE.pgc - vl 모듈 서비스 VL_FEE (one service = one file). + * 기능: 수수료 재계산 정합 검증 (저장 fee vs MDR 재계산, RC 1=불일치). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_FEE.h" + +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); +} diff --git a/app/src/vl/svc/VL_FORMAT.h b/app/src/vl/svc/VL_FORMAT.h new file mode 100644 index 0000000..70783bc --- /dev/null +++ b/app/src/vl/svc/VL_FORMAT.h @@ -0,0 +1,12 @@ +/* + * VL_FORMAT.h - vl 서비스 VL_FORMAT 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_MERCHANT, T_RC + */ +#ifndef VL_FORMAT_H +#define VL_FORMAT_H + +#include "vl_svc.h" + +void VL_FORMAT(TPSVCINFO *p); + +#endif /* VL_FORMAT_H */ diff --git a/app/src/vl/svc/VL_FORMAT.pgc b/app/src/vl/svc/VL_FORMAT.pgc new file mode 100644 index 0000000..7e285d8 --- /dev/null +++ b/app/src/vl/svc/VL_FORMAT.pgc @@ -0,0 +1,20 @@ +/* + * VL_FORMAT.pgc - vl 모듈 서비스 VL_FORMAT (one service = one file). + * 기능: 가맹점ID 형식(길이 4~16) 검증 (RC 1=형식 위반). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_FORMAT.h" + +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); +} diff --git a/app/src/vl/svc/VL_LEDGER.h b/app/src/vl/svc/VL_LEDGER.h new file mode 100644 index 0000000..b56f2f2 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGER.h @@ -0,0 +1,12 @@ +/* + * VL_LEDGER.h - vl 서비스 VL_LEDGER 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_GROSS, T_PURCHASE_ID, T_RC + */ +#ifndef VL_LEDGER_H +#define VL_LEDGER_H + +#include "vl_svc.h" + +void VL_LEDGER(TPSVCINFO *p); + +#endif /* VL_LEDGER_H */ diff --git a/app/src/vl/svc/VL_LEDGER.pgc b/app/src/vl/svc/VL_LEDGER.pgc new file mode 100644 index 0000000..cc7f96f --- /dev/null +++ b/app/src/vl/svc/VL_LEDGER.pgc @@ -0,0 +1,32 @@ +/* + * VL_LEDGER.pgc - vl 모듈 서비스 VL_LEDGER (one service = one file). + * 기능: 원장 합계 정합 검증 (sum(ledger) vs purchase.net, RC 1=불일치). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LEDGER.h" + +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); +} diff --git a/app/src/vl/svc/VL_LIMIT.h b/app/src/vl/svc/VL_LIMIT.h new file mode 100644 index 0000000..263b25a --- /dev/null +++ b/app/src/vl/svc/VL_LIMIT.h @@ -0,0 +1,12 @@ +/* + * VL_LIMIT.h - vl 서비스 VL_LIMIT 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_MERCHANT, T_RC + */ +#ifndef VL_LIMIT_H +#define VL_LIMIT_H + +#include "vl_svc.h" + +void VL_LIMIT(TPSVCINFO *p); + +#endif /* VL_LIMIT_H */ diff --git a/app/src/vl/svc/VL_LIMIT.pgc b/app/src/vl/svc/VL_LIMIT.pgc new file mode 100644 index 0000000..173761e --- /dev/null +++ b/app/src/vl/svc/VL_LIMIT.pgc @@ -0,0 +1,26 @@ +/* + * VL_LIMIT.pgc - vl 모듈 서비스 VL_LIMIT (one service = one file). + * 기능: 매입금액 vs 가맹점 일한도 검증 (RC 1=초과). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LIMIT.h" + +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); +} diff --git a/app/src/vl/svc/VL_MERCH.h b/app/src/vl/svc/VL_MERCH.h new file mode 100644 index 0000000..afc79ec --- /dev/null +++ b/app/src/vl/svc/VL_MERCH.h @@ -0,0 +1,12 @@ +/* + * VL_MERCH.h - vl 서비스 VL_MERCH 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_RC, T_STATUS + */ +#ifndef VL_MERCH_H +#define VL_MERCH_H + +#include "vl_svc.h" + +void VL_MERCH(TPSVCINFO *p); + +#endif /* VL_MERCH_H */ diff --git a/app/src/vl/svc/VL_MERCH.pgc b/app/src/vl/svc/VL_MERCH.pgc new file mode 100644 index 0000000..e766e8c --- /dev/null +++ b/app/src/vl/svc/VL_MERCH.pgc @@ -0,0 +1,25 @@ +/* + * VL_MERCH.pgc - vl 모듈 서비스 VL_MERCH (one service = one file). + * 기능: 가맹점 상태 검증 (merchant.status != ACTIVE → RC 1). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_MERCH.h" + +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); +} diff --git a/app/src/vl/svc/VL_REFINTEG.h b/app/src/vl/svc/VL_REFINTEG.h new file mode 100644 index 0000000..8334895 --- /dev/null +++ b/app/src/vl/svc/VL_REFINTEG.h @@ -0,0 +1,12 @@ +/* + * VL_REFINTEG.h - vl 서비스 VL_REFINTEG 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_RC + */ +#ifndef VL_REFINTEG_H +#define VL_REFINTEG_H + +#include "vl_svc.h" + +void VL_REFINTEG(TPSVCINFO *p); + +#endif /* VL_REFINTEG_H */ diff --git a/app/src/vl/svc/VL_REFINTEG.pgc b/app/src/vl/svc/VL_REFINTEG.pgc new file mode 100644 index 0000000..bfb76ff --- /dev/null +++ b/app/src/vl/svc/VL_REFINTEG.pgc @@ -0,0 +1,24 @@ +/* + * VL_REFINTEG.pgc - vl 모듈 서비스 VL_REFINTEG (one service = one file). + * 기능: 참조무결성(purchase.merchant_id ∈ merchant) 검증. + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_REFINTEG.h" + +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); +} diff --git a/app/src/vl/svc/VL_REPORT.h b/app/src/vl/svc/VL_REPORT.h new file mode 100644 index 0000000..37e2833 --- /dev/null +++ b/app/src/vl/svc/VL_REPORT.h @@ -0,0 +1,12 @@ +/* + * VL_REPORT.h - vl 서비스 VL_REPORT 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_STR4 + */ +#ifndef VL_REPORT_H +#define VL_REPORT_H + +#include "vl_svc.h" + +void VL_REPORT(TPSVCINFO *p); + +#endif /* VL_REPORT_H */ diff --git a/app/src/vl/svc/VL_REPORT.pgc b/app/src/vl/svc/VL_REPORT.pgc new file mode 100644 index 0000000..c798b3e --- /dev/null +++ b/app/src/vl/svc/VL_REPORT.pgc @@ -0,0 +1,26 @@ +/* + * VL_REPORT.pgc - vl 모듈 서비스 VL_REPORT (one service = one file). + * 기능: 심각도별 의심거래 건수 리포트. + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_REPORT.h" + +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); +} diff --git a/app/src/vl/svc/VL_REQUIRED.h b/app/src/vl/svc/VL_REQUIRED.h new file mode 100644 index 0000000..ddce30a --- /dev/null +++ b/app/src/vl/svc/VL_REQUIRED.h @@ -0,0 +1,12 @@ +/* + * VL_REQUIRED.h - vl 서비스 VL_REQUIRED 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_MERCHANT, T_RC + */ +#ifndef VL_REQUIRED_H +#define VL_REQUIRED_H + +#include "vl_svc.h" + +void VL_REQUIRED(TPSVCINFO *p); + +#endif /* VL_REQUIRED_H */ diff --git a/app/src/vl/svc/VL_REQUIRED.pgc b/app/src/vl/svc/VL_REQUIRED.pgc new file mode 100644 index 0000000..df85fce --- /dev/null +++ b/app/src/vl/svc/VL_REQUIRED.pgc @@ -0,0 +1,20 @@ +/* + * VL_REQUIRED.pgc - vl 모듈 서비스 VL_REQUIRED (one service = one file). + * 기능: 필수필드(가맹점/금액) 존재 검증 (RC 비트합). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_REQUIRED.h" + +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); +} diff --git a/app/src/vl/svc/VL_RULE.h b/app/src/vl/svc/VL_RULE.h new file mode 100644 index 0000000..bee1714 --- /dev/null +++ b/app/src/vl/svc/VL_RULE.h @@ -0,0 +1,12 @@ +/* + * VL_RULE.h - vl 서비스 VL_RULE 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_STR1, T_STR3, T_STR4 + */ +#ifndef VL_RULE_H +#define VL_RULE_H + +#include "vl_svc.h" + +void VL_RULE(TPSVCINFO *p); + +#endif /* VL_RULE_H */ diff --git a/app/src/vl/svc/VL_RULE.pgc b/app/src/vl/svc/VL_RULE.pgc new file mode 100644 index 0000000..903bb09 --- /dev/null +++ b/app/src/vl/svc/VL_RULE.pgc @@ -0,0 +1,21 @@ +/* + * VL_RULE.pgc - vl 모듈 서비스 VL_RULE (one service = one file). + * 기능: 검증 규칙 등록/갱신 (dbio upsert). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULE.h" + +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); +} diff --git a/app/src/vl/svc/VL_SALES.h b/app/src/vl/svc/VL_SALES.h new file mode 100644 index 0000000..9002338 --- /dev/null +++ b/app/src/vl/svc/VL_SALES.h @@ -0,0 +1,12 @@ +/* + * VL_SALES.h - vl 서비스 VL_SALES 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_PURCHASE_ID, T_RC + */ +#ifndef VL_SALES_H +#define VL_SALES_H + +#include "vl_svc.h" + +void VL_SALES(TPSVCINFO *p); + +#endif /* VL_SALES_H */ diff --git a/app/src/vl/svc/VL_SALES.pgc b/app/src/vl/svc/VL_SALES.pgc new file mode 100644 index 0000000..391975f --- /dev/null +++ b/app/src/vl/svc/VL_SALES.pgc @@ -0,0 +1,25 @@ +/* + * VL_SALES.pgc - vl 모듈 서비스 VL_SALES (one service = one file). + * 기능: 매입 원거래 존재 검증 (RC 1=미존재). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SALES.h" + +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); +} diff --git a/app/src/vl/svc/VL_SETTLE.h b/app/src/vl/svc/VL_SETTLE.h new file mode 100644 index 0000000..3a17841 --- /dev/null +++ b/app/src/vl/svc/VL_SETTLE.h @@ -0,0 +1,12 @@ +/* + * VL_SETTLE.h - vl 서비스 VL_SETTLE 카피북 (copybook / record header). + * 사용 UBF 필드: T_NET, T_PURCHASE_ID, T_RC + */ +#ifndef VL_SETTLE_H +#define VL_SETTLE_H + +#include "vl_svc.h" + +void VL_SETTLE(TPSVCINFO *p); + +#endif /* VL_SETTLE_H */ diff --git a/app/src/vl/svc/VL_SETTLE.pgc b/app/src/vl/svc/VL_SETTLE.pgc new file mode 100644 index 0000000..79d0938 --- /dev/null +++ b/app/src/vl/svc/VL_SETTLE.pgc @@ -0,0 +1,27 @@ +/* + * VL_SETTLE.pgc - vl 모듈 서비스 VL_SETTLE (one service = one file). + * 기능: 정산금액 정합 검증 (settlement.net vs purchase.net, RC 1=불일치). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SETTLE.h" + +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); +} diff --git a/app/src/vl/svc/VL_STATUS.h b/app/src/vl/svc/VL_STATUS.h new file mode 100644 index 0000000..34ddc2b --- /dev/null +++ b/app/src/vl/svc/VL_STATUS.h @@ -0,0 +1,12 @@ +/* + * VL_STATUS.h - vl 서비스 VL_STATUS 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_MSG, T_RC + */ +#ifndef VL_STATUS_H +#define VL_STATUS_H + +#include "vl_svc.h" + +void VL_STATUS(TPSVCINFO *p); + +#endif /* VL_STATUS_H */ diff --git a/app/src/vl/svc/VL_STATUS.pgc b/app/src/vl/svc/VL_STATUS.pgc new file mode 100644 index 0000000..2ac9b5c --- /dev/null +++ b/app/src/vl/svc/VL_STATUS.pgc @@ -0,0 +1,21 @@ +/* + * VL_STATUS.pgc - vl 모듈 서비스 VL_STATUS (one service = one file). + * 기능: 서비스 상태 (전체 OPEN 의심거래 수, RC=0). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_STATUS.h" + +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); +} diff --git a/app/src/vl/svc/VL_STATUS_CHK.h b/app/src/vl/svc/VL_STATUS_CHK.h new file mode 100644 index 0000000..3b04770 --- /dev/null +++ b/app/src/vl/svc/VL_STATUS_CHK.h @@ -0,0 +1,12 @@ +/* + * VL_STATUS_CHK.h - vl 서비스 VL_STATUS_CHK 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_RC, T_STATUS + */ +#ifndef VL_STATUS_CHK_H +#define VL_STATUS_CHK_H + +#include "vl_svc.h" + +void VL_STATUS_CHK(TPSVCINFO *p); + +#endif /* VL_STATUS_CHK_H */ diff --git a/app/src/vl/svc/VL_STATUS_CHK.pgc b/app/src/vl/svc/VL_STATUS_CHK.pgc new file mode 100644 index 0000000..2a8b2df --- /dev/null +++ b/app/src/vl/svc/VL_STATUS_CHK.pgc @@ -0,0 +1,27 @@ +/* + * VL_STATUS_CHK.pgc - vl 모듈 서비스 VL_STATUS_CHK (one service = one file). + * 기능: 매입 상태 유효값 검증 (허용집합 밖 → RC 1). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_STATUS_CHK.h" + +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); +} diff --git a/app/src/vl/svc/VL_SUMMARY.h b/app/src/vl/svc/VL_SUMMARY.h new file mode 100644 index 0000000..8ef7332 --- /dev/null +++ b/app/src/vl/svc/VL_SUMMARY.h @@ -0,0 +1,12 @@ +/* + * VL_SUMMARY.h - vl 서비스 VL_SUMMARY 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_RC + */ +#ifndef VL_SUMMARY_H +#define VL_SUMMARY_H + +#include "vl_svc.h" + +void VL_SUMMARY(TPSVCINFO *p); + +#endif /* VL_SUMMARY_H */ diff --git a/app/src/vl/svc/VL_SUMMARY.pgc b/app/src/vl/svc/VL_SUMMARY.pgc new file mode 100644 index 0000000..2ceaaaa --- /dev/null +++ b/app/src/vl/svc/VL_SUMMARY.pgc @@ -0,0 +1,20 @@ +/* + * VL_SUMMARY.pgc - vl 모듈 서비스 VL_SUMMARY (one service = one file). + * 기능: 당일 미결(OPEN) 의심거래 요약 (dbio). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUMMARY.h" + +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); +} diff --git a/app/src/vl/svc/VL_THRESHOLD.h b/app/src/vl/svc/VL_THRESHOLD.h new file mode 100644 index 0000000..2d9d8c2 --- /dev/null +++ b/app/src/vl/svc/VL_THRESHOLD.h @@ -0,0 +1,12 @@ +/* + * VL_THRESHOLD.h - vl 서비스 VL_THRESHOLD 카피북 (copybook / record header). + * 사용 UBF 필드: T_AMOUNT, T_ARG1, T_RC, T_STR3, T_STR4 + */ +#ifndef VL_THRESHOLD_H +#define VL_THRESHOLD_H + +#include "vl_svc.h" + +void VL_THRESHOLD(TPSVCINFO *p); + +#endif /* VL_THRESHOLD_H */ diff --git a/app/src/vl/svc/VL_THRESHOLD.pgc b/app/src/vl/svc/VL_THRESHOLD.pgc new file mode 100644 index 0000000..53b4a26 --- /dev/null +++ b/app/src/vl/svc/VL_THRESHOLD.pgc @@ -0,0 +1,22 @@ +/* + * VL_THRESHOLD.pgc - vl 모듈 서비스 VL_THRESHOLD (one service = one file). + * 기능: 규칙 임계치 검사 (dbio get_rule; amount>threshold → RC 1). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_THRESHOLD.h" + +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); +} diff --git a/app/src/vl/svc/VL_TOLERANCE.h b/app/src/vl/svc/VL_TOLERANCE.h new file mode 100644 index 0000000..a5e4985 --- /dev/null +++ b/app/src/vl/svc/VL_TOLERANCE.h @@ -0,0 +1,12 @@ +/* + * VL_TOLERANCE.h - vl 서비스 VL_TOLERANCE 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_DELTA, T_PURCHASE_ID, T_RC + */ +#ifndef VL_TOLERANCE_H +#define VL_TOLERANCE_H + +#include "vl_svc.h" + +void VL_TOLERANCE(TPSVCINFO *p); + +#endif /* VL_TOLERANCE_H */ diff --git a/app/src/vl/svc/VL_TOLERANCE.pgc b/app/src/vl/svc/VL_TOLERANCE.pgc new file mode 100644 index 0000000..e860a69 --- /dev/null +++ b/app/src/vl/svc/VL_TOLERANCE.pgc @@ -0,0 +1,27 @@ +/* + * VL_TOLERANCE.pgc - vl 모듈 서비스 VL_TOLERANCE (one service = one file). + * 기능: 금액 허용오차(amount == fee+net ± tol) 검증 (RC 1=초과). + * 기존 인라인 vl_svr.pgc에서 분리; caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_TOLERANCE.h" + +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); +} diff --git a/app/src/vl/svc/vl_svc.h b/app/src/vl/svc/vl_svc.h new file mode 100644 index 0000000..6fbc4e5 --- /dev/null +++ b/app/src/vl/svc/vl_svc.h @@ -0,0 +1,33 @@ +/* + * vl_svc.h - vl (정합성검증/validation) 서비스 공통 카피북 (shared copybook for all vl svc/*.pgc). + * + * Every per-service file (svc/.pgc) includes its own copybook header + * (svc/.h), which in turn includes THIS shared base. It pulls in the + * ATMI/UBF runtime, the acq.fd field ids, the vl dbio layer (libvldbio.a) and + * the common math (libacqcommon.a), and provides the tiny UBF helpers + tpreturn + * macros every service uses. No business logic and no service function lives here. + */ +#ifndef VL_SVC_H +#define VL_SVC_H + +#include +#include +#include +#include +#include +#include +#include +#include "acq.fd.h" +#include "vl_dbio.h" +#include "acq_common.h" + +/* ----- small UBF helpers (one copy per service TU) ----------------------- */ +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) + +#endif /* VL_SVC_H */ diff --git a/app/src/vl/vl_svr.pgc b/app/src/vl/vl_svr.pgc index a3f0266..5c9ae66 100644 --- a/app/src/vl/vl_svr.pgc +++ b/app/src/vl/vl_svr.pgc @@ -1,69 +1,85 @@ /* - * vl_svr.pgc - vl (정합성검증/validation) MODULE SERVER. + * vl_svr.pgc - vl (정합성검증/validation) MODULE SERVER (thin dispatcher). * - * 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. + * The legacy "one module server, many services" pattern: this binary owns NO + * business logic. Each of vl's ~30 online 검증 services now lives in its own file + * (app/src/vl/svc/.pgc) with its own copybook header + * (app/src/vl/svc/.h); each SELECTs the target rows, applies a 검증 + * branch, and returns an 에러코드 in T_RC. Objects are archived into libvlsvc.a + * and linked into this server. Here we only include every service copybook, + * tpopen() the ECPG XA RM, tpadvertise() all services, tpclose() at shutdown. * * 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 -#include -#include #include #include #include -#include -#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); } +/* every vl service's copybook header (declares its prototype) */ +#include "VL_SALES.h" +#include "VL_CARD.h" +#include "VL_LIMIT.h" +#include "VL_AMOUNT.h" +#include "VL_ANOMALY.h" +#include "VL_REQUIRED.h" +#include "VL_CODEVAL.h" +#include "VL_APPR_ACQ.h" +#include "VL_DUP.h" +#include "VL_DATE.h" +#include "VL_FEE.h" +#include "VL_SETTLE.h" +#include "VL_LEDGER.h" +#include "VL_FORMAT.h" +#include "VL_REFINTEG.h" +#include "VL_DUPAUTH.h" +#include "VL_BIN.h" +#include "VL_EXPIRY.h" +#include "VL_TOLERANCE.h" +#include "VL_MERCH.h" +#include "VL_STATUS_CHK.h" +#include "VL_BALANCE.h" +#include "VL_SUMMARY.h" +#include "VL_REPORT.h" +#include "VL_BATCH_CHK.h" +#include "VL_THRESHOLD.h" +#include "VL_RULE.h" +#include "VL_CROSSCHK.h" +#include "VL_STATUS.h" +#include "VL_CONFIRM.h" -#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 --------------------------------------------------- */ +/* ----- advertise table: {service name, function} (fn is extern from its file) */ 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}, + {"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} }; @@ -90,585 +106,4 @@ void tpsvrdone(void) 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 - 금액 유효성(0data; - 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: */