고도화: 전 모듈 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,42 @@
/*
* st_adjnet_batch.pgc - st 가맹점 조정 순액 백필 (INSERT..SELECT upsert + XA).
*
* st_adjust 를 가맹점 기준이 아닌 정산건 기준으로 집계하기 어려우므로, 지급(st_payment)
* 가맹점별 조정 총액을 st_merch_settle 순액에 반영하는 INSERT..SELECT upsert 를 수행.
* 사용법: st_adjnet_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "st_adjnet_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
st_adjnet_rec_t rec; rec.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 st_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount)
SELECT merchant_id, biz_date, count(*), coalesce(sum(net_amount), 0), 0, coalesce(sum(net_amount), 0)
FROM st_payment WHERE biz_date = :h_bizdate GROUP BY merchant_id, biz_date
ON CONFLICT (merchant_id, biz_date) DO UPDATE
SET net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount, updated_at = now();
if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT..SELECT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.rows = (long) sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> %s COMMIT: bizdate=%s upserted=%ld\n", ST_ADJNET_TAG, bizdate, rec.rows);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,41 @@
/*
* st_paysweep_batch.pgc - st 소액 예정지급 일괄 보류 (집합 UPDATE + XA).
*
* 최소액 미만 SCHEDULED 지급을 하나의 집합 UPDATE 로 HELD 전환. 사용법:
* st_paysweep_batch [YYYY-MM-DD] [floor]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "st_paysweep_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
st_paysweep_rec_t rec;
rec.floor_amt = (argc > 2) ? atol(argv[2]) : 10000;
rec.held = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_floor = rec.floor_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 UPDATE st_payment SET status = 'HELD'
WHERE biz_date = :h_bizdate AND status = 'SCHEDULED' AND net_amount < :h_floor;
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.held = (long) sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> %s COMMIT: bizdate=%s floor=%ld held=%ld\n", ST_PAYSWEEP_TAG, bizdate, rec.floor_amt, rec.held);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,40 @@
/*
* st_purgeadj_batch.pgc - st 무효 조정 범위 정리 (BETWEEN DELETE + XA).
*
* 두 영업일 사이의 delta=0 조정을 하나의 범위 DELETE 로 제거하고 2PC 커밋.
* 사용법: st_purgeadj_batch [FROM] [TO]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "st_purgeadj_batch.h"
int main(int argc, char **argv)
{
st_purgeadj_rec_t rec;
strncpy(rec.from_date, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.from_date)-1); rec.from_date[sizeof(rec.from_date)-1] = 0;
strncpy(rec.to_date, (argc > 2) ? argv[2] : "2026-07-15", sizeof(rec.to_date)-1); rec.to_date[sizeof(rec.to_date)-1] = 0;
rec.removed = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_from[16], h_to[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_from, rec.from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0;
strncpy(h_to, rec.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 st_adjust WHERE biz_date BETWEEN :h_from AND :h_to AND delta = 0;
if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.removed = (long) sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> %s COMMIT: from=%s to=%s removed=%ld\n", ST_PURGEADJ_TAG, rec.from_date, rec.to_date, rec.removed);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,57 @@
/*
* st_rankpay_batch.pgc - st 가맹점별 최상위 지급 -> 정산상세 적재 (윈도우 커서 + XA).
*
* row_number() OVER (PARTITION BY merchant_id ORDER BY net_amount DESC) 커서로 가맹점별
* 1순위 지급을 골라 st_settle_dtl 에 RATE 라인으로 기록. 사용법: st_rankpay_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "st_rankpay_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
st_rankpay_rec_t rec;
long loaded = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_mid[32];
long h_net, h_rn;
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 rpc CURSOR FOR
SELECT merchant_id, net_amount,
row_number() OVER (PARTITION BY merchant_id ORDER BY net_amount DESC)
FROM st_payment WHERE biz_date = :h_bizdate;
EXEC SQL OPEN rpc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH rpc INTO :h_mid, :h_net, :h_rn;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rpc; tpabort(0); return 1; }
strncpy(rec.merchant, h_mid, sizeof(rec.merchant)-1); rec.merchant[sizeof(rec.merchant)-1] = 0;
rec.net = h_net; rec.rnk = h_rn;
if (rec.rnk == 1) {
EXEC SQL INSERT INTO st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date)
VALUES (nextval('st_dtl_seq'), 0, 0, :h_mid, 'RATE', :h_net, :h_bizdate);
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rpc; tpabort(0); return 1; }
loaded++;
}
}
EXEC SQL CLOSE rpc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> %s COMMIT: bizdate=%s loaded=%ld\n", ST_RANKPAY_TAG, bizdate, loaded);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,54 @@
/*
* st_rateapply_batch.pgc - st 구간요율 적용 산출 (조인 커서 + XA).
*
* st_settle_dtl 을 st_fee_rate 와 구간(band_min) 조인 커서로 순회하며 요율 적용액을
* 누계한다. 하나의 글로벌 트랜잭션에서 조회 후 2PC 커밋. 사용법: st_rateapply_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "st_rateapply_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
st_rateapply_rec_t rec;
long total = 0, lines = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ft[16];
long h_amt, h_bps;
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 rac CURSOR FOR
SELECT d.fee_type, d.amount, r.rate_bps
FROM st_settle_dtl d
JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min
WHERE d.biz_date = :h_bizdate ORDER BY d.dtl_id;
EXEC SQL OPEN rac;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH rac INTO :h_ft, :h_amt, :h_bps;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rac; tpabort(0); return 1; }
strncpy(rec.fee_type, h_ft, sizeof(rec.fee_type)-1); rec.fee_type[sizeof(rec.fee_type)-1] = 0;
rec.amt = h_amt; rec.bps = h_bps;
total += rec.amt * rec.bps / 10000;
lines++;
}
EXEC SQL CLOSE rac;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> %s COMMIT: bizdate=%s lines=%ld fee=%ld\n", ST_RATEAPPLY_TAG, bizdate, lines, total);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,5 @@
/* st_adjcorr_dbio.h - 정산건별 조정합 상관 서브쿼리 copybook (libstdbio.a). */
#ifndef ST_ADJCORR_DBIO_H
#define ST_ADJCORR_DBIO_H
long stdb_settlements_net_positive(const char *bizdate);
#endif

