고도화: 전 모듈 dbio 25→40 · batch 17→22 세분화 (+432본, 전부 고유)

- 11개 모듈 각 dbio +15(→40), batch +5(→22) 새 고유 ECPG 프로그램 + 카피북 헤더
  (window/HAVING/JOIN/FILTER/NOT EXISTS/upsert/correlated/INSERT..SELECT 등 구조 다양)
- 전 코퍼스 .pgc 1004/1004 정규화-고유 (dup 그룹 0)
- 통합 검증: 12서버 runok, 333 서비스, 242 배치 바이너리, 매입체인 XA 커밋, prepared_xacts=0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
forge-bot 2026-07-19 14:37:10 +00:00
parent 05ad8a3064
commit fc16ea64c8
422 changed files with 8717 additions and 0 deletions

View file

@ -0,0 +1,46 @@
/*
* py_cancelpurge_batch.pgc - py 취소 지급 정리 (기간 DELETE 배치, XA).
*
* Deletes cancelled(C) payouts across a [from,to] date range in ONE XA
* transaction and reports how many rows were purged.
* Usage: py_cancelpurge_batch FROM_DATE TO_DATE
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "py_cancelpurge_batch.h"
int main(int argc, char **argv)
{
const char *from_date = (argc > 1) ? argv[1] : "2026-01-01";
const char *to_date = (argc > 2) ? argv[2] : "2026-06-30";
py_cancelpurge_rec_t rec;
EXEC SQL BEGIN DECLARE SECTION;
char h_from[16], h_to[16];
long h_purged;
EXEC SQL END DECLARE SECTION;
strncpy(h_from, from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0;
strncpy(h_to, to_date, sizeof(h_to)-1); h_to[sizeof(h_to)-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 py_payout
WHERE status = 'C' AND biz_date >= :h_from AND biz_date <= :h_to;
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_cancelpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
h_purged = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
strncpy(rec.from_date, from_date, sizeof(rec.from_date)-1); rec.from_date[sizeof(rec.from_date)-1] = 0;
strncpy(rec.to_date, to_date, sizeof(rec.to_date)-1); rec.to_date[sizeof(rec.to_date)-1] = 0;
rec.purged = h_purged;
printf(">>> py_cancelpurge_batch COMMIT: range=%s..%s purged=%ld\n", rec.from_date, rec.to_date, rec.purged);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* py_chanwalk_batch.pgc - py 채널별 집계 순회 (커서 배치, XA).
*
* Walks a channel-grouped cursor over the day's payouts, accumulating channel
* count and grand total, inside ONE XA transaction.
* Usage: py_chanwalk_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "py_chanwalk_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
py_chanwalk_rec_t rec;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ch[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;
rec.channels = 0; rec.grand_total = 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 cwcur CURSOR FOR
SELECT channel, count(*), coalesce(sum(amount),0)
FROM py_payout WHERE biz_date = :h_bizdate GROUP BY channel ORDER BY channel;
EXEC SQL OPEN cwcur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_chanwalk_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cwcur INTO :h_ch, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cwcur; fprintf(stderr, "py_chanwalk_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
rec.channels++; rec.grand_total += h_amt;
}
EXEC SQL CLOSE cwcur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0;
printf(">>> py_chanwalk_batch COMMIT: date=%s channels=%ld grand_total=%ld\n",
rec.biz_date, rec.channels, rec.grand_total);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,44 @@
/*
* py_ledgerpost_batch.pgc - py 지급완료 원장 전기 (INSERT..SELECT 배치, XA).
*
* Posts every paid(P) payout of the day into py_ledger as an OUT entry with a
* single INSERT..SELECT, in ONE XA transaction.
* Usage: py_ledgerpost_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "py_ledgerpost_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
py_ledgerpost_rec_t rec;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_posted;
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, 'OUT', net, biz_date
FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P';
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_ledgerpost_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
h_posted = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0;
rec.posted = h_posted;
printf(">>> py_ledgerpost_batch COMMIT: date=%s posted=%ld\n", rec.biz_date, rec.posted);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,49 @@
/*
* py_recon_batch.pgc - py 지급집계 대사 (집계 vs 실집계 2-쿼리 대조 배치, XA).
*
* Compares the stored py_summary amount against the live py_payout sum for one
* merchant/day and reports the gap, all inside ONE XA transaction.
* Usage: py_recon_batch MERCHANT [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "py_recon_batch.h"
int main(int argc, char **argv)
{
const char *merch = (argc > 1) ? argv[1] : "M0001";
const char *bizdate = (argc > 2) ? argv[2] : "2026-07-19";
py_recon_rec_t rec;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_bizdate[16];
long h_rec, h_live;
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;
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(payout_amount,0) INTO :h_rec FROM py_summary
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_recon_batch sum FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
if (sqlca.sqlcode == 100) h_rec = 0;
EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_live FROM py_payout
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_recon_batch live 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; }
strncpy(rec.merchant_id, merch, sizeof(rec.merchant_id)-1); rec.merchant_id[sizeof(rec.merchant_id)-1] = 0;
rec.recorded_amt = h_rec; rec.live_amt = h_live; rec.gap = h_live - h_rec;
printf(">>> py_recon_batch COMMIT: merch=%s date=%s recorded=%ld live=%ld gap=%ld\n",
rec.merchant_id, bizdate, rec.recorded_amt, rec.live_amt, rec.gap);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,43 @@
/*
* py_winrank_batch.pgc - py 누계 최고점 (윈도우 함수 배치, XA).
*
* Computes the peak per-merchant running total of net payouts for the day using
* a SUM() OVER window, inside ONE XA transaction.
* Usage: py_winrank_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "py_winrank_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
py_winrank_rec_t rec;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_peak;
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(run_tot),0) INTO :h_peak FROM (
SELECT sum(net) OVER (PARTITION BY merchant_id ORDER BY payout_id) AS run_tot
FROM py_payout WHERE biz_date = :h_bizdate) w;
if (sqlca.sqlcode < 0) { fprintf(stderr, "py_winrank_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; }
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0;
rec.peak_running = h_peak;
printf(">>> py_winrank_batch COMMIT: date=%s peak_running=%ld\n", rec.biz_date, rec.peak_running);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,11 @@
/*
* py_account_having_dbio.h - py DB copybook (py_account_having_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_HAVING_DBIO_H
#define PY_ACCOUNT_HAVING_DBIO_H
long pydb_account_low_balance_merchants(long floor_bal);
#endif /* PY_ACCOUNT_HAVING_DBIO_H */