View file

@ -0,0 +1,30 @@
/*
* st_adjcorr_dbio.pgc - 조정합이 양수인 정산건 (st_adjcorr_dbio), libstdbio.a.
* ECPG; 상관 서브쿼리로 각 정산건의 delta 합을 구해 양수인 distinct 정산건 수를 센다.
*/
#include <string.h>
#include <userlog.h>
#include "st_adjcorr_dbio.h"
long stdb_settlements_net_positive(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_sid, h_delta, positive = 0;
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL DECLARE acc2 CURSOR FOR
SELECT DISTINCT a.settlement_id,
(SELECT coalesce(sum(a2.delta), 0) FROM st_adjust a2 WHERE a2.settlement_id = a.settlement_id)
FROM st_adjust a WHERE a.biz_date = :h_bizdate;
EXEC SQL OPEN acc2;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH acc2 INTO :h_sid, :h_delta;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE acc2; return -1; }
if (h_delta > 0) positive++;
}
EXEC SQL CLOSE acc2;
return positive;
}

View file

@ -0,0 +1,6 @@
/* st_adjnet_batch.h - st 조정 순액 집계 배치 copybook. */
#ifndef ST_ADJNET_BATCH_H
#define ST_ADJNET_BATCH_H
#define ST_ADJNET_TAG "st_adjnet_batch"
typedef struct { long rows; } st_adjnet_rec_t;
#endif

View file

@ -0,0 +1,5 @@
/* st_adjpurge_dbio.h - 0-델타 조정 범위 삭제 copybook (libstdbio.a). */
#ifndef ST_ADJPURGE_DBIO_H
#define ST_ADJPURGE_DBIO_H
long stdb_purge_zero_adj(const char *from_date, const char *to_date);
#endif

View file

@ -0,0 +1,19 @@
/*
* st_adjpurge_dbio.pgc - 영향 없는 조정 정리 (st_adjpurge_dbio), libstdbio.a.
* ECPG; DELETE .. WHERE biz_date BETWEEN a AND b AND delta=0 범위 삭제. 반환=삭제 행수.
*/
#include <string.h>
#include <userlog.h>
#include "st_adjpurge_dbio.h"
long stdb_purge_zero_adj(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 st_adjust WHERE biz_date BETWEEN :h_from AND :h_to AND delta = 0;
if (sqlca.sqlcode < 0) { userlog("stdb_purge_zero_adj FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return (long) sqlca.sqlerrd[2];
}

View file

@ -0,0 +1,5 @@
/* st_dtlinsert_dbio.h - 지급 -> 정산상세 INSERT..SELECT copybook (libstdbio.a). */
#ifndef ST_DTLINSERT_DBIO_H
#define ST_DTLINSERT_DBIO_H
long stdb_seed_cap_lines(const char *bizdate);
#endif

View file

@ -0,0 +1,20 @@
/*
* st_dtlinsert_dbio.pgc - 지급액으로부터 CAP 라인 생성 (st_dtlinsert_dbio), libstdbio.a.
* ECPG; INSERT .. SELECT 로 st_payment 를 CAP 수수료 라인으로 st_settle_dtl 에 적재. 반환=행수.
*/
#include <string.h>
#include <userlog.h>
#include "st_dtlinsert_dbio.h"
long stdb_seed_cap_lines(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 st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date)
SELECT nextval('st_dtl_seq'), 0, 0, merchant_id, 'CAP', net_amount / 100, biz_date
FROM st_payment WHERE biz_date = :h_bizdate AND status = 'PAID';
if (sqlca.sqlcode < 0) { userlog("stdb_seed_cap_lines FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return (long) sqlca.sqlerrd[2];
}

View file

@ -0,0 +1,5 @@
/* st_feecase_dbio.h - 수수료 종류별 CASE 합계 copybook (libstdbio.a). */
#ifndef ST_FEECASE_DBIO_H
#define ST_FEECASE_DBIO_H
long stdb_mdr_vs_van(const char *bizdate, long *van_out);
#endif

View file

@ -0,0 +1,22 @@
/*
* st_feecase_dbio.pgc - MDR/VAN 수수료 분리 합계 (st_feecase_dbio), libstdbio.a.
* ECPG; sum(CASE WHEN fee_type=... THEN amount ELSE 0 END) 형태로 한 SELECT 에서 두 합계.
*/
#include <string.h>
#include <userlog.h>
#include "st_feecase_dbio.h"
long stdb_mdr_vs_van(const char *bizdate, long *van_out)
{
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(CASE WHEN fee_type = 'MDR' THEN amount ELSE 0 END), 0),
coalesce(sum(CASE WHEN fee_type = 'VAN' THEN amount ELSE 0 END), 0)
INTO :h_mdr, :h_van FROM st_settle_dtl WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("stdb_mdr_vs_van FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
if (van_out) *van_out = h_van;
return h_mdr;
}

View file

@ -0,0 +1,5 @@
/* st_feehaving_dbio.h - 고수수료 가맹점 HAVING copybook (libstdbio.a). */
#ifndef ST_FEEHAVING_DBIO_H
#define ST_FEEHAVING_DBIO_H
long stdb_high_fee_merchants(const char *bizdate, long amt_thr, long cnt_thr);
#endif

View file

@ -0,0 +1,30 @@
/*
* st_feehaving_dbio.pgc - 고수수료 다건 가맹점 (st_feehaving_dbio), libstdbio.a.
* ECPG; GROUP BY merchant_id HAVING sum(amount) > a AND count(*) > c 커서로 대상 가맹점 수.
*/
#include <string.h>
#include <userlog.h>
#include "st_feehaving_dbio.h"
long stdb_high_fee_merchants(const char *bizdate, long amt_thr, long cnt_thr)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_mid[32];
long h_amt = amt_thr, h_cnt = cnt_thr, h_sum, h_n, 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 fhc CURSOR FOR
SELECT merchant_id, sum(amount), count(*) FROM st_settle_dtl
WHERE biz_date = :h_bizdate GROUP BY merchant_id
HAVING sum(amount) > :h_amt AND count(*) > :h_cnt;
EXEC SQL OPEN fhc;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH fhc INTO :h_mid, :h_sum, :h_n;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE fhc; return -1; }
merchants++;
}
EXEC SQL CLOSE fhc;
return merchants;
}