View file

@ -0,0 +1,21 @@
/*
* py_account_having_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_account_having_dbio), archived into libpydbio.a.
* 가맹점 계좌 합산잔액이 기준 미만인 가맹점 수를 GROUP BY HAVING 으로 센다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_account_having_dbio.h"
/* GROUP BY HAVING: 합산잔액 < floor 인 가맹점 수. */
long pydb_account_low_balance_merchants(long floor_bal)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_floor = floor_bal, h_cnt = 0;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT count(*) INTO :h_cnt FROM (
SELECT merchant_id FROM py_account
GROUP BY merchant_id HAVING coalesce(sum(balance),0) < :h_floor) t;
if (sqlca.sqlcode < 0) { userlog("pydb_account_low_balance_merchants FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_cnt;
}

View file

@ -0,0 +1,14 @@
/*
* py_cancelpurge_batch.h - py copybook.
* / .
*/
#ifndef PY_CANCELPURGE_BATCH_H
#define PY_CANCELPURGE_BATCH_H
typedef struct {
char from_date[16];
char to_date[16];
long purged;
} py_cancelpurge_rec_t;
#endif /* PY_CANCELPURGE_BATCH_H */

View file

@ -0,0 +1,11 @@
/*
* py_channel_walk_dbio.h - py DB copybook (py_channel_walk_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_CHANNEL_WALK_DBIO_H
#define PY_CHANNEL_WALK_DBIO_H
long pydb_channel_active(const char *bizdate, long min_txn);
#endif /* PY_CHANNEL_WALK_DBIO_H */

View file

@ -0,0 +1,32 @@
/*
* py_channel_walk_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_channel_walk_dbio), archived into libpydbio.a.
* 채널별 집계를 커서로 순회하며 활성 채널(기준 건수 이상)을 센다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_channel_walk_dbio.h"
/* 커서 순회: 채널별 (건수, 금액) 을 훑어 min_txn 이상인 채널 수 반환. */
long pydb_channel_active(const char *bizdate, long min_txn)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ch[16];
long h_min = min_txn, h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
long active = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL DECLARE pych CURSOR FOR
SELECT channel, count(*), coalesce(sum(amount),0)
FROM py_payout WHERE biz_date = :h_bizdate GROUP BY channel ORDER BY channel;
EXEC SQL OPEN pych;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH pych INTO :h_ch, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pych; return -1; }
if (h_cnt >= h_min) active++;
}
EXEC SQL CLOSE pych;
return active;
}