View file

@ -0,0 +1,5 @@
/* st_feeweight_dbio.h - 요율 가중 평균 (JOIN) copybook (libstdbio.a). */
#ifndef ST_FEEWEIGHT_DBIO_H
#define ST_FEEWEIGHT_DBIO_H
long stdb_weighted_rate(const char *bizdate);
#endif

View file

@ -0,0 +1,22 @@
/*
* st_feeweight_dbio.pgc - 금액 가중 평균 요율 (st_feeweight_dbio), libstdbio.a.
* ECPG; st_settle_dtl 를 st_fee_rate 와 조인해 sum(amount*rate_bps)/nullif(sum(amount),0) 가중평균.
*/
#include <string.h>
#include <userlog.h>
#include "st_feeweight_dbio.h"
long stdb_weighted_rate(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
double h_w = 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(d.amount * r.rate_bps)::float8 / nullif(sum(d.amount), 0), 0)
INTO :h_w FROM st_settle_dtl d
JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min
WHERE d.biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("stdb_weighted_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return (long) h_w;
}

View file

@ -0,0 +1,5 @@
/* st_feewindow_dbio.h - 가맹점별 수수료 순위 copybook (libstdbio.a). */
#ifndef ST_FEEWINDOW_DBIO_H
#define ST_FEEWINDOW_DBIO_H
long stdb_top3_fee_lines(const char *bizdate);
#endif

View file

@ -0,0 +1,31 @@
/*
* st_feewindow_dbio.pgc - 가맹점별 수수료 상위 라인 (st_feewindow_dbio), libstdbio.a.
* ECPG; 호출자 XA 브랜치에서 수행, EXEC SQL CONNECT 없음.
* dense_rank() OVER (PARTITION BY merchant_id ORDER BY amount DESC) 로 가맹점별 상위 3 라인 수.
*/
#include <string.h>
#include <userlog.h>
#include "st_feewindow_dbio.h"
long stdb_top3_fee_lines(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_mid[32];
long h_dr, h_amt, tops = 0;
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL DECLARE fwc CURSOR FOR
SELECT merchant_id, amount,
dense_rank() OVER (PARTITION BY merchant_id ORDER BY amount DESC)
FROM st_settle_dtl WHERE biz_date = :h_bizdate;
EXEC SQL OPEN fwc;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH fwc INTO :h_mid, :h_amt, :h_dr;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE fwc; return -1; }
if (h_dr <= 3) tops++;
}
EXEC SQL CLOSE fwc;
return tops;
}

View file

@ -0,0 +1,5 @@
/* st_merchdistinct_dbio.h - 고유 가맹점/수수료종류 수 copybook (libstdbio.a). */
#ifndef ST_MERCHDISTINCT_DBIO_H
#define ST_MERCHDISTINCT_DBIO_H
long stdb_distinct_merch(const char *bizdate, long *feetypes_out);
#endif

View file

@ -0,0 +1,21 @@
/*
* st_merchdistinct_dbio.pgc - 고유 가맹점/수수료종류 수 (st_merchdistinct_dbio), libstdbio.a.
* ECPG; count(DISTINCT merchant_id) 와 count(DISTINCT fee_type) 를 한 SELECT 로 반환.
*/
#include <string.h>
#include <userlog.h>
#include "st_merchdistinct_dbio.h"
long stdb_distinct_merch(const char *bizdate, long *feetypes_out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_m = 0, h_ft = 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), count(DISTINCT fee_type)
INTO :h_m, :h_ft FROM st_settle_dtl WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("stdb_distinct_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
if (feetypes_out) *feetypes_out = h_ft;
return h_m;
}

View file

@ -0,0 +1,5 @@
/* st_merchupsert_dbio.h - 가맹점 정산 집계 upsert copybook (libstdbio.a). */
#ifndef ST_MERCHUPSERT_DBIO_H
#define ST_MERCHUPSERT_DBIO_H
int stdb_bump_merch(const char *merchant, const char *bizdate, long gross, long fee, long net);
#endif

View file

@ -0,0 +1,27 @@
/*
* st_merchupsert_dbio.pgc - 가맹점 정산 집계 증분 upsert (st_merchupsert_dbio), libstdbio.a.
* ECPG; 복합키 (merchant_id, biz_date) ON CONFLICT DO UPDATE 로 건수/총액/수수료/순액을 누적.
*/
#include <string.h>
#include <userlog.h>
#include "st_merchupsert_dbio.h"
int stdb_bump_merch(const char *merchant, const char *bizdate, long gross, long fee, long net)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_mid[32], h_bizdate[16];
long h_g = gross, h_f = fee, h_n = net;
EXEC SQL END DECLARE SECTION;
strncpy(h_mid, merchant, sizeof(h_mid)-1); h_mid[sizeof(h_mid)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL INSERT INTO st_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount)
VALUES (:h_mid, :h_bizdate, 1, :h_g, :h_f, :h_n)
ON CONFLICT (merchant_id, biz_date) DO UPDATE
SET txn_count = st_merch_settle.txn_count + 1,
gross_amount = st_merch_settle.gross_amount + EXCLUDED.gross_amount,
fee_amount = st_merch_settle.fee_amount + EXCLUDED.fee_amount,
net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount,
updated_at = now();
if (sqlca.sqlcode < 0) { userlog("stdb_bump_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return 0;
}

View file

@ -0,0 +1,5 @@
/* st_netminmax_dbio.h - 지급 순액 최소/최대/합 copybook (libstdbio.a). */
#ifndef ST_NETMINMAX_DBIO_H
#define ST_NETMINMAX_DBIO_H
long stdb_net_span(const char *bizdate, const char *status, long *min_out, long *max_out);
#endif

View file

@ -0,0 +1,24 @@
/*
* st_netminmax_dbio.pgc - 상태별 순액 스팬 (st_netminmax_dbio), libstdbio.a.
* ECPG; 특정 status 지급의 min/max/sum(net_amount) 을 한 번에 구해 합계를 반환.
*/
#include <string.h>
#include <userlog.h>
#include "st_netminmax_dbio.h"
long stdb_net_span(const char *bizdate, const char *status, long *min_out, long *max_out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_status[16];
long h_min = 0, h_max = 0, h_sum = 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(min(net_amount),0), coalesce(max(net_amount),0), coalesce(sum(net_amount),0)
INTO :h_min, :h_max, :h_sum FROM st_payment
WHERE biz_date = :h_bizdate AND status = :h_status;
if (sqlca.sqlcode < 0) { userlog("stdb_net_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
if (min_out) *min_out = h_min;
if (max_out) *max_out = h_max;
return h_sum;
}

View file

@ -0,0 +1,5 @@
/* st_paycursor2_dbio.h - 예정 지급 보류 전환 (positioned UPDATE) copybook (libstdbio.a). */
#ifndef ST_PAYCURSOR2_DBIO_H
#define ST_PAYCURSOR2_DBIO_H
long stdb_hold_small_payments(const char *bizdate, long floor_amt);
#endif

View file

@ -0,0 +1,33 @@
/*
* st_paycursor2_dbio.pgc - 소액 예정지급 보류 전환 (st_paycursor2_dbio), libstdbio.a.
* ECPG; FOR UPDATE 커서로 SCHEDULED 지급을 순회하며 최소액 미만이면 WHERE CURRENT OF 로 HELD 전환.
*/
#include <string.h>
#include <userlog.h>
#include "st_paycursor2_dbio.h"
long stdb_hold_small_payments(const char *bizdate, long floor_amt)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_floor = floor_amt, h_id, h_net, held = 0;
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL DECLARE p2c CURSOR FOR
SELECT pay_id, net_amount FROM st_payment
WHERE biz_date = :h_bizdate AND status = 'SCHEDULED' FOR UPDATE;
EXEC SQL OPEN p2c;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH p2c INTO :h_id, :h_net;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE p2c; return -1; }
if (h_net < h_floor) {
EXEC SQL UPDATE st_payment SET status = 'HELD' WHERE CURRENT OF p2c;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE p2c; return -1; }
held++;
}
}
EXEC SQL CLOSE p2c;
return held;
}