View file

@ -0,0 +1,14 @@
/*
* py_chanwalk_batch.h - py copybook.
* (,) .
*/
#ifndef PY_CHANWALK_BATCH_H
#define PY_CHANWALK_BATCH_H
typedef struct {
char biz_date[16];
long channels;
long grand_total;
} py_chanwalk_rec_t;
#endif /* PY_CHANWALK_BATCH_H */

View file

@ -0,0 +1,11 @@
/*
* py_file_span_dbio.h - py DB copybook (py_file_span_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_SPAN_DBIO_H
#define PY_FILE_SPAN_DBIO_H
int pydb_file_record_span(const char *bizdate, long *min_out, long *max_out);
#endif /* PY_FILE_SPAN_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_file_span_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_file_span_dbio), archived into libpydbio.a.
* 당일 지급파일의 최소/최대 레코드건수를 한 번에 조회한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_file_span_dbio.h"
/* min/max: 당일 파일 레코드건수 범위를 out 파라미터로 반환. */
int pydb_file_record_span(const char *bizdate, long *min_out, long *max_out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_min = 0, 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(min(record_count),0), coalesce(max(record_count),0)
INTO :h_min, :h_max FROM py_file WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("pydb_file_record_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
*min_out = h_min; *max_out = h_max;
return 0;
}

View file

@ -0,0 +1,11 @@
/*
* py_ledger_mix_dbio.h - py DB copybook (py_ledger_mix_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_MIX_DBIO_H
#define PY_LEDGER_MIX_DBIO_H
int pydb_ledger_type_mix(const char *bizdate, long *kinds_out, long *out_amt_out);
#endif /* PY_LEDGER_MIX_DBIO_H */

View file

@ -0,0 +1,24 @@
/*
* py_ledger_mix_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_mix_dbio), archived into libpydbio.a.
* 지급원장 상세의 엔트리 종류 수와 OUT 금액을 count DISTINCT + FILTER 로 함께 구한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_ledger_mix_dbio.h"
/* count DISTINCT + FILTER: 엔트리 종류 수와 OUT 합계 동시 산출. */
int pydb_ledger_type_mix(const char *bizdate, long *kinds_out, long *out_amt_out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_kinds = 0, h_out = 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 entry_type),
coalesce(sum(amount) FILTER (WHERE entry_type = 'OUT'), 0)
INTO :h_kinds, :h_out FROM py_ledger WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("pydb_ledger_type_mix FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
*kinds_out = h_kinds; *out_amt_out = h_out;
return 0;
}

View file

@ -0,0 +1,11 @@
/*
* py_ledger_post_dbio.h - py DB copybook (py_ledger_post_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_POST_DBIO_H
#define PY_LEDGER_POST_DBIO_H
long pydb_ledger_post_paid(const char *bizdate);
#endif /* PY_LEDGER_POST_DBIO_H */

View file

@ -0,0 +1,22 @@
/*
* py_ledger_post_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_post_dbio), archived into libpydbio.a.
* 지급완료(P) 건을 지급원장 상세로 INSERT..SELECT 전기한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_ledger_post_dbio.h"
/* INSERT..SELECT: 지급완료 건을 OUT 엔트리로 원장상세에 적재, 적재 건수 반환. */
long pydb_ledger_post_paid(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 INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
SELECT nextval('py_ledger_seq'), payout_id, 'OUT', net, biz_date
FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P';
if (sqlca.sqlcode < 0) { userlog("pydb_ledger_post_paid FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return sqlca.sqlerrd[2];
}

View file

@ -0,0 +1,13 @@
/*
* py_ledgerpost_batch.h - py copybook.
* INSERT..SELECT .
*/
#ifndef PY_LEDGERPOST_BATCH_H
#define PY_LEDGERPOST_BATCH_H
typedef struct {
char biz_date[16];
long posted;
} py_ledgerpost_rec_t;
#endif /* PY_LEDGERPOST_BATCH_H */

View file

@ -0,0 +1,11 @@
/*
* py_payout_corr_dbio.h - py DB copybook (py_payout_corr_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_CORR_DBIO_H
#define PY_PAYOUT_CORR_DBIO_H
long pydb_payout_above_own_avg(const char *bizdate);
#endif /* PY_PAYOUT_CORR_DBIO_H */

View file

@ -0,0 +1,24 @@
/*
* py_payout_corr_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_corr_dbio), archived into libpydbio.a.
* 같은 가맹점 평균 금액을 상회하는 지급건 수를 상관 서브쿼리로 센다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_corr_dbio.h"
/* 상관 서브쿼리: 자기 가맹점 평균금액 초과 지급건 수. */
long pydb_payout_above_own_avg(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 py_payout p1
WHERE p1.biz_date = :h_bizdate
AND p1.amount > (SELECT avg(p2.amount) FROM py_payout p2
WHERE p2.merchant_id = p1.merchant_id);
if (sqlca.sqlcode < 0) { userlog("pydb_payout_above_own_avg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_cnt;
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_delrange_dbio.h - py DB copybook (py_payout_delrange_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_DELRANGE_DBIO_H
#define PY_PAYOUT_DELRANGE_DBIO_H
long pydb_payout_purge_cancelled(const char *from_date, const char *to_date);
#endif /* PY_PAYOUT_DELRANGE_DBIO_H */

View file

@ -0,0 +1,22 @@
/*
* py_payout_delrange_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_delrange_dbio), archived into libpydbio.a.
* 기간 범위의 취소(C) 지급건을 정리 삭제한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_delrange_dbio.h"
/* DELETE 범위: [from,to] 기간의 취소 지급건 삭제, 삭제 건수 반환. */
long pydb_payout_purge_cancelled(const char *from_date, const char *to_date)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_from[16], h_to[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_from, from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0;
strncpy(h_to, to_date, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0;
EXEC SQL DELETE FROM py_payout
WHERE status = 'C' AND biz_date >= :h_from AND biz_date <= :h_to;
if (sqlca.sqlcode < 0) { userlog("pydb_payout_purge_cancelled FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return sqlca.sqlerrd[2];
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_notexists_dbio.h - py DB copybook (py_payout_notexists_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_NOTEXISTS_DBIO_H
#define PY_PAYOUT_NOTEXISTS_DBIO_H
long pydb_payout_unscheduled(const char *bizdate);
#endif /* PY_PAYOUT_NOTEXISTS_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_payout_notexists_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_notexists_dbio), archived into libpydbio.a.
* 예약 테이블에 대응 행이 없는 지급건(미예약)을 NOT EXISTS 로 센다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_notexists_dbio.h"
/* NOT EXISTS: 예약행이 없는 당일 지급건 수. */
long pydb_payout_unscheduled(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 py_payout pp
WHERE pp.biz_date = :h_bizdate
AND NOT EXISTS (SELECT 1 FROM py_schedule ps WHERE ps.payout_id = pp.payout_id);
if (sqlca.sqlcode < 0) { userlog("pydb_payout_unscheduled FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_cnt;
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_rejrate_dbio.h - py DB copybook (py_payout_rejrate_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_REJRATE_DBIO_H
#define PY_PAYOUT_REJRATE_DBIO_H
long pydb_payout_reject_rate(const char *bizdate);
#endif /* PY_PAYOUT_REJRATE_DBIO_H */

View file

@ -0,0 +1,24 @@
/*
* py_payout_rejrate_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_rejrate_dbio), archived into libpydbio.a.
* 반려(J) 지급건 비율(퍼센트)을 CASE 분기 나눗셈으로 산출한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_rejrate_dbio.h"
/* 비율(CASE): 반려 건수 비중(%) = count(J)*100 / count(*). */
long pydb_payout_reject_rate(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_rate = 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 count(*) > 0
THEN count(*) FILTER (WHERE status = 'J') * 100 / count(*)
ELSE 0 END
INTO :h_rate FROM py_payout WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("pydb_payout_reject_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_rate;
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_retryhist_dbio.h - py DB copybook (py_payout_retryhist_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_RETRYHIST_DBIO_H
#define PY_PAYOUT_RETRYHIST_DBIO_H
long pydb_payout_retry_buckets(const char *bizdate, long min_share);
#endif /* PY_PAYOUT_RETRYHIST_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_payout_retryhist_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_retryhist_dbio), archived into libpydbio.a.
* 재시도 횟수별 분포에서 일정 건수 이상 나타난 버킷 수를 구한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_retryhist_dbio.h"
/* GROUP BY retry_count HAVING: 건수 >= min_share 인 재시도 버킷 수. */
long pydb_payout_retry_buckets(const char *bizdate, long min_share)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_min = min_share, 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 (
SELECT retry_count FROM py_payout WHERE biz_date = :h_bizdate
GROUP BY retry_count HAVING count(*) >= :h_min) b;
if (sqlca.sqlcode < 0) { userlog("pydb_payout_retry_buckets FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_cnt;
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_weighted_dbio.h - py DB copybook (py_payout_weighted_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_WEIGHTED_DBIO_H
#define PY_PAYOUT_WEIGHTED_DBIO_H
long pydb_payout_weighted_bps(const char *bizdate);
#endif /* PY_PAYOUT_WEIGHTED_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_payout_weighted_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_weighted_dbio), archived into libpydbio.a.
* 금액 가중 수수료율(bps) = sum(fee)*10000 / sum(amount).
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_weighted_dbio.h"
/* 가중평균 수수료율(bps): 총수수료를 총금액으로 나눈 값. */
long pydb_payout_weighted_bps(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_bps = 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 coalesce(sum(amount),0) > 0
THEN sum(fee) * 10000 / sum(amount) ELSE 0 END
INTO :h_bps FROM py_payout WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("pydb_payout_weighted_bps FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_bps;
}

View file

@ -0,0 +1,11 @@
/*
* py_payout_window_dbio.h - py DB copybook (py_payout_window_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_WINDOW_DBIO_H
#define PY_PAYOUT_WINDOW_DBIO_H
long pydb_payout_peak_running(const char *bizdate);
#endif /* PY_PAYOUT_WINDOW_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_payout_window_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_window_dbio), archived into libpydbio.a.
* 가맹점별 지급 순액을 윈도우 누계로 계산해 최대 누계선을 구한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_payout_window_dbio.h"
/* 윈도우 함수: 가맹점별 payout_id 순 누계(net) 중 최대값. */
long pydb_payout_peak_running(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_peak = 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(run_tot),0) INTO :h_peak FROM (
SELECT sum(net) OVER (PARTITION BY merchant_id ORDER BY payout_id) AS run_tot
FROM py_payout WHERE biz_date = :h_bizdate) w;
if (sqlca.sqlcode < 0) { userlog("pydb_payout_peak_running FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_peak;
}

View file

@ -0,0 +1,15 @@
/*
* py_recon_batch.h - py copybook ( ).
* vs .
*/
#ifndef PY_RECON_BATCH_H
#define PY_RECON_BATCH_H
typedef struct {
char merchant_id[64];
long recorded_amt;
long live_amt;
long gap;
} py_recon_rec_t;
#endif /* PY_RECON_BATCH_H */

View file

@ -0,0 +1,11 @@
/*
* py_schedule_due_dbio.h - py DB copybook (py_schedule_due_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_DUE_DBIO_H
#define PY_SCHEDULE_DUE_DBIO_H
long pydb_schedule_due_net(const char *rundate);
#endif /* PY_SCHEDULE_DUE_DBIO_H */

View file

@ -0,0 +1,23 @@
/*
* py_schedule_due_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_schedule_due_dbio), archived into libpydbio.a.
* 실행일에 예약(S)되고 승인(A)된 지급건의 순액을 예약-지급 조인으로 합산한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_schedule_due_dbio.h"
/* JOIN: 예약(S) x 승인(A) 지급건의 실행 대상 순액 합. */
long pydb_schedule_due_net(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(pp.net),0) INTO :h_v
FROM py_schedule ps JOIN py_payout pp ON ps.payout_id = pp.payout_id
WHERE ps.run_date = :h_rundate AND ps.status = 'S' AND pp.status = 'A';
if (sqlca.sqlcode < 0) { userlog("pydb_schedule_due_net FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_v;
}

View file

@ -0,0 +1,11 @@
/*
* py_summary_accum_dbio.h - py DB copybook (py_summary_accum_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_ACCUM_DBIO_H
#define PY_SUMMARY_ACCUM_DBIO_H
int pydb_summary_accumulate(const char *merch, const char *bizdate, long cnt, long amount);
#endif /* PY_SUMMARY_ACCUM_DBIO_H */

View file

@ -0,0 +1,27 @@
/*
* py_summary_accum_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_accum_dbio), archived into libpydbio.a.
* 가맹점 일별 집계에 건수/금액을 누적 가산하는 upsert.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_summary_accum_dbio.h"
/* ON CONFLICT 누적 upsert: 기존 집계에 delta 를 더한다. */
int pydb_summary_accumulate(const char *merch, const char *bizdate, long cnt, long amount)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_bizdate[16];
long h_cnt = cnt, h_amt = amount;
EXEC SQL END DECLARE SECTION;
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL INSERT INTO py_summary (merchant_id, biz_date, payout_count, payout_amount)
VALUES (:h_merch, :h_bizdate, :h_cnt, :h_amt)
ON CONFLICT (merchant_id, biz_date) DO UPDATE
SET payout_count = py_summary.payout_count + EXCLUDED.payout_count,
payout_amount = py_summary.payout_amount + EXCLUDED.payout_amount,
updated_at = now();
if (sqlca.sqlcode < 0) { userlog("pydb_summary_accumulate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return 0;
}

View file

@ -0,0 +1,12 @@
/*
* py_summary_gap_dbio.h - py DB copybook (py_summary_gap_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_GAP_DBIO_H
#define PY_SUMMARY_GAP_DBIO_H
int pydb_summary_gap(const char *merch, const char *bizdate,
long *recorded, long *live, long *gap);
#endif /* PY_SUMMARY_GAP_DBIO_H */

View file

@ -0,0 +1,29 @@
/*
* py_summary_gap_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_gap_dbio), archived into libpydbio.a.
* 집계 테이블에 적재된 금액과 원장 실집계 금액의 차이를 대조한다.
* ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "py_summary_gap_dbio.h"
/* 집계 vs 실집계 대조: 적재금액, 실집계금액, 차액을 반환. */
int pydb_summary_gap(const char *merch, const char *bizdate,
long *recorded, long *live, long *gap)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_bizdate[16];
long h_rec = 0, h_live = 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(payout_amount,0) INTO :h_rec FROM py_summary
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0 && sqlca.sqlcode != 100) return -1;
if (sqlca.sqlcode == 100) h_rec = 0;
EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_live FROM py_payout
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("pydb_summary_gap FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
*recorded = h_rec; *live = h_live; *gap = h_live - h_rec;
return 0;
}

View file

@ -0,0 +1,13 @@
/*
* py_winrank_batch.h - py copybook.
* .
*/
#ifndef PY_WINRANK_BATCH_H
#define PY_WINRANK_BATCH_H
typedef struct {
char biz_date[16];
long peak_running;
} py_winrank_rec_t;
#endif /* PY_WINRANK_BATCH_H */