View file

@ -0,0 +1,5 @@
/* st_payinsub_dbio.h - 활성 가맹점 지급합 (IN subselect) copybook (libstdbio.a). */
#ifndef ST_PAYINSUB_DBIO_H
#define ST_PAYINSUB_DBIO_H
long stdb_pay_for_active_merch(const char *bizdate, long min_txn);
#endif

View file

@ -0,0 +1,22 @@
/*
* st_payinsub_dbio.pgc - 활성 가맹점 지급 합계 (st_payinsub_dbio), libstdbio.a.
* ECPG; merchant_id IN (SELECT ... FROM st_merch_settle WHERE txn_count > n) IN 서브셀렉트로 지급합.
*/
#include <string.h>
#include <userlog.h>
#include "st_payinsub_dbio.h"
long stdb_pay_for_active_merch(const char *bizdate, long min_txn)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_min = min_txn, 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_amount), 0) INTO :h_v FROM st_payment
WHERE biz_date = :h_bizdate
AND merchant_id IN (SELECT merchant_id FROM st_merch_settle
WHERE biz_date = :h_bizdate AND txn_count > :h_min);
if (sqlca.sqlcode < 0) { userlog("stdb_pay_for_active_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_v;
}

View file

@ -0,0 +1,5 @@
/* st_paynotexists_dbio.h - 지급 누락 가맹점 (NOT EXISTS) copybook (libstdbio.a). */
#ifndef ST_PAYNOTEXISTS_DBIO_H
#define ST_PAYNOTEXISTS_DBIO_H
long stdb_merch_without_payment(const char *bizdate);
#endif

View file

@ -0,0 +1,22 @@
/*
* st_paynotexists_dbio.pgc - 지급이 없는 정산 가맹점 (st_paynotexists_dbio), libstdbio.a.
* ECPG; merchant_id+biz_date 2컬럼 상관 NOT EXISTS 로 st_payment 가 없는 st_merch_settle 를 센다.
*/
#include <string.h>
#include <userlog.h>
#include "st_paynotexists_dbio.h"
long stdb_merch_without_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 st_merch_settle ms
WHERE ms.biz_date = :h_bizdate
AND NOT EXISTS (SELECT 1 FROM st_payment p
WHERE p.merchant_id = ms.merchant_id AND p.biz_date = ms.biz_date);
if (sqlca.sqlcode < 0) { userlog("stdb_merch_without_payment FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return h_v;
}

View file

@ -0,0 +1,6 @@
/* st_paysweep_batch.h - st 예정지급 보류 스윕 배치 copybook. */
#ifndef ST_PAYSWEEP_BATCH_H
#define ST_PAYSWEEP_BATCH_H
#define ST_PAYSWEEP_TAG "st_paysweep_batch"
typedef struct { long floor_amt; long held; } st_paysweep_rec_t;
#endif

View file

@ -0,0 +1,6 @@
/* st_purgeadj_batch.h - st 조정 범위 삭제 배치 copybook. */
#ifndef ST_PURGEADJ_BATCH_H
#define ST_PURGEADJ_BATCH_H
#define ST_PURGEADJ_TAG "st_purgeadj_batch"
typedef struct { char from_date[16]; char to_date[16]; long removed; } st_purgeadj_rec_t;
#endif

View file

@ -0,0 +1,6 @@
/* st_rankpay_batch.h - st 가맹점 상위지급 적재 배치 copybook. */
#ifndef ST_RANKPAY_BATCH_H
#define ST_RANKPAY_BATCH_H
#define ST_RANKPAY_TAG "st_rankpay_batch"
typedef struct { char merchant[32]; long net; long rnk; } st_rankpay_rec_t;
#endif

View file

@ -0,0 +1,6 @@
/* st_rateapply_batch.h - st 구간요율 적용 배치 copybook. */
#ifndef ST_RATEAPPLY_BATCH_H
#define ST_RATEAPPLY_BATCH_H
#define ST_RATEAPPLY_TAG "st_rateapply_batch"
typedef struct { char fee_type[16]; long amt; long bps; } st_rateapply_rec_t;
#endif

View file

@ -0,0 +1,5 @@
/* st_ratejoin_dbio.h - 상세x요율 조인 커서 copybook (libstdbio.a). */
#ifndef ST_RATEJOIN_DBIO_H
#define ST_RATEJOIN_DBIO_H
long stdb_apply_band_rate(const char *bizdate);
#endif

View file

@ -0,0 +1,32 @@
/*
* st_ratejoin_dbio.pgc - 구간 요율 적용 (st_ratejoin_dbio), libstdbio.a.
* ECPG; st_settle_dtl 를 st_fee_rate 와 fee_type + band_min 조건으로 조인, 커서로 요율 적용액 합.
*/
#include <string.h>
#include <userlog.h>
#include "st_ratejoin_dbio.h"
long stdb_apply_band_rate(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ft[16];
long h_amt, h_bps, 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 rjc CURSOR FOR
SELECT d.fee_type, d.amount, r.rate_bps
FROM st_settle_dtl d
JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min
WHERE d.biz_date = :h_bizdate
ORDER BY d.dtl_id;
EXEC SQL OPEN rjc;
if (sqlca.sqlcode < 0) return -1;
for (;;) {
EXEC SQL FETCH rjc INTO :h_ft, :h_amt, :h_bps;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rjc; return -1; }
total += h_amt * h_bps / 10000;
}
EXEC SQL CLOSE rjc;
return total;
}

View file

@ -0,0 +1,5 @@
/* st_statusfilter_dbio.h - 지급 상태별 FILTER 집계 copybook (libstdbio.a). */
#ifndef ST_STATUSFILTER_DBIO_H
#define ST_STATUSFILTER_DBIO_H
long stdb_pay_status_mix(const char *bizdate, long *paid_out, long *cancelled_out);
#endif

View file

@ -0,0 +1,24 @@
/*
* st_statusfilter_dbio.pgc - 지급 상태 구성 (st_statusfilter_dbio), libstdbio.a.
* ECPG; count(*) FILTER (WHERE status=...) 3종으로 HELD/PAID/CANCELLED 를 동시에 집계.
*/
#include <string.h>
#include <userlog.h>
#include "st_statusfilter_dbio.h"
long stdb_pay_status_mix(const char *bizdate, long *paid_out, long *cancelled_out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_held = 0, h_paid = 0, h_cancel = 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 = 'HELD'),
count(*) FILTER (WHERE status = 'PAID'),
count(*) FILTER (WHERE status = 'CANCELLED')
INTO :h_held, :h_paid, :h_cancel FROM st_payment WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("stdb_pay_status_mix FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
if (paid_out) *paid_out = h_paid;
if (cancelled_out) *cancelled_out = h_cancel;
return h_held;
}