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

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

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

View file

@ -50,6 +50,51 @@
#include "ACQ_RELEASE.h"
#include "ACQ_VOID.h"
#include "ACQ_REFUND.h"
#include "ACQ_CB_OPEN.h"
#include "ACQ_CB_QRY.h"
#include "ACQ_CB_DEFEND.h"
#include "ACQ_CB_STAT.h"
#include "ACQ_REBUY.h"
#include "ACQ_PARTCXL.h"
#include "ACQ_PARTRFND.h"
#include "ACQ_SPLITPAY.h"
#include "ACQ_SPLITQRY.h"
#include "ACQ_CCYSUM.h"
#include "ACQ_FXRATE.h"
#include "ACQ_CTRYSUM.h"
#include "ACQ_FEERECALC.h"
#include "ACQ_FEEWAIVE.h"
#include "ACQ_FEEHIST.h"
#include "ACQ_CLAIMREG.h"
#include "ACQ_CLAIMPROC.h"
#include "ACQ_CLAIMQRY.h"
#include "ACQ_CORRHIST.h"
#include "ACQ_TAXREISSUE.h"
#include "ACQ_TAXCXL.h"
#include "ACQ_RETURN.h"
#include "ACQ_REMATCH.h"
#include "ACQ_BULKVERIFY.h"
#include "ACQ_LIMITUSAGE.h"
#include "ACQ_RESERVE.h"
#include "ACQ_RESERVECXL.h"
#include "ACQ_RESERVEQRY.h"
#include "ACQ_PROMO.h"
#include "ACQ_NOINTSHARE.h"
#include "ACQ_DAYSTAT.h"
#include "ACQ_WEEKSTAT.h"
#include "ACQ_MONSTAT.h"
#include "ACQ_CHANSTAT.h"
#include "ACQ_ISSSTAT.h"
#include "ACQ_ALERTCHK.h"
#include "ACQ_EDIRESEND.h"
#include "ACQ_SETTLEDUE.h"
#include "ACQ_HOLDQRY.h"
#include "ACQ_DUPSCAN.h"
#include "ACQ_VELOCITY.h"
#include "ACQ_TOPMERCH.h"
#include "ACQ_NETCALC.h"
#include "ACQ_APPRGAP.h"
#include "ACQ_FXCONVERT.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
@ -68,6 +113,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"ACQ_APPRMAP", ACQ_APPRMAP}, {"ACQ_SETTLELINK", ACQ_SETTLELINK},
{"ACQ_HOLD", ACQ_HOLD}, {"ACQ_RELEASE", ACQ_RELEASE},
{"ACQ_VOID", ACQ_VOID}, {"ACQ_REFUND", ACQ_REFUND},
{"ACQ_CB_OPEN", ACQ_CB_OPEN}, {"ACQ_CB_QRY", ACQ_CB_QRY},
{"ACQ_CB_DEFEND", ACQ_CB_DEFEND},{"ACQ_CB_STAT", ACQ_CB_STAT},
{"ACQ_REBUY", ACQ_REBUY}, {"ACQ_PARTCXL", ACQ_PARTCXL},
{"ACQ_PARTRFND", ACQ_PARTRFND}, {"ACQ_SPLITPAY", ACQ_SPLITPAY},
{"ACQ_SPLITQRY", ACQ_SPLITQRY}, {"ACQ_CCYSUM", ACQ_CCYSUM},
{"ACQ_FXRATE", ACQ_FXRATE}, {"ACQ_CTRYSUM", ACQ_CTRYSUM},
{"ACQ_FEERECALC", ACQ_FEERECALC},{"ACQ_FEEWAIVE", ACQ_FEEWAIVE},
{"ACQ_FEEHIST", ACQ_FEEHIST}, {"ACQ_CLAIMREG", ACQ_CLAIMREG},
{"ACQ_CLAIMPROC", ACQ_CLAIMPROC},{"ACQ_CLAIMQRY", ACQ_CLAIMQRY},
{"ACQ_CORRHIST", ACQ_CORRHIST}, {"ACQ_TAXREISSUE", ACQ_TAXREISSUE},
{"ACQ_TAXCXL", ACQ_TAXCXL}, {"ACQ_RETURN", ACQ_RETURN},
{"ACQ_REMATCH", ACQ_REMATCH}, {"ACQ_BULKVERIFY", ACQ_BULKVERIFY},
{"ACQ_LIMITUSAGE", ACQ_LIMITUSAGE},{"ACQ_RESERVE", ACQ_RESERVE},
{"ACQ_RESERVECXL", ACQ_RESERVECXL},{"ACQ_RESERVEQRY", ACQ_RESERVEQRY},
{"ACQ_PROMO", ACQ_PROMO}, {"ACQ_NOINTSHARE", ACQ_NOINTSHARE},
{"ACQ_DAYSTAT", ACQ_DAYSTAT}, {"ACQ_WEEKSTAT", ACQ_WEEKSTAT},
{"ACQ_MONSTAT", ACQ_MONSTAT}, {"ACQ_CHANSTAT", ACQ_CHANSTAT},
{"ACQ_ISSSTAT", ACQ_ISSSTAT}, {"ACQ_ALERTCHK", ACQ_ALERTCHK},
{"ACQ_EDIRESEND", ACQ_EDIRESEND},{"ACQ_SETTLEDUE", ACQ_SETTLEDUE},
{"ACQ_HOLDQRY", ACQ_HOLDQRY}, {"ACQ_DUPSCAN", ACQ_DUPSCAN},
{"ACQ_VELOCITY", ACQ_VELOCITY}, {"ACQ_TOPMERCH", ACQ_TOPMERCH},
{"ACQ_NETCALC", ACQ_NETCALC}, {"ACQ_APPRGAP", ACQ_APPRGAP},
{"ACQ_FXCONVERT", ACQ_FXCONVERT},
{NULL, NULL}
};

View file

@ -0,0 +1,57 @@
/*
* ac_alert_batch.pgc - ac 고액매입 경보 스캔 배치 (임계 커서, XA).
*
* ATMI client: 인자로 받은 임계금액(기본 500만원) 이상의 영업일 매입을
* 개별 라인으로 뿜어내고, 경보 건수·최고액을 요약한다. 임계는 두 번째
* 인자로 조정 가능해 운영 중 재기동 없이 스캔 수위를 바꾼다.
* Usage: ac_alert_batch [YYYY-MM-DD] [임계금액]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_alert_batch.h"
int main(int argc, char **argv)
{
ac_alert_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_thr, h_pid, h_amt;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
cx.threshold = (argc > 2) ? atol(argv[2]) : 5000000L;
if (cx.threshold <= 0) cx.threshold = 5000000L;
h_thr = cx.threshold;
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 alb_cur CURSOR FOR
SELECT purchase_id, merchant_id, amount FROM purchase
WHERE biz_date = :h_bizdate AND amount >= :h_thr AND status NOT IN ('C', 'V')
ORDER BY amount DESC;
EXEC SQL OPEN alb_cur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH alb_cur INTO :h_pid, :h_merch, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE alb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
cx.alerts++;
if (h_amt > cx.peak) cx.peak = h_amt;
printf(" ALERT pid=%ld merchant=%s amount=%ld\n", h_pid, h_merch, h_amt);
}
EXEC SQL CLOSE alb_cur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_alert_batch COMMIT: bizdate=%s thr=%ld alerts=%ld peak=%ld\n",
bizdate, cx.threshold, cx.alerts, cx.peak);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,62 @@
/*
* ac_apprgap_batch.pgc - ac 승인-매입 갭 양방향 대사 배치 (2단 점검, XA).
*
* ATMI client: 승인 없는 매입 금액과 매입 없는 고아 승인 건수를 양방향으로
* 재고, 거절(approved=false) 승인이 달린 매입 수까지 3개 지표를 산출한다.
* 갭이 있으면 종료코드 2.
* Usage: ac_apprgap_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_apprgap_batch.h"
int main(int argc, char **argv)
{
ac_apprgap_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_noap = 0, h_noamt = 0, h_orphan = 0, h_denied = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* (1) 승인 없는 매입: 건수 + 금액 */
EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0)
INTO :h_noap, :h_noamt
FROM purchase u
WHERE u.biz_date = :h_bizdate AND u.status NOT IN ('C', 'V', 'P')
AND NOT EXISTS (SELECT 1 FROM approval a
WHERE a.purchase_id = u.purchase_id AND a.approved = true);
if (sqlca.sqlcode < 0) { fprintf(stderr, "noappr FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
/* (2) 매입 없는 고아 승인 */
EXEC SQL SELECT count(*) INTO :h_orphan
FROM approval a LEFT JOIN purchase u ON u.purchase_id = a.purchase_id
WHERE u.purchase_id IS NULL;
if (sqlca.sqlcode < 0) { fprintf(stderr, "orphan FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
/* (3) 거절 승인이 달린 영업일 매입 */
EXEC SQL SELECT count(DISTINCT u.purchase_id) INTO :h_denied
FROM purchase u JOIN approval a ON a.purchase_id = u.purchase_id
WHERE u.biz_date = :h_bizdate AND a.approved = false;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_apprgap_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.no_approval = h_noap; cx.no_appr_amt = h_noamt;
cx.orphan_appr = h_orphan; cx.denied = h_denied;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_apprgap_batch COMMIT: bizdate=%s no_appr=%ld(%ld) orphan=%ld denied=%ld\n",
bizdate, cx.no_approval, cx.no_appr_amt, cx.orphan_appr, cx.denied);
tpclose(); tpterm();
return (cx.no_approval + cx.orphan_appr) > 0 ? 2 : 0;
}

View file

@ -0,0 +1,59 @@
/*
* ac_cbaging_batch.pgc - ac 지불거절 경과일 aging 배치 (버킷 집계, XA).
*
* ATMI client: 한 글로벌 트랜잭션 안에서 CB 접수 건을 등록 경과일 기준
* 3개 버킷(7일 미만/7~30일/30일 초과)으로 나눠 세고, 방어되지 않은 장기
* 미제 건수를 함께 보고한다. tpcommit 이 XA 2PC 를 마무리한다.
* Usage: ac_cbaging_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_cbaging_batch.h"
/* 경과일을 aging 버킷 인덱스로 변환 */
int acb_cb_bucket(long age_days)
{
if (age_days < 7) return 0;
if (age_days <= 30) return 1;
return 2;
}
int main(int argc, char **argv)
{
ac_cbaging_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_fresh = 0, h_mid = 0, h_stale = 0, h_open = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
strncpy(cx.base_date, bizdate, sizeof(cx.base_date)-1);
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 CAST(:h_bizdate AS date) - biz_date < 7),
count(*) FILTER (WHERE CAST(:h_bizdate AS date) - biz_date BETWEEN 7 AND 30),
count(*) FILTER (WHERE CAST(:h_bizdate AS date) - biz_date > 30),
count(*) FILTER (WHERE reason NOT LIKE 'DEFEND:%')
INTO :h_fresh, :h_mid, :h_stale, :h_open
FROM ac_cancel WHERE cancel_type = 'CB';
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_cbaging_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.fresh = h_fresh; cx.mid = h_mid; cx.stale = h_stale; cx.undefended = h_open;
(void)acb_cb_bucket(31);
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_cbaging_batch COMMIT: base=%s fresh=%ld mid=%ld stale=%ld undefended=%ld\n",
cx.base_date, cx.fresh, cx.mid, cx.stale, cx.undefended);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,53 @@
/*
* ac_ccysum_batch.pgc - ac 통화별 해외매입 집계 배치 (통화 커서, XA).
*
* ATMI client: 영업일 해외매입 원장을 통화별로 그룹 집계해 통화마다
* 건수/원통화합계/원화환산합계/평균환율을 한 줄씩 출력한다.
* Usage: ac_ccysum_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_ccysum_batch.h"
int main(int argc, char **argv)
{
ac_ccysum_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ccy[8];
long h_n, h_fx, h_krw, h_rate;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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 ccb_cur CURSOR FOR
SELECT ccy, count(*), sum(fx_amount), sum(krw_amount), avg(rate_bps)::bigint
FROM ac_fx WHERE biz_date = :h_bizdate
GROUP BY ccy ORDER BY sum(krw_amount) DESC;
EXEC SQL OPEN ccb_cur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH ccb_cur INTO :h_ccy, :h_n, :h_fx, :h_krw, :h_rate;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
printf(" ccy=%-4s n=%ld fx=%ld krw=%ld avg_rate=%ld\n", h_ccy, h_n, h_fx, h_krw, h_rate);
cx.ccy_kinds++;
cx.krw_total += h_krw;
}
EXEC SQL CLOSE ccb_cur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_ccysum_batch COMMIT: bizdate=%s ccy_kinds=%ld krw_total=%ld\n",
bizdate, cx.ccy_kinds, cx.krw_total);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* ac_claimsweep_batch.pgc - ac 장기 미제 클레임 정리 배치 (DELETE, XA).
*
* ATMI client: 기준일로부터 30일이 지난 미종결 클레임 행을 미매입 원장에서
* 일괄 삭제(자동 종결)한다. 삭제 전 대상 건수를 세어 대조 출력한다.
* Usage: ac_claimsweep_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_claimsweep_batch.h"
int main(int argc, char **argv)
{
ac_claimsweep_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_target = 0, h_deleted;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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_target FROM ac_unmatch
WHERE reason LIKE 'CLAIM:%' AND biz_date < CAST(:h_bizdate AS date) - 30;
if (sqlca.sqlcode < 0) { fprintf(stderr, "count FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
EXEC SQL DELETE FROM ac_unmatch
WHERE reason LIKE 'CLAIM:%' AND biz_date < CAST(:h_bizdate AS date) - 30;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_claimsweep_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
h_deleted = sqlca.sqlerrd[2];
cx.target = h_target; cx.deleted = h_deleted;
if (cx.target != cx.deleted) {
fprintf(stderr, "ac_claimsweep_batch MISMATCH target=%ld deleted=%ld\n", cx.target, cx.deleted);
tpabort(0); return 1;
}
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_claimsweep_batch COMMIT: base=%s swept=%ld\n", bizdate, cx.deleted);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,48 @@
/*
* ac_feewaive_batch.pgc - ac 수수료 감면 총액 일 집계 배치 (사유별 분해, XA).
*
* ATMI client: 영업일의 음수 수수료 조정(감면)을 감면(WAIVE)/무이자 분담
* (NOINT)/기타 사유로 분해 집계한다. 감면 총액은 부호를 뒤집어 보고한다.
* Usage: ac_feewaive_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_feewaive_batch.h"
int main(int argc, char **argv)
{
ac_feewaive_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_waive = 0, h_noint = 0, h_etc = 0, h_rows = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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) FILTER (WHERE reason = 'WAIVE'), 0),
coalesce(-sum(delta) FILTER (WHERE reason = 'NOINT'), 0),
coalesce(-sum(delta) FILTER (WHERE reason NOT IN ('WAIVE', 'NOINT')), 0),
count(*)
INTO :h_waive, :h_noint, :h_etc, :h_rows
FROM ac_fee_adj WHERE biz_date = :h_bizdate AND delta < 0;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_feewaive_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.waive_sum = h_waive; cx.noint_sum = h_noint; cx.etc_sum = h_etc; cx.rows = h_rows;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_feewaive_batch COMMIT: bizdate=%s rows=%ld waive=%ld noint=%ld etc=%ld\n",
bizdate, cx.rows, cx.waive_sum, cx.noint_sum, cx.etc_sum);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,68 @@
/*
* ac_limitwatch_batch.pgc - ac 가맹점 한도 임박 감시 배치 (조인 커서, XA).
*
* ATMI client: 영업일 사용액을 가맹점 일한도와 조인 집계해 소진율 90%
* (9000bps)를 넘긴 가맹점을 골라 경고 라인을 출력한다. C 헬퍼가 소진율을
* bps 로 환산한다.
* Usage: ac_limitwatch_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_limitwatch_batch.h"
/* 소진율 bps 계산 (한도 0 이면 만점 처리) */
long acb_usage_bps(long used, long limit)
{
if (limit <= 0) return 10000L;
return (used * 10000L) / limit;
}
int main(int argc, char **argv)
{
ac_limitwatch_ctx_t cx;
long bps;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_used, h_limit;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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 lwb_cur CURSOR FOR
SELECT m.merchant_id, coalesce(sum(u.amount), 0), m.daily_limit
FROM merchant m JOIN purchase u
ON u.merchant_id = m.merchant_id AND u.biz_date = :h_bizdate
AND u.status NOT IN ('C', 'V')
GROUP BY m.merchant_id, m.daily_limit;
EXEC SQL OPEN lwb_cur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH lwb_cur INTO :h_merch, :h_used, :h_limit;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lwb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
cx.scanned++;
bps = acb_usage_bps(h_used, h_limit);
if (bps > 9000L) {
cx.warned++;
printf(" ** 한도 임박 merchant=%s used=%ld limit=%ld usage=%ld bps\n",
h_merch, h_used, h_limit, bps);
}
}
EXEC SQL CLOSE lwb_cur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_limitwatch_batch COMMIT: bizdate=%s scanned=%ld warned=%ld\n",
bizdate, cx.scanned, cx.warned);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,54 @@
/*
* ac_promo_batch.pgc - ac 프로모션 매입 집계 배치 (분류 집계 + 원장 대조, XA).
*
* ATMI client: 영업일 프로모션 분류(PROMO) 매입의 건수/총액과 면제 수수료
* 추정액(가맹점 MDR 기준)을 집계하고, PROMO 원장 분개 합계와 대조한다.
* Usage: ac_promo_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_promo_batch.h"
int main(int argc, char **argv)
{
ac_promo_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_n = 0, h_amt = 0, h_freefee = 0, h_ledger = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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; }
/* 프로모션 매입 + 면제 수수료 추정(MDR 재적용) */
EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0),
coalesce(sum(u.amount * m.mdr_bps / 10000), 0)
INTO :h_n, :h_amt, :h_freefee
FROM purchase u JOIN merchant m ON m.merchant_id = u.merchant_id
WHERE u.biz_date = :h_bizdate AND u.category = 'PROMO' AND u.status <> 'C';
if (sqlca.sqlcode < 0) { fprintf(stderr, "promo agg FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
/* PROMO 원장 분개 합계 대조 */
EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_ledger
FROM ledger WHERE entry_type = 'PROMO' AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_promo_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.promo_count = h_n; cx.promo_amount = h_amt;
cx.waived_fee = h_freefee; cx.ledger_sum = h_ledger;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_promo_batch COMMIT: bizdate=%s n=%ld amt=%ld waived_fee=%ld ledger=%ld %s\n",
bizdate, cx.promo_count, cx.promo_amount, cx.waived_fee, cx.ledger_sum,
cx.promo_amount == cx.ledger_sum ? "(대조 일치)" : "(대조 불일치)");
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,48 @@
/*
* ac_reservepost_batch.pgc - ac 매입 예약 전환 배치 (상태 전이, XA).
*
* ATMI client: 예약일이 도래한 예약(P) 매입을 접수(A)로 일괄 전환하고,
* 전환 후에도 남은 미래 예약 건수를 세어 함께 보고한다.
* Usage: ac_reservepost_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_reservepost_batch.h"
int main(int argc, char **argv)
{
ac_reservepost_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_left = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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; }
/* 예약일 도래 건 전환: P → A */
EXEC SQL UPDATE purchase SET status = 'A', updated_at = now()
WHERE status = 'P' AND biz_date <= CAST(:h_bizdate AS date);
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_reservepost_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.posted = sqlca.sqlerrd[2];
/* 미래 예약 잔량 */
EXEC SQL SELECT count(*) INTO :h_left FROM purchase WHERE status = 'P';
if (sqlca.sqlcode < 0) { fprintf(stderr, "left FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
cx.remaining = h_left;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_reservepost_batch COMMIT: base=%s posted=%ld remaining=%ld\n",
bizdate, cx.posted, cx.remaining);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,56 @@
/*
* ac_settledue_batch.pgc - ac 정산예정 대기열 집계 배치 (NOT EXISTS, XA).
*
* ATMI client: 대사완료(M) 상태이나 정산행이 아직 없는 매입을 가맹점별로
* 묶어 정산예정액을 산출한다. 가맹점별 라인 출력 후 전체 합계로 마감.
* Usage: ac_settledue_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_settledue_batch.h"
int main(int argc, char **argv)
{
ac_settledue_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_n, h_net;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE sdb_cur CURSOR FOR
SELECT u.merchant_id, count(*), sum(u.net)
FROM purchase u
WHERE u.biz_date = :h_bizdate AND u.status = 'M'
AND NOT EXISTS (SELECT 1 FROM settlement s WHERE s.purchase_id = u.purchase_id)
GROUP BY u.merchant_id ORDER BY u.merchant_id;
EXEC SQL OPEN sdb_cur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH sdb_cur INTO :h_merch, :h_n, :h_net;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE sdb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
printf(" merchant=%s due_n=%ld due_net=%ld\n", h_merch, h_n, h_net);
cx.merchants++;
cx.due_count += h_n;
cx.due_net += h_net;
}
EXEC SQL CLOSE sdb_cur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_settledue_batch COMMIT: bizdate=%s merchants=%ld due_n=%ld due_net=%ld\n",
bizdate, cx.merchants, cx.due_count, cx.due_net);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,58 @@
/*
* ac_splitpay_batch.pgc - ac 분할납부 스케줄 정합 배치 (그룹 커서, XA).
*
* ATMI client: 영업일에 등록된 분할납부 스케줄을 매입 단위로 그룹 커서
* 순회하며 스케줄 합계와 매입 원금의 불일치 건을 찾아낸다. 불일치가
* 하나라도 있으면 종료코드 2 로 마감 라인에 신호를 준다.
* Usage: ac_splitpay_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_splitpay_batch.h"
int main(int argc, char **argv)
{
ac_splitpay_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_pid, h_sched, h_orig;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE spb_cur CURSOR FOR
SELECT i.purchase_id, sum(i.month_amount), max(u.amount)
FROM ac_install i JOIN purchase u ON u.purchase_id = i.purchase_id
WHERE i.biz_date = :h_bizdate
GROUP BY i.purchase_id;
EXEC SQL OPEN spb_cur;
if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH spb_cur INTO :h_pid, :h_sched, :h_orig;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE spb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
cx.plans++;
cx.sched_total += h_sched;
if (h_sched != h_orig) {
cx.mismatch++;
printf(" !! 스케줄 불일치 pid=%ld sched=%ld orig=%ld\n", h_pid, h_sched, h_orig);
}
}
EXEC SQL CLOSE spb_cur;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_splitpay_batch COMMIT: bizdate=%s plans=%ld sched_total=%ld mismatch=%ld\n",
bizdate, cx.plans, cx.sched_total, cx.mismatch);
tpclose(); tpterm();
return cx.mismatch > 0 ? 2 : 0;
}

View file

@ -0,0 +1,53 @@
/*
* ac_taxneg_batch.pgc - ac 세금계산서 상쇄 검증 배치 (부호 검증, XA).
*
* ATMI client: 영업일의 음수(취소) 세금계산서를 검사한다. 음수 계산서의
* 부가세가 공급가의 10% 상쇄와 어긋난 건, 그리고 매입별 계산서 잔액이
* 음수로 뒤집힌(과상쇄) 매입 수를 찾아 보고한다.
* Usage: ac_taxneg_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_taxneg_batch.h"
int main(int argc, char **argv)
{
ac_taxneg_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_neg = 0, h_badvat = 0, h_over = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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 vat <> supply / 10)
INTO :h_neg, :h_badvat
FROM ac_tax_invoice WHERE biz_date = :h_bizdate AND supply < 0;
if (sqlca.sqlcode < 0) { fprintf(stderr, "neg scan FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
/* 과상쇄: 매입별 공급가 잔액이 음수인 매입 수 */
EXEC SQL SELECT count(*) INTO :h_over
FROM (SELECT purchase_id FROM ac_tax_invoice
GROUP BY purchase_id HAVING sum(supply) < 0) o;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_taxneg_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.neg_rows = h_neg; cx.bad_vat = h_badvat; cx.over_offset = h_over;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_taxneg_batch COMMIT: bizdate=%s neg=%ld bad_vat=%ld over_offset=%ld\n",
bizdate, cx.neg_rows, cx.bad_vat, cx.over_offset);
tpclose(); tpterm();
return (cx.bad_vat + cx.over_offset) > 0 ? 2 : 0;
}

View file

@ -0,0 +1,61 @@
/*
* ac_weekroll_batch.pgc - ac 주간 가맹점 집계 롤업 배치 (INSERT..SELECT, XA).
*
* ATMI client: 기준일 포함 직전 7일 매입을 가맹점별로 롤업해 기준일 키의
* merchant_summary 로 밀어넣는다(ON CONFLICT 갱신). 반영 행수와 주간
* 총액을 되읽어 보고한다.
* Usage: ac_weekroll_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_weekroll_batch.h"
int main(int argc, char **argv)
{
ac_weekroll_ctx_t cx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_gross = 0;
EXEC SQL END DECLARE SECTION;
memset(&cx, 0, sizeof(cx));
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(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* 주간 롤업: 직전 7일 → 기준일 키 upsert */
EXEC SQL INSERT INTO merchant_summary (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount, updated_at)
SELECT merchant_id, CAST(:h_bizdate AS date), count(*), sum(amount), sum(fee), sum(net), now()
FROM purchase
WHERE biz_date > CAST(:h_bizdate AS date) - 7
AND biz_date <= CAST(:h_bizdate AS date)
AND status NOT IN ('C', 'V', 'P')
GROUP BY merchant_id
ON CONFLICT (merchant_id, biz_date) DO UPDATE
SET txn_count = EXCLUDED.txn_count,
gross_amount = EXCLUDED.gross_amount,
fee_amount = EXCLUDED.fee_amount,
net_amount = EXCLUDED.net_amount,
updated_at = now();
if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_weekroll_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
cx.rolled = sqlca.sqlerrd[2];
/* 반영 결과 되읽기 */
EXEC SQL SELECT coalesce(sum(gross_amount), 0) INTO :h_gross
FROM merchant_summary WHERE biz_date = CAST(:h_bizdate AS date);
if (sqlca.sqlcode < 0) { fprintf(stderr, "readback FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
cx.week_gross = h_gross;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_weekroll_batch COMMIT: base=%s rolled=%ld week_gross=%ld\n",
bizdate, cx.rolled, cx.week_gross);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,14 @@
/*
* ac_alert_batch.h - copybook for ac_alert_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_ALERT_BATCH_H
#define AC_ALERT_BATCH_H
typedef struct {
long threshold; /* 고액 임계금액 */
long alerts; /* 경보 건수 */
long peak; /* 최고 금액 */
} ac_alert_ctx_t;
#endif /* AC_ALERT_BATCH_H */

View file

@ -0,0 +1,15 @@
/*
* ac_apprgap_batch.h - copybook for ac_apprgap_batch (XA client).
* - . Kept in dbio/ for -I resolution.
*/
#ifndef AC_APPRGAP_BATCH_H
#define AC_APPRGAP_BATCH_H
typedef struct {
long no_approval; /* 승인 없는 매입 건수 */
long no_appr_amt; /* 그 금액 */
long orphan_appr; /* 매입 없는 승인 */
long denied; /* 거절 승인 달린 매입 */
} ac_apprgap_ctx_t;
#endif /* AC_APPRGAP_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* ac_cbaging_batch.h - copybook for ac_cbaging_batch (XA client).
* aging + . Kept in dbio/ for -I resolution.
*/
#ifndef AC_CBAGING_BATCH_H
#define AC_CBAGING_BATCH_H
typedef struct {
char base_date[16];
long fresh; /* 경과 7일 미만 */
long mid; /* 7~30일 */
long stale; /* 30일 초과 */
long undefended; /* 방어 미착수 건수 */
} ac_cbaging_ctx_t;
int acb_cb_bucket(long age_days); /* 0=fresh, 1=mid, 2=stale */
#endif /* AC_CBAGING_BATCH_H */

View file

@ -0,0 +1,13 @@
/*
* ac_ccysum_batch.h - copybook for ac_ccysum_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_CCYSUM_BATCH_H
#define AC_CCYSUM_BATCH_H
typedef struct {
long ccy_kinds; /* 통화 종수 */
long krw_total; /* 원화 환산 총액 */
} ac_ccysum_ctx_t;
#endif /* AC_CCYSUM_BATCH_H */

View file

@ -0,0 +1,13 @@
/*
* ac_claimsweep_batch.h - copybook for ac_claimsweep_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_CLAIMSWEEP_BATCH_H
#define AC_CLAIMSWEEP_BATCH_H
typedef struct {
long target; /* 삭제 대상 사전 계수 */
long deleted; /* 실제 삭제 행 수 */
} ac_claimsweep_ctx_t;
#endif /* AC_CLAIMSWEEP_BATCH_H */

View file

@ -0,0 +1,15 @@
/*
* ac_feewaive_batch.h - copybook for ac_feewaive_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_FEEWAIVE_BATCH_H
#define AC_FEEWAIVE_BATCH_H
typedef struct {
long rows; /* 감면(음수 delta) 행 수 */
long waive_sum; /* WAIVE 감면 총액(양수 표기) */
long noint_sum; /* 무이자 분담 총액 */
long etc_sum; /* 기타 사유 총액 */
} ac_feewaive_ctx_t;
#endif /* AC_FEEWAIVE_BATCH_H */

View file

@ -0,0 +1,15 @@
/*
* ac_limitwatch_batch.h - copybook for ac_limitwatch_batch (XA client).
* + . Kept in dbio/ for -I resolution.
*/
#ifndef AC_LIMITWATCH_BATCH_H
#define AC_LIMITWATCH_BATCH_H
typedef struct {
long scanned; /* 검사 가맹점 수 */
long warned; /* 90% 초과 경고 수 */
} ac_limitwatch_ctx_t;
long acb_usage_bps(long used, long limit);
#endif /* AC_LIMITWATCH_BATCH_H */

View file

@ -0,0 +1,15 @@
/*
* ac_promo_batch.h - copybook for ac_promo_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_PROMO_BATCH_H
#define AC_PROMO_BATCH_H
typedef struct {
long promo_count;
long promo_amount;
long waived_fee; /* MDR 기준 면제 수수료 추정액 */
long ledger_sum; /* PROMO 원장 분개 합계 */
} ac_promo_ctx_t;
#endif /* AC_PROMO_BATCH_H */

View file

@ -0,0 +1,13 @@
/*
* ac_reservepost_batch.h - copybook for ac_reservepost_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_RESERVEPOST_BATCH_H
#define AC_RESERVEPOST_BATCH_H
typedef struct {
long posted; /* P→A 전환 건수 */
long remaining; /* 미래 예약 잔량 */
} ac_reservepost_ctx_t;
#endif /* AC_RESERVEPOST_BATCH_H */

View file

@ -0,0 +1,14 @@
/*
* ac_settledue_batch.h - copybook for ac_settledue_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_SETTLEDUE_BATCH_H
#define AC_SETTLEDUE_BATCH_H
typedef struct {
long merchants; /* 정산 대기 가맹점 수 */
long due_count; /* 정산예정 건수 */
long due_net; /* 정산예정 총액 */
} ac_settledue_ctx_t;
#endif /* AC_SETTLEDUE_BATCH_H */

View file

@ -0,0 +1,14 @@
/*
* ac_splitpay_batch.h - copybook for ac_splitpay_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_SPLITPAY_BATCH_H
#define AC_SPLITPAY_BATCH_H
typedef struct {
long plans; /* 검사한 분할 플랜 수 */
long sched_total; /* 스케줄 금액 합계 */
long mismatch; /* 원금 불일치 플랜 수 */
} ac_splitpay_ctx_t;
#endif /* AC_SPLITPAY_BATCH_H */

View file

@ -0,0 +1,14 @@
/*
* ac_taxneg_batch.h - copybook for ac_taxneg_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_TAXNEG_BATCH_H
#define AC_TAXNEG_BATCH_H
typedef struct {
long neg_rows; /* 음수 계산서 건수 */
long bad_vat; /* 부가세 상쇄식 위반 */
long over_offset; /* 과상쇄 매입 수 */
} ac_taxneg_ctx_t;
#endif /* AC_TAXNEG_BATCH_H */

View file

@ -0,0 +1,13 @@
/*
* ac_weekroll_batch.h - copybook for ac_weekroll_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AC_WEEKROLL_BATCH_H
#define AC_WEEKROLL_BATCH_H
typedef struct {
long rolled; /* upsert 반영 행수 */
long week_gross; /* 주간 총액 되읽기 */
} ac_weekroll_ctx_t;
#endif /* AC_WEEKROLL_BATCH_H */

View file

@ -0,0 +1,23 @@
/*
* ACQ_ALERTCHK.h - ac ACQ_ALERTCHK (copybook / record header).
* .
* Request fields: T_MERCHANT, T_BIZDATE, T_AMT1(; 0= 100)
* Response fields: T_RC(1=), T_MSG, T_ARG1( ), T_ARG2( )
*/
#ifndef ACQ_ALERTCHK_H
#define ACQ_ALERTCHK_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long threshold; /* 고액 임계금액 */
long big_count;
long hold_count;
char message[160];
} ac_alertchk_rec_t;
void ACQ_ALERTCHK(TPSVCINFO *p);
#endif /* ACQ_ALERTCHK_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_ALERTCHK.pgc - ac 모듈 서비스 ACQ_ALERTCHK (one service = one file).
* 매입 알림 점검: 가맹점의 당일 고액거래(임계금액 이상)와 보류 적체 건수를
* 점검해 경보 여부와 알림 메시지를 만들어 돌려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_ALERTCHK.h"
void ACQ_ALERTCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long threshold;
char msg[160];
EXEC SQL BEGIN DECLARE SECTION;
long h_thr, h_big = 0, h_hold = 0;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
threshold = getl(b, T_AMT1);
if (threshold <= 0) threshold = 1000000L; /* 기본 임계: 100만원 */
h_thr = threshold;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*) FILTER (WHERE amount >= :h_thr),
count(*) FILTER (WHERE status = 'H')
INTO :h_big, :h_hold
FROM purchase WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("ACQ_ALERTCHK FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
if (h_big > 0 || h_hold > 0) {
snprintf(msg, sizeof(msg), "ALERT merchant=%s big=%ld hold=%ld thr=%ld",
h_merch, h_big, h_hold, threshold);
Bchg(b, T_MSG, 0, msg, 0L);
setl(b, T_RC, 1); /* 경보 */
} else {
Bchg(b, T_MSG, 0, "CLEAR", 0L);
setl(b, T_RC, 0); /* 정상 */
}
setl(b, T_ARG1, h_big);
setl(b, T_ARG2, h_hold);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_APPRGAP.h - ac ACQ_APPRGAP (copybook / record header).
* - .
* Request fields: T_BIZDATE
* Response fields: T_ARG1( ), T_AMT1(), T_ARG2( ), T_RC
*/
#ifndef ACQ_APPRGAP_H
#define ACQ_APPRGAP_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long no_approval; /* 승인 없는 매입 건수 */
long no_appr_amt;
long orphan_appr; /* 매입 없는 승인 건수 */
} ac_apprgap_rec_t;
void ACQ_APPRGAP(TPSVCINFO *p);
#endif /* ACQ_APPRGAP_H */

View file

@ -0,0 +1,38 @@
/*
* ACQ_APPRGAP.pgc - ac 모듈 서비스 ACQ_APPRGAP (one service = one file).
* 승인-매입 갭 점검: 승인 없는 매입(무승인 매입)과 매입 없는 승인(고아
* 승인)을 양방향으로 세어 갭 규모를 보고한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_APPRGAP.h"
void ACQ_APPRGAP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_noappr = 0, h_orphan = 0, h_amt = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
/* 승인 없는 매입: 건수 + 금액 */
EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0)
INTO :h_noappr, :h_amt
FROM purchase u
WHERE u.biz_date = :h_bizdate AND u.status NOT IN ('C', 'V', 'P')
AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = u.purchase_id AND a.approved = true);
if (sqlca.sqlcode < 0) FAIL(b);
/* 매입 없는 고아 승인 */
EXEC SQL SELECT count(*) INTO :h_orphan
FROM approval a LEFT JOIN purchase u ON u.purchase_id = a.purchase_id
WHERE u.purchase_id IS NULL;
if (sqlca.sqlcode < 0) { userlog("ACQ_APPRGAP FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ARG1, h_noappr); /* 무승인 매입 건수 */
setl(b, T_AMT1, h_amt); /* 무승인 매입 금액 */
setl(b, T_ARG2, h_orphan); /* 고아 승인 건수 */
setl(b, T_RC, (h_noappr + h_orphan) > 0 ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_BULKVERIFY.h - ac ACQ_BULKVERIFY (copybook / record header).
* .
* Request fields: T_MERCHANT, T_BIZDATE
* Response fields: T_COUNT, T_ARG1( ), T_ARG2( ), T_RC
*/
#ifndef ACQ_BULKVERIFY_H
#define ACQ_BULKVERIFY_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long total;
long fee_bad; /* MDR 재계산 불일치 */
long net_bad; /* amount-fee 불일치 */
} ac_bulkverify_rec_t;
void ACQ_BULKVERIFY(TPSVCINFO *p);
#endif /* ACQ_BULKVERIFY_H */

View file

@ -0,0 +1,37 @@
/*
* ACQ_BULKVERIFY.pgc - ac 모듈 서비스 ACQ_BULKVERIFY (one service = one file).
* 대량매입 검증: 가맹점·영업일 단위로 전 건의 수수료(MDR 재계산 대비)와
* 정산금(amount-fee 대비) 정합을 일괄 점검해 오류 건수를 보고한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_BULKVERIFY.h"
void ACQ_BULKVERIFY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long bps = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_bps, h_total = 0, h_feebad = 0, h_netbad = 0;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (acdb_merchant_mdr(h_merch, &bps) < 0) { setl(b, T_RC, 5); FAIL(b); }
h_bps = bps;
EXEC SQL SELECT count(*),
count(*) FILTER (WHERE fee <> amount * :h_bps / 10000),
count(*) FILTER (WHERE net <> amount - fee)
INTO :h_total, :h_feebad, :h_netbad
FROM purchase
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status NOT IN ('C', 'V');
if (sqlca.sqlcode < 0) { userlog("ACQ_BULKVERIFY FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, h_total);
setl(b, T_ARG1, h_feebad); /* 수수료 오류 건수 */
setl(b, T_ARG2, h_netbad); /* 정산금 오류 건수 */
setl(b, T_RC, (h_feebad + h_netbad) > 0 ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_CB_DEFEND.h - ac ACQ_CB_DEFEND (copybook / record header).
* .
* Request fields: T_CANCEL_ID, T_REASON( )
* Response fields: T_PURCHASE_ID, T_RC(0=, 1= , 4=CB )
*/
#ifndef ACQ_CB_DEFEND_H
#define ACQ_CB_DEFEND_H
#include "ac_svc.h"
typedef struct {
long cancel_id; /* CB 접수 ID */
char evidence[96]; /* 방어 증빙 사유 */
long purchase_id; /* 복원 대상 매입 */
long rc;
} ac_cb_defend_rec_t;
void ACQ_CB_DEFEND(TPSVCINFO *p);
#endif /* ACQ_CB_DEFEND_H */

View file

@ -0,0 +1,40 @@
/*
* ACQ_CB_DEFEND.pgc - ac 모듈 서비스 ACQ_CB_DEFEND (one service = one file).
* 지불거절 방어: 접수된 CB 건에 방어 증빙 사유를 덧붙이고, 보류 중이던
* 원매입을 대사완료(M) 상태로 복원한다. 방어 결과는 T_RC 로 응답.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CB_DEFEND.h"
void ACQ_CB_DEFEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char evid[96];
EXEC SQL BEGIN DECLARE SECTION;
long h_cid, h_pid = 0, h_upd;
char h_evid[110];
EXEC SQL END DECLARE SECTION;
h_cid = getl(b, T_CANCEL_ID);
gets_(b, T_REASON, evid, sizeof(evid));
if (evid[0] == 0) strcpy(evid, "EVIDENCE");
snprintf(h_evid, sizeof(h_evid), "DEFEND:%s", evid);
EXEC SQL SELECT purchase_id INTO :h_pid FROM ac_cancel
WHERE cancel_id = :h_cid AND cancel_type = 'CB';
if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE ac_cancel SET reason = :h_evid WHERE cancel_id = :h_cid;
if (sqlca.sqlcode < 0) FAIL(b);
/* 방어 접수 시 보류(H) 원매입을 대사완료로 복원 */
EXEC SQL UPDATE purchase SET status = 'M', updated_at = now()
WHERE purchase_id = :h_pid AND status = 'H';
if (sqlca.sqlcode < 0) { userlog("ACQ_CB_DEFEND FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
h_upd = sqlca.sqlerrd[2];
setl(b, T_PURCHASE_ID, h_pid);
setl(b, T_RC, h_upd > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_CB_OPEN.h - ac ACQ_CB_OPEN (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_AMOUNT, T_REASON, T_BIZDATE
* Response fields: T_CANCEL_ID, T_RC
*/
#ifndef ACQ_CB_OPEN_H
#define ACQ_CB_OPEN_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long cb_amount; /* 지불거절 금액 */
char reason[128]; /* 접수 사유 */
char biz_date[16];
long cancel_id; /* 생성된 CB 접수 ID */
} ac_cb_open_rec_t;
void ACQ_CB_OPEN(TPSVCINFO *p);
#endif /* ACQ_CB_OPEN_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_CB_OPEN.pgc - ac 모듈 서비스 ACQ_CB_OPEN (one service = one file).
* 지불거절(chargeback) 접수: 대사완료/정산완료 매입만 접수 가능, ac_cancel 에
* CB 유형 행을 생성하고 원매입을 보류(H) 상태로 전이한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CB_OPEN.h"
void ACQ_CB_OPEN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_amt, h_orig;
char h_st[4], h_bizdate[16], h_reason[140];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
gets_(b, T_REASON, reason, sizeof(reason));
snprintf(h_reason, sizeof(h_reason), "CB:%s", reason[0] ? reason : "ISSUER");
/* 원매입 상태·금액 검증: 대사완료(M) 또는 정산완료(S)만 지불거절 대상 */
EXEC SQL SELECT status, amount INTO :h_st, :h_orig FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'M' && h_st[0] != 'S') { setl(b, T_RC, 2); FAIL(b); }
if (h_amt <= 0 || h_amt > h_orig) { setl(b, T_RC, 3); FAIL(b); }
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_pid, 'CB', :h_amt, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
/* 분쟁 중 원매입은 보류 전이 */
EXEC SQL UPDATE purchase SET status = 'H', updated_at = now() WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_CB_OPEN FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_CANCEL_ID, h_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_CB_QRY.h - ac ACQ_CB_QRY (copybook / record header).
* .
* Request fields: T_MERCHANT, T_BIZDATE
* Response fields: T_COUNT, T_AMOUNT, T_ID1(occ 0..4 = ID)
*/
#ifndef ACQ_CB_QRY_H
#define ACQ_CB_QRY_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long cb_count; /* CB 건수 */
long cb_total; /* CB 금액 합계 */
long recent_ids[5]; /* 최근 접수 ID */
} ac_cb_qry_rec_t;
void ACQ_CB_QRY(TPSVCINFO *p);
#endif /* ACQ_CB_QRY_H */

View file

@ -0,0 +1,40 @@
/*
* ACQ_CB_QRY.pgc - ac 모듈 서비스 ACQ_CB_QRY (one service = one file).
* 지불거절 목록조회: 가맹점·영업일 기준 CB 접수 건을 커서로 훑어 건수/합계를
* 집계하고 최근 5건의 접수 ID 를 다중 occurrence 로 반환한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CB_QRY.h"
void ACQ_CB_QRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long cnt = 0, total = 0, shown = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_cid, h_amt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE cbq_cur CURSOR FOR
SELECT c.cancel_id, c.amount
FROM ac_cancel c JOIN purchase u ON u.purchase_id = c.purchase_id
WHERE c.cancel_type = 'CB' AND c.biz_date = :h_bizdate AND u.merchant_id = :h_merch
ORDER BY c.cancel_id DESC;
EXEC SQL OPEN cbq_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cbq_cur INTO :h_cid, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbq_cur; FAIL(b); }
cnt++; total += h_amt;
if (shown < 5) { Badd(b, T_ID1, (char *)&h_cid, 0L); shown++; }
}
EXEC SQL CLOSE cbq_cur;
setl(b, T_COUNT, cnt);
setl(b, T_AMOUNT, total);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_CB_STAT.h - ac ACQ_CB_STAT (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_COUNT, T_GROSS, T_AMT1(), T_AMT2(), T_ARG1()
*/
#ifndef ACQ_CB_STAT_H
#define ACQ_CB_STAT_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long cb_count;
long cb_sum;
long cb_avg;
long cb_max;
long defended; /* 방어 완료 건수 */
} ac_cb_stat_rec_t;
void ACQ_CB_STAT(TPSVCINFO *p);
#endif /* ACQ_CB_STAT_H */

View file

@ -0,0 +1,34 @@
/*
* ACQ_CB_STAT.pgc - ac 모듈 서비스 ACQ_CB_STAT (one service = one file).
* 지불거절 일별 통계: 영업일 기준 CB 건수·합계·평균·최대 금액과 방어 완료
* (DEFEND 사유) 건수를 한 번의 FILTER 집계로 산출한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CB_STAT.h"
void ACQ_CB_STAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_sum = 0, h_avg = 0, h_max = 0, h_def = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*),
coalesce(sum(amount), 0),
coalesce(avg(amount)::bigint, 0),
coalesce(max(amount), 0),
count(*) FILTER (WHERE reason LIKE 'DEFEND:%')
INTO :h_cnt, :h_sum, :h_avg, :h_max, :h_def
FROM ac_cancel WHERE cancel_type = 'CB' AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("ACQ_CB_STAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_AMT1, h_avg);
setl(b, T_AMT2, h_max);
setl(b, T_ARG1, h_def);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_CCYSUM.h - ac ACQ_CCYSUM (copybook / record header).
* .
* Request fields: T_CCY(), T_BIZDATE
* Response fields: T_COUNT, T_FX_AMT, T_AMOUNT(), T_ARG1( )
*/
#ifndef ACQ_CCYSUM_H
#define ACQ_CCYSUM_H
#include "ac_svc.h"
typedef struct {
char ccy[8]; /* 통화코드(빈값=전체) */
char biz_date[16];
long fx_count;
long fx_sum; /* 원통화 합계 */
long krw_sum; /* 원화 환산 합계 */
long ccy_kinds; /* 통화 종수 */
} ac_ccysum_rec_t;
void ACQ_CCYSUM(TPSVCINFO *p);
#endif /* ACQ_CCYSUM_H */

View file

@ -0,0 +1,40 @@
/*
* ACQ_CCYSUM.pgc - ac 모듈 서비스 ACQ_CCYSUM (one service = one file).
* 통화별 매입 집계: 통화코드를 지정하면 해당 통화의 원통화/원화 합계를,
* 미지정 시 영업일 전체의 통화 종수와 원화 환산 총액을 집계한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CCYSUM.h"
void ACQ_CCYSUM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_fx = 0, h_krw = 0, h_nccy = 0;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CCY, h_ccy, sizeof(h_ccy));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (h_ccy[0]) {
/* 단일 통화 집계 */
EXEC SQL SELECT count(*), coalesce(sum(fx_amount),0), coalesce(sum(krw_amount),0)
INTO :h_cnt, :h_fx, :h_krw
FROM ac_fx WHERE ccy = :h_ccy AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
h_nccy = 1;
} else {
/* 전체 통화 요약: 통화 종수 + 원화 환산 총액 */
EXEC SQL SELECT count(*), count(DISTINCT ccy), coalesce(sum(krw_amount),0)
INTO :h_cnt, :h_nccy, :h_krw
FROM ac_fx WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("ACQ_CCYSUM FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
}
setl(b, T_COUNT, h_cnt);
setl(b, T_FX_AMT, h_fx);
setl(b, T_AMOUNT, h_krw);
setl(b, T_ARG1, h_nccy);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_CHANSTAT.h - ac ACQ_CHANSTAT (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_CHANNEL( ), T_AMT1, T_ARG1(), T_STR2/T_AMT3(occ)
*/
#ifndef ACQ_CHANSTAT_H
#define ACQ_CHANSTAT_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
char best_channel[16]; /* 최대 매출 채널 */
long best_amount;
long kinds; /* 채널 종수 */
char channels[6][16];
long amounts[6];
} ac_chanstat_rec_t;
void ACQ_CHANSTAT(TPSVCINFO *p);
#endif /* ACQ_CHANSTAT_H */

View file

@ -0,0 +1,45 @@
/*
* ACQ_CHANSTAT.pgc - ac 모듈 서비스 ACQ_CHANSTAT (one service = one file).
* 매입 채널별 통계: 영업일 채널 그룹 집계를 커서로 순회하며 채널 종수와
* 최대 매출 채널을 찾고, 채널명/금액 쌍을 다중 occurrence 로 반환한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CHANSTAT.h"
void ACQ_CHANSTAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long kinds = 0, best = 0;
char bestchan[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_sum, h_n;
char h_chan[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
bestchan[0] = 0;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE chs_cur CURSOR FOR
SELECT channel, count(*), sum(amount) FROM purchase
WHERE biz_date = :h_bizdate AND status <> 'C'
GROUP BY channel ORDER BY sum(amount) DESC;
EXEC SQL OPEN chs_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH chs_cur INTO :h_chan, :h_n, :h_sum;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chs_cur; FAIL(b); }
if (kinds == 0) { /* 정렬 첫 행 = 최대 매출 채널 */
strncpy(bestchan, h_chan, sizeof(bestchan)-1); bestchan[sizeof(bestchan)-1] = 0;
best = h_sum;
}
if (kinds < 6) { Badd(b, T_STR2, h_chan, 0L); Badd(b, T_AMT3, (char *)&h_sum, 0L); }
kinds++;
}
EXEC SQL CLOSE chs_cur;
if (bestchan[0]) Bchg(b, T_CHANNEL, 0, bestchan, 0L);
setl(b, T_AMT1, best);
setl(b, T_ARG1, kinds); /* 채널 종수 */
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_CLAIMPROC.h - ac ACQ_CLAIMPROC (copybook / record header).
* .
* Request fields: T_ID1( ID), T_STR1(), T_BIZDATE
* Response fields: T_PURCHASE_ID, T_MSG, T_RC(4= )
*/
#ifndef ACQ_CLAIMPROC_H
#define ACQ_CLAIMPROC_H
#include "ac_svc.h"
typedef struct {
long claim_id;
char result[64]; /* 처리 결과 코드 */
char biz_date[16];
long purchase_id;
} ac_claimproc_rec_t;
void ACQ_CLAIMPROC(TPSVCINFO *p);
#endif /* ACQ_CLAIMPROC_H */

View file

@ -0,0 +1,38 @@
/*
* ACQ_CLAIMPROC.pgc - ac 모듈 서비스 ACQ_CLAIMPROC (one service = one file).
* 클레임 처리(종결): 등록된 클레임 행을 삭제하고 처리 결과를 원장에
* CLAIMRES 분개(금액 0)로 남긴다. 클레임이 아니거나 없으면 T_RC=4.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CLAIMPROC.h"
void ACQ_CLAIMPROC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], result[64];
EXEC SQL BEGIN DECLARE SECTION;
long h_uid, h_pid = 0, h_del;
EXEC SQL END DECLARE SECTION;
h_uid = getl(b, T_ID1);
gets_(b, T_STR1, result, sizeof(result));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (result[0] == 0) strcpy(result, "ACCEPTED");
EXEC SQL SELECT purchase_id INTO :h_pid FROM ac_unmatch
WHERE unmatch_id = :h_uid AND reason LIKE 'CLAIM:%';
if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL DELETE FROM ac_unmatch WHERE unmatch_id = :h_uid;
if (sqlca.sqlcode < 0) FAIL(b);
h_del = sqlca.sqlerrd[2];
/* 처리 흔적을 원장에 남긴다 (금액 0, 유형 CLAIMRES) */
if (h_del > 0 && acdb_insert_ledger(h_pid, "CLAIMRES", 0, bizdate) < 0) FAIL(b);
Bchg(b, T_MSG, 0, result, 0L);
setl(b, T_PURCHASE_ID, h_pid);
setl(b, T_RC, h_del > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_CLAIMQRY.h - ac ACQ_CLAIMQRY (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_COUNT, T_ARG1( ), T_ID1/T_STR1(occ 0..4)
*/
#ifndef ACQ_CLAIMQRY_H
#define ACQ_CLAIMQRY_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long total; /* 미종결 클레임 총건수 */
long claim_ids[5];
char reasons[5][128];
} ac_claimqry_rec_t;
void ACQ_CLAIMQRY(TPSVCINFO *p);
#endif /* ACQ_CLAIMQRY_H */

View file

@ -0,0 +1,42 @@
/*
* ACQ_CLAIMQRY.pgc - ac 모듈 서비스 ACQ_CLAIMQRY (one service = one file).
* 클레임 목록조회: 영업일의 미종결 클레임을 커서로 순회, 총건수와 함께
* 상위 5건의 클레임 ID/사유를 다중 occurrence 로 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CLAIMQRY.h"
void ACQ_CLAIMQRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long total = 0, listed = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_uid;
char h_reason[128], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE clq_cur CURSOR FOR
SELECT unmatch_id, reason FROM ac_unmatch
WHERE reason LIKE 'CLAIM:%' AND biz_date = :h_bizdate
ORDER BY unmatch_id;
EXEC SQL OPEN clq_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH clq_cur INTO :h_uid, :h_reason;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clq_cur; FAIL(b); }
total++;
if (listed < 5) {
Badd(b, T_ID1, (char *)&h_uid, 0L);
Badd(b, T_STR1, h_reason, 0L);
listed++;
}
}
EXEC SQL CLOSE clq_cur;
setl(b, T_COUNT, total);
setl(b, T_ARG1, listed);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_CLAIMREG.h - ac ACQ_CLAIMREG (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_REASON, T_BIZDATE
* Response fields: T_ID1( ID), T_RC(2=, 5= )
*/
#ifndef ACQ_CLAIMREG_H
#define ACQ_CLAIMREG_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
char reason[100]; /* 이의신청 사유 */
char biz_date[16];
long claim_id; /* ac_unmatch 행 ID */
} ac_claimreg_rec_t;
void ACQ_CLAIMREG(TPSVCINFO *p);
#endif /* ACQ_CLAIMREG_H */

View file

@ -0,0 +1,42 @@
/*
* ACQ_CLAIMREG.pgc - ac 모듈 서비스 ACQ_CLAIMREG (one service = one file).
* 클레임 등록: 가맹점 이의신청을 미매입 원장(ac_unmatch)에 CLAIM 사유로
* 기록한다. 동일 매입에 미종결 클레임이 이미 있으면 중복 등록을 막는다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CLAIMREG.h"
void ACQ_CLAIMREG(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char why[100];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_exist = 0, h_have = 0;
char h_reason[120], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_REASON, why, sizeof(why));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
snprintf(h_reason, sizeof(h_reason), "CLAIM:%s", why[0] ? why : "MERCHANT");
EXEC SQL SELECT count(*) INTO :h_exist FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_exist == 0) { setl(b, T_RC, 5); FAIL(b); }
/* 미종결 클레임 중복 방지 */
EXEC SQL SELECT count(*) INTO :h_have FROM ac_unmatch
WHERE purchase_id = :h_pid AND reason LIKE 'CLAIM:%';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_have > 0) { setl(b, T_RC, 2); FAIL(b); }
EXEC SQL SELECT nextval('ac_unmatch_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date)
VALUES (:h_id, :h_pid, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("ACQ_CLAIMREG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ID1, h_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_CORRHIST.h - ac ACQ_CORRHIST (copybook / record header).
* .
* Request fields: T_PURCHASE_ID
* Response fields: T_COUNT, T_ARG1( ), T_ADJ/T_DELTA(occ 0..9)
*/
#ifndef ACQ_CORRHIST_H
#define ACQ_CORRHIST_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long adj_rows; /* 수수료 정정 건수(최대 10) */
long cancel_rows; /* 취소류 정정 건수 */
long adj_ids[10];
long deltas[10];
} ac_corrhist_rec_t;
void ACQ_CORRHIST(TPSVCINFO *p);
#endif /* ACQ_CORRHIST_H */

View file

@ -0,0 +1,41 @@
/*
* ACQ_CORRHIST.pgc - ac 모듈 서비스 ACQ_CORRHIST (one service = one file).
* 정정 이력조회: 매입 건에 붙은 수수료 조정 이력(최근 10건)을 역순 커서로
* 내려주고, 취소계 정정(ac_cancel) 건수를 별도 카운트로 함께 응답한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CORRHIST.h"
void ACQ_CORRHIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rows = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_adj, h_delta, h_cxl = 0;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL DECLARE crh_cur CURSOR FOR
SELECT adj_id, delta FROM ac_fee_adj
WHERE purchase_id = :h_pid ORDER BY adj_id DESC LIMIT 10;
EXEC SQL OPEN crh_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH crh_cur INTO :h_adj, :h_delta;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crh_cur; FAIL(b); }
Badd(b, T_ADJ, (char *)&h_adj, 0L);
Badd(b, T_DELTA, (char *)&h_delta, 0L);
rows++;
}
EXEC SQL CLOSE crh_cur;
/* 취소·환불류 정정 건수 병기 */
EXEC SQL SELECT count(*) INTO :h_cxl FROM ac_cancel WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_CORRHIST FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, rows);
setl(b, T_ARG1, h_cxl);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_CTRYSUM.h - ac ACQ_CTRYSUM (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_ARG1(), T_ARG2(), T_ARG3(), T_ARG4(), T_GROSS
*/
#ifndef ACQ_CTRYSUM_H
#define ACQ_CTRYSUM_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long amer_krw; /* 미주권 원화 합계 */
long japan_krw;
long europe_krw;
long etc_krw;
} ac_ctrysum_rec_t;
void ACQ_CTRYSUM(TPSVCINFO *p);
#endif /* ACQ_CTRYSUM_H */

View file

@ -0,0 +1,48 @@
/*
* ACQ_CTRYSUM.pgc - ac 모듈 서비스 ACQ_CTRYSUM (one service = one file).
* 국가별 매입 집계: 통화코드를 국가권역(미주/일본/유럽/기타)으로 매핑하는
* C 분류기를 거쳐 해외매입 원화금액을 권역별 버킷에 누적한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_CTRYSUM.h"
/* 통화코드 → 권역 인덱스 (0=미주, 1=일본, 2=유럽, 3=기타) */
static int ac_ccy_region(const char *ccy)
{
if (strcmp(ccy, "USD") == 0 || strcmp(ccy, "CAD") == 0) return 0;
if (strcmp(ccy, "JPY") == 0) return 1;
if (strcmp(ccy, "EUR") == 0 || strcmp(ccy, "GBP") == 0 || strcmp(ccy, "CHF") == 0) return 2;
return 3;
}
void ACQ_CTRYSUM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long bucket[4] = {0, 0, 0, 0};
EXEC SQL BEGIN DECLARE SECTION;
long h_krw;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE ctr_cur CURSOR FOR
SELECT ccy, sum(krw_amount) FROM ac_fx
WHERE biz_date = :h_bizdate GROUP BY ccy;
EXEC SQL OPEN ctr_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH ctr_cur INTO :h_ccy, :h_krw;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ctr_cur; FAIL(b); }
bucket[ac_ccy_region(h_ccy)] += h_krw;
}
EXEC SQL CLOSE ctr_cur;
setl(b, T_ARG1, bucket[0]); /* 미주 */
setl(b, T_ARG2, bucket[1]); /* 일본 */
setl(b, T_ARG3, bucket[2]); /* 유럽 */
setl(b, T_ARG4, bucket[3]); /* 기타 */
setl(b, T_GROSS, bucket[0] + bucket[1] + bucket[2] + bucket[3]);
OK(b);
}

View file

@ -0,0 +1,26 @@
/*
* ACQ_DAYSTAT.h - ac ACQ_DAYSTAT (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_ARG1..T_ARG6( ), T_COUNT, T_GROSS
*/
#ifndef ACQ_DAYSTAT_H
#define ACQ_DAYSTAT_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long cnt_accept; /* A */
long cnt_matched; /* M */
long cnt_settled; /* S */
long cnt_cancel; /* C */
long cnt_hold; /* H */
long cnt_void; /* V */
long total;
long gross;
} ac_daystat_rec_t;
void ACQ_DAYSTAT(TPSVCINFO *p);
#endif /* ACQ_DAYSTAT_H */

View file

@ -0,0 +1,40 @@
/*
* ACQ_DAYSTAT.pgc - ac 모듈 서비스 ACQ_DAYSTAT (one service = one file).
* 매입 일별 상태 통계: 영업일 전 건을 상태별(접수/대사/정산/취소/보류/무효)
* FILTER 집계 한 방으로 나눠 세고, 총건수·총액을 함께 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_DAYSTAT.h"
void ACQ_DAYSTAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_a = 0, h_m = 0, h_s = 0, h_c = 0, h_h = 0, h_v = 0, h_tot = 0, h_sum = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*) FILTER (WHERE status = 'A'),
count(*) FILTER (WHERE status = 'M'),
count(*) FILTER (WHERE status = 'S'),
count(*) FILTER (WHERE status = 'C'),
count(*) FILTER (WHERE status = 'H'),
count(*) FILTER (WHERE status = 'V'),
count(*),
coalesce(sum(amount), 0)
INTO :h_a, :h_m, :h_s, :h_c, :h_h, :h_v, :h_tot, :h_sum
FROM purchase WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("ACQ_DAYSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ARG1, h_a); /* 접수 */
setl(b, T_ARG2, h_m); /* 대사완료 */
setl(b, T_ARG3, h_s); /* 정산완료 */
setl(b, T_ARG4, h_c); /* 취소 */
setl(b, T_ARG5, h_h); /* 보류 */
setl(b, T_ARG6, h_v); /* 무효 */
setl(b, T_COUNT, h_tot);
setl(b, T_GROSS, h_sum);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_DUPSCAN.h - ac ACQ_DUPSCAN (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_COUNT( ), T_ARG1( ), T_AMT1( ), T_RC
*/
#ifndef ACQ_DUPSCAN_H
#define ACQ_DUPSCAN_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
long dup_groups; /* (가맹점,금액) 중복 그룹 수 */
long dup_rows;
long dup_amount;
} ac_dupscan_rec_t;
void ACQ_DUPSCAN(TPSVCINFO *p);
#endif /* ACQ_DUPSCAN_H */

View file

@ -0,0 +1,33 @@
/*
* ACQ_DUPSCAN.pgc - ac 모듈 서비스 ACQ_DUPSCAN (one service = one file).
* 중복매입 스캔: 영업일 내 (가맹점, 금액) 조합이 2건 이상인 그룹을 부질의로
* 찾아 의심 그룹 수와 중복 연루 총액을 보고한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_DUPSCAN.h"
void ACQ_DUPSCAN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_groups = 0, h_rows = 0, h_amt = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*), coalesce(sum(dup_n), 0), coalesce(sum(dup_amt), 0)
INTO :h_groups, :h_rows, :h_amt
FROM (SELECT count(*) AS dup_n, sum(amount) AS dup_amt
FROM purchase
WHERE biz_date = :h_bizdate AND status NOT IN ('C', 'V')
GROUP BY merchant_id, amount
HAVING count(*) > 1) g;
if (sqlca.sqlcode < 0) { userlog("ACQ_DUPSCAN FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, h_groups); /* 의심 그룹 수 */
setl(b, T_ARG1, h_rows); /* 연루 매입 건수 */
setl(b, T_AMT1, h_amt); /* 연루 총액 */
setl(b, T_RC, h_groups > 0 ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_EDIRESEND.h - ac ACQ_EDIRESEND (copybook / record header).
* EDI .
* Request fields: T_DOCNO( )
* Response fields: T_ID1( EDI ID), T_DOCNO(-R ), T_AMOUNT, T_RC
*/
#ifndef ACQ_EDIRESEND_H
#define ACQ_EDIRESEND_H
#include "ac_svc.h"
typedef struct {
char doc_no[64]; /* 원본 문서번호 */
char resend_doc[72]; /* -R 접미 재전송 문서번호 */
long new_edi_id;
long amount;
} ac_ediresend_rec_t;
void ACQ_EDIRESEND(TPSVCINFO *p);
#endif /* ACQ_EDIRESEND_H */

View file

@ -0,0 +1,46 @@
/*
* ACQ_EDIRESEND.pgc - ac 모듈 서비스 ACQ_EDIRESEND (one service = one file).
* EDI 재전송: 문서번호의 최신 EDI 원장을 찾아 재전송 사본(-R 접미 문서번호)
* 을 발행한다. 이미 재전송본이면 다시 만들지 않는다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_EDIRESEND.h"
void ACQ_EDIRESEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
size_t dlen;
EXEC SQL BEGIN DECLARE SECTION;
long h_new, h_amt = 0;
char h_doc[64], h_redoc[72], h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_DOCNO, h_doc, sizeof(h_doc));
if (h_doc[0] == 0) { setl(b, T_RC, 8); FAIL(b); }
/* 재전송본(-R 접미) 자체는 재전송 금지 */
dlen = strlen(h_doc);
if (dlen >= 2 && strcmp(h_doc + dlen - 2, "-R") == 0) { setl(b, T_RC, 2); FAIL(b); }
/* 최신 원본 EDI 행 조회 */
EXEC SQL SELECT merchant_id, amount, to_char(biz_date, 'YYYY-MM-DD')
INTO :h_merch, :h_amt, :h_bizdate
FROM ac_edi WHERE doc_no = :h_doc
ORDER BY edi_id DESC LIMIT 1;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
snprintf(h_redoc, sizeof(h_redoc), "%s-R", h_doc);
EXEC SQL SELECT nextval('ac_edi_seq') INTO :h_new;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_edi (edi_id, merchant_id, doc_no, amount, biz_date)
VALUES (:h_new, :h_merch, :h_redoc, :h_amt, :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("ACQ_EDIRESEND FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
Bchg(b, T_DOCNO, 0, h_redoc, 0L);
setl(b, T_ID1, h_new);
setl(b, T_AMOUNT, h_amt);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_FEEHIST.h - ac ACQ_FEEHIST (copybook / record header).
* .
* Request fields: T_PURCHASE_ID
* Response fields: T_COUNT, T_DELTA(), T_AMT1(), T_AMT2(), T_RC
*/
#ifndef ACQ_FEEHIST_H
#define ACQ_FEEHIST_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long adj_count;
long net_delta; /* 순증감 합계 */
long up_sum; /* 증액분 */
long down_sum; /* 감액분 */
} ac_feehist_rec_t;
void ACQ_FEEHIST(TPSVCINFO *p);
#endif /* ACQ_FEEHIST_H */

View file

@ -0,0 +1,32 @@
/*
* ACQ_FEEHIST.pgc - ac 모듈 서비스 ACQ_FEEHIST (one service = one file).
* 수수료 조정 이력 집계: 매입 건의 조정 건수, 순증감 합계, 증액분/감액분을
* FILTER 집계로 한 번에 산출한다. 순증감이 0 이 아니면 T_RC=1 로 표시.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_FEEHIST.h"
void ACQ_FEEHIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_n = 0, h_net = 0, h_up = 0, h_dn = 0;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*),
coalesce(sum(delta), 0),
coalesce(sum(delta) FILTER (WHERE delta > 0), 0),
coalesce(sum(delta) FILTER (WHERE delta < 0), 0)
INTO :h_n, :h_net, :h_up, :h_dn
FROM ac_fee_adj WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_FEEHIST FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, h_n);
setl(b, T_DELTA, h_net); /* 순증감 */
setl(b, T_AMT1, h_up); /* 증액 합계 */
setl(b, T_AMT2, h_dn); /* 감액 합계(음수) */
setl(b, T_RC, h_net != 0 ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_FEERECALC.h - ac ACQ_FEERECALC (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_BIZDATE
* Response fields: T_FEE( ), T_DELTA(), T_ADJ, T_RC
*/
#ifndef ACQ_FEERECALC_H
#define ACQ_FEERECALC_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
char biz_date[16];
long new_fee; /* 현행 MDR 재산정 수수료 */
long delta; /* 기존 대비 차액 */
long adj_id;
} ac_feerecalc_rec_t;
void ACQ_FEERECALC(TPSVCINFO *p);
#endif /* ACQ_FEERECALC_H */

View file

@ -0,0 +1,44 @@
/*
* ACQ_FEERECALC.pgc - ac 모듈 서비스 ACQ_FEERECALC (one service = one file).
* 수수료 재산정: 가맹점의 현행 MDR 로 수수료를 다시 계산해 기존 수수료와의
* 차액이 있으면 조정 이력을 남기고 purchase 의 fee/net 을 보정한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_FEERECALC.h"
void ACQ_FEERECALC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long bps = 0, newfee, delta, adj;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt = 0, h_fee = 0, h_newfee;
char h_merch[64];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT merchant_id, amount, fee INTO :h_merch, :h_amt, :h_fee
FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (acdb_merchant_mdr(h_merch, &bps) < 0) FAIL(b);
newfee = acq_fee(h_amt, bps);
delta = newfee - h_fee;
if (delta != 0) {
adj = acdb_insert_fee_adj(h_pid, delta, "RECALC", bizdate);
if (adj < 0) FAIL(b);
h_newfee = newfee;
EXEC SQL UPDATE purchase SET fee = :h_newfee, net = amount - :h_newfee, updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_FEERECALC FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ADJ, adj);
}
setl(b, T_FEE, newfee);
setl(b, T_DELTA, delta);
setl(b, T_RC, delta == 0 ? 1 : 0); /* 1=차액 없음 */
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_FEEWAIVE.h - ac ACQ_FEEWAIVE (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_ARG1( bps), T_BIZDATE
* Response fields: T_AMT1(), T_ADJ, T_RC
*/
#ifndef ACQ_FEEWAIVE_H
#define ACQ_FEEWAIVE_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long waive_bps; /* 감면율 (basis points) */
char biz_date[16];
long waive_amount; /* 감면액 */
long adj_id;
} ac_feewaive_rec_t;
void ACQ_FEEWAIVE(TPSVCINFO *p);
#endif /* ACQ_FEEWAIVE_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_FEEWAIVE.pgc - ac 모듈 서비스 ACQ_FEEWAIVE (one service = one file).
* 수수료 감면: 요청 감면율(bps)만큼 수수료를 깎아 가맹점 정산금에 돌려준다.
* 음수 delta 조정 이력을 남기고 purchase 의 fee 차감 / net 증액을 반영.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_FEEWAIVE.h"
void ACQ_FEEWAIVE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rate, waive, adj;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_fee = 0, h_w;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
rate = getl(b, T_ARG1); /* 감면율 bps (1~10000) */
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (rate < 1 || rate > 10000) { setl(b, T_RC, 9); FAIL(b); }
EXEC SQL SELECT fee INTO :h_fee FROM purchase
WHERE purchase_id = :h_pid AND status NOT IN ('C', 'V');
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
waive = acq_fee(h_fee, rate); /* fee * rate / 10000 */
if (waive <= 0) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); }
adj = acdb_insert_fee_adj(h_pid, -waive, "WAIVE", bizdate);
if (adj < 0) FAIL(b);
h_w = waive;
EXEC SQL UPDATE purchase SET fee = fee - :h_w, net = net + :h_w, updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_FEEWAIVE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_AMT1, waive);
setl(b, T_ADJ, adj);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,24 @@
/*
* ACQ_FXCONVERT.h - ac ACQ_FXCONVERT (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_CCY, T_FX_AMT, T_ARG1( bps), T_BIZDATE
* Response fields: T_ID1(fx ID), T_AMOUNT( ), T_RC
*/
#ifndef ACQ_FXCONVERT_H
#define ACQ_FXCONVERT_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
char ccy[8];
long fx_amount; /* 원통화 금액 */
long rate_bps; /* 적용환율 */
char biz_date[16];
long fx_id;
long krw_amount; /* 원화 환산액 */
} ac_fxconvert_rec_t;
void ACQ_FXCONVERT(TPSVCINFO *p);
#endif /* ACQ_FXCONVERT_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_FXCONVERT.pgc - ac 모듈 서비스 ACQ_FXCONVERT (one service = one file).
* 통화 환산 등록: 해외매입의 원통화 금액에 적용환율(bps)을 곱해 원화를
* 산출하고 ac_fx 원장에 기록, 매입 채널을 FOREIGN 으로 표시한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_FXCONVERT.h"
void ACQ_FXCONVERT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long krw;
EXEC SQL BEGIN DECLARE SECTION;
long h_fxid, h_pid, h_fx, h_rate, h_krw;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_fx = getl(b, T_FX_AMT);
h_rate = getl(b, T_ARG1); /* 환율 bps: KRW = fx * rate / 10000 */
gets_(b, T_CCY, h_ccy, sizeof(h_ccy));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (h_fx <= 0 || h_rate <= 0 || h_ccy[0] == 0) { setl(b, T_RC, 3); FAIL(b); }
krw = (h_fx * h_rate) / 10000L;
h_krw = krw;
EXEC SQL SELECT nextval('ac_fx_seq') INTO :h_fxid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date)
VALUES (:h_fxid, :h_pid, :h_ccy, :h_fx, :h_krw, :h_rate, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
/* 해외매입 채널 표시 (존재하는 매입에만 반영) */
EXEC SQL UPDATE purchase SET channel = 'FOREIGN', updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_FXCONVERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ID1, h_fxid);
setl(b, T_AMOUNT, krw);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_FXRATE.h - ac ACQ_FXRATE (copybook / record header).
* .
* Request fields: T_CCY, T_BIZDATE
* Response fields: T_ARG1( bps), T_ARG2(), T_ARG3(), T_COUNT, T_RC
*/
#ifndef ACQ_FXRATE_H
#define ACQ_FXRATE_H
#include "ac_svc.h"
typedef struct {
char ccy[8];
char biz_date[16];
long avg_bps; /* 평균환율 bps */
long min_bps;
long max_bps;
} ac_fxrate_rec_t;
void ACQ_FXRATE(TPSVCINFO *p);
#endif /* ACQ_FXRATE_H */

View file

@ -0,0 +1,35 @@
/*
* ACQ_FXRATE.pgc - ac 모듈 서비스 ACQ_FXRATE (one service = one file).
* 통화 적용환율 조회: 해외매입 원장(ac_fx)에서 특정 통화의 최근 영업일
* 평균/최저/최고 환율(bps)을 조회한다. 건수가 없으면 T_RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_FXRATE.h"
void ACQ_FXRATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_n = 0, h_avg = 0, h_min = 0, h_max = 0;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CCY, h_ccy, sizeof(h_ccy));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (h_ccy[0] == 0) { setl(b, T_RC, 8); FAIL(b); }
EXEC SQL SELECT count(*),
coalesce(avg(rate_bps)::bigint, 0),
coalesce(min(rate_bps), 0),
coalesce(max(rate_bps), 0)
INTO :h_n, :h_avg, :h_min, :h_max
FROM ac_fx WHERE ccy = :h_ccy AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { userlog("ACQ_FXRATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_ARG1, h_avg);
setl(b, T_ARG2, h_min);
setl(b, T_ARG3, h_max);
setl(b, T_COUNT, h_n);
setl(b, T_RC, h_n > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_HOLDQRY.h - ac ACQ_HOLDQRY (copybook / record header).
* .
* Request fields: T_MERCHANT
* Response fields: T_COUNT, T_GROSS, T_ID2/T_AMT4(occ 0..4), T_RC(1= )
*/
#ifndef ACQ_HOLDQRY_H
#define ACQ_HOLDQRY_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
long hold_count;
long hold_total;
long top_pids[5];
long top_amounts[5];
} ac_holdqry_rec_t;
void ACQ_HOLDQRY(TPSVCINFO *p);
#endif /* ACQ_HOLDQRY_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_HOLDQRY.pgc - ac 모듈 서비스 ACQ_HOLDQRY (one service = one file).
* 보류 매입 조회: 가맹점의 보류(H) 건을 최근 갱신순 커서로 훑어 건수·총액을
* 집계하고 상위 5건의 매입 ID/금액을 occurrence 로 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_HOLDQRY.h"
void ACQ_HOLDQRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long cnt = 0, total = 0, top = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt;
char h_merch[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
EXEC SQL DECLARE hlq_cur CURSOR FOR
SELECT purchase_id, amount FROM purchase
WHERE merchant_id = :h_merch AND status = 'H'
ORDER BY updated_at DESC;
EXEC SQL OPEN hlq_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH hlq_cur INTO :h_pid, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE hlq_cur; FAIL(b); }
cnt++; total += h_amt;
if (top < 5) {
Badd(b, T_ID2, (char *)&h_pid, 0L);
Badd(b, T_AMT4, (char *)&h_amt, 0L);
top++;
}
}
EXEC SQL CLOSE hlq_cur;
setl(b, T_COUNT, cnt);
setl(b, T_GROSS, total);
setl(b, T_RC, cnt > 0 ? 1 : 0); /* 1=보류 존재 */
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_ISSSTAT.h - ac ACQ_ISSSTAT (copybook / record header).
* .
* Request fields: T_BIZDATE
* Response fields: T_ISSUER(1), T_COUNT, T_GROSS, T_ARG1( ), T_ARG2( bps)
*/
#ifndef ACQ_ISSSTAT_H
#define ACQ_ISSSTAT_H
#include "ac_svc.h"
typedef struct {
char biz_date[16];
char top_issuer[32];
long top_count;
long top_sum;
long issuer_kinds;
long share_bps; /* 1위 점유율 */
} ac_issstat_rec_t;
void ACQ_ISSSTAT(TPSVCINFO *p);
#endif /* ACQ_ISSSTAT_H */

View file

@ -0,0 +1,44 @@
/*
* ACQ_ISSSTAT.pgc - ac 모듈 서비스 ACQ_ISSSTAT (one service = one file).
* 매입 발급사별 통계: 영업일 발급사 그룹 집계에서 1위 발급사(건수·총액)와
* 전체 발급사 수, 1위의 점유율(bps)을 계산해 응답한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_ISSSTAT.h"
void ACQ_ISSSTAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long share_bps = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_topcnt = 0, h_topsum = 0, h_kinds = 0, h_total = 0;
char h_top[32], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_top[0] = 0;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
/* 전체 발급사 수 + 총액 */
EXEC SQL SELECT count(DISTINCT issuer), coalesce(sum(amount), 0)
INTO :h_kinds, :h_total
FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C';
if (sqlca.sqlcode < 0) FAIL(b);
/* 1위 발급사 */
EXEC SQL SELECT issuer, count(*), sum(amount)
INTO :h_top, :h_topcnt, :h_topsum
FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C'
GROUP BY issuer ORDER BY sum(amount) DESC, issuer LIMIT 1;
if (sqlca.sqlcode < 0) { userlog("ACQ_ISSSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); }
if (h_total > 0) share_bps = (h_topsum * 10000) / h_total;
Bchg(b, T_ISSUER, 0, h_top, 0L);
setl(b, T_COUNT, h_topcnt);
setl(b, T_GROSS, h_topsum);
setl(b, T_ARG1, h_kinds);
setl(b, T_ARG2, share_bps); /* 1위 점유율 bps */
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_LIMITUSAGE.h - ac ACQ_LIMITUSAGE (copybook / record header).
* .
* Request fields: T_MERCHANT, T_BIZDATE
* Response fields: T_AMT1(), T_AMT2(), T_AMT3(), T_ARG1( bps), T_RC
*/
#ifndef ACQ_LIMITUSAGE_H
#define ACQ_LIMITUSAGE_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long used; /* 당일 사용액 */
long limit; /* 일한도 */
long remain; /* 잔여 한도 */
long usage_bps; /* 소진율 (basis points) */
} ac_limitusage_rec_t;
void ACQ_LIMITUSAGE(TPSVCINFO *p);
#endif /* ACQ_LIMITUSAGE_H */

View file

@ -0,0 +1,37 @@
/*
* ACQ_LIMITUSAGE.pgc - ac 모듈 서비스 ACQ_LIMITUSAGE (one service = one file).
* 가맹점 한도소진 조회: 영업일 유효 매입(취소/무효 제외) 합계를 일한도와
* 비교해 사용액/잔여액/소진율(bps)을 산출한다. 90% 초과 시 경보 표시.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_LIMITUSAGE.h"
void ACQ_LIMITUSAGE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pct_bps;
EXEC SQL BEGIN DECLARE SECTION;
long h_limit = 0, h_used = 0;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT daily_limit INTO :h_limit FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_used FROM purchase
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status NOT IN ('C', 'V');
if (sqlca.sqlcode < 0) { userlog("ACQ_LIMITUSAGE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
pct_bps = (h_limit > 0) ? (h_used * 10000) / h_limit : 10000;
setl(b, T_AMT1, h_used);
setl(b, T_AMT2, h_limit);
setl(b, T_AMT3, h_limit > h_used ? h_limit - h_used : 0); /* 잔여 한도 */
setl(b, T_ARG1, pct_bps);
setl(b, T_RC, pct_bps > 9000 ? 1 : 0); /* 90% 초과 경보 */
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_MONSTAT.h - ac ACQ_MONSTAT (copybook / record header).
* .
* Request fields: T_BIZDATE( )
* Response fields: T_COUNT, T_GROSS, T_AMT1(), T_ARG1(), T_RC
*/
#ifndef ACQ_MONSTAT_H
#define ACQ_MONSTAT_H
#include "ac_svc.h"
typedef struct {
char base_date[16];
long mon_count;
long mon_gross;
long mon_avg; /* 평균 거래액 */
long biz_days; /* 거래 발생 영업일수 */
} ac_monstat_rec_t;
void ACQ_MONSTAT(TPSVCINFO *p);
#endif /* ACQ_MONSTAT_H */

View file

@ -0,0 +1,35 @@
/*
* ACQ_MONSTAT.pgc - ac 모듈 서비스 ACQ_MONSTAT (one service = one file).
* 매입 월간 통계: 기준일이 속한 달 전체의 건수/총액/평균 거래액과 영업일수
* (거래 발생일 기준)를 date_trunc 월 경계로 집계한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_MONSTAT.h"
void ACQ_MONSTAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_amt = 0, h_avg = 0, h_days = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*),
coalesce(sum(amount), 0),
coalesce(avg(amount)::bigint, 0),
count(DISTINCT biz_date)
INTO :h_cnt, :h_amt, :h_avg, :h_days
FROM purchase
WHERE date_trunc('month', biz_date) = date_trunc('month', CAST(:h_bizdate AS date))
AND status NOT IN ('C', 'V');
if (sqlca.sqlcode < 0) { userlog("ACQ_MONSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_amt);
setl(b, T_AMT1, h_avg); /* 평균 거래액 */
setl(b, T_ARG1, h_days); /* 거래 발생 영업일수 */
setl(b, T_RC, h_cnt > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_NETCALC.h - ac ACQ_NETCALC (copybook / record header).
* .
* Request fields: T_PURCHASE_ID
* Response fields: T_NET( ), T_TAX( ), T_DELTA, T_RC
*/
#ifndef ACQ_NETCALC_H
#define ACQ_NETCALC_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long new_net; /* amount - fee - 원천세 */
long wht_sum;
long delta; /* 기존 net 대비 변경분 */
} ac_netcalc_rec_t;
void ACQ_NETCALC(TPSVCINFO *p);
#endif /* ACQ_NETCALC_H */

View file

@ -0,0 +1,46 @@
/*
* ACQ_NETCALC.pgc - ac 모듈 서비스 ACQ_NETCALC (one service = one file).
* 정산금 재계산: 매입금액에서 수수료와 원천세 누계를 차감한 실정산금을
* 다시 구해 purchase.net 을 보정한다. 변경 없으면 T_RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_NETCALC.h"
void ACQ_NETCALC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long newnet;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt = 0, h_fee = 0, h_oldnet = 0, h_wht = 0, h_newnet;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT amount, fee, net INTO :h_amt, :h_fee, :h_oldnet
FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
/* 원천세 누계 반영 */
EXEC SQL SELECT coalesce(sum(tax_amount), 0) INTO :h_wht
FROM ac_wht WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
newnet = h_amt - h_fee - h_wht;
if (newnet == h_oldnet) {
setl(b, T_NET, newnet);
setl(b, T_RC, 1); /* 이미 정합 */
OK(b);
}
h_newnet = newnet;
EXEC SQL UPDATE purchase SET net = :h_newnet, updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_NETCALC FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_NET, newnet);
setl(b, T_TAX, h_wht);
setl(b, T_DELTA, newnet - h_oldnet);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_NOINTSHARE.h - ac ACQ_NOINTSHARE (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_BIZDATE
* Response fields: T_AMT1(), T_INSTALL_N, T_ISSUER, T_ADJ, T_RC
*/
#ifndef ACQ_NOINTSHARE_H
#define ACQ_NOINTSHARE_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
char biz_date[16];
long share; /* 발급사 분담금 (months*15bps) */
long months; /* 할부 개월수 */
char issuer[32];
long adj_id;
} ac_nointshare_rec_t;
void ACQ_NOINTSHARE(TPSVCINFO *p);
#endif /* ACQ_NOINTSHARE_H */

View file

@ -0,0 +1,44 @@
/*
* ACQ_NOINTSHARE.pgc - ac 모듈 서비스 ACQ_NOINTSHARE (one service = one file).
* 무이자 할부 분담금 산정: 할부 매입의 개월수에 월당 15bps 를 곱한 발급사
* 분담률로 분담금을 계산해 수수료 조정(NOINT)으로 반영한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_NOINTSHARE.h"
#define AC_NOINT_BPS_PER_MONTH 15L /* 발급사 분담률: 월당 15bps */
void ACQ_NOINTSHARE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long share, adj;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_months = 0, h_amt = 0;
char h_issuer[32];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
/* 할부 스케줄에서 개월수 확인 (스케줄 없으면 무이자 대상 아님) */
EXEC SQL SELECT coalesce(max(months), 0) INTO :h_months
FROM ac_install WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_months < 2) { setl(b, T_RC, 4); FAIL(b); }
EXEC SQL SELECT amount, issuer INTO :h_amt, :h_issuer
FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
share = acq_fee(h_amt, h_months * AC_NOINT_BPS_PER_MONTH);
adj = acdb_insert_fee_adj(h_pid, -share, "NOINT", bizdate);
if (adj < 0) { userlog("ACQ_NOINTSHARE adj FAIL pid=%ld", h_pid); FAIL(b); }
Bchg(b, T_ISSUER, 0, h_issuer, 0L);
setl(b, T_AMT1, share); /* 발급사 분담금 */
setl(b, T_INSTALL_N, h_months);
setl(b, T_ADJ, adj);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* ACQ_PARTCXL.h - ac ACQ_PARTCXL (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_AMOUNT(), T_BIZDATE
* Response fields: T_CANCEL_ID, T_AMT1(), T_FEE( ), T_RC
*/
#ifndef ACQ_PARTCXL_H
#define ACQ_PARTCXL_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long cancel_amount; /* 부분취소액 */
char biz_date[16];
long cancel_id;
long remain_amount; /* 잔여 매입금액 */
long fee_back; /* 비례 환급 수수료 */
} ac_partcxl_rec_t;
void ACQ_PARTCXL(TPSVCINFO *p);
#endif /* ACQ_PARTCXL_H */

View file

@ -0,0 +1,51 @@
/*
* ACQ_PARTCXL.pgc - ac 모듈 서비스 ACQ_PARTCXL (one service = one file).
* 부분취소: 취소액이 잔여 매입금액 미만인 경우에만 허용. 수수료를 비례
* 배분해 환급하고 purchase 잔액(amount/fee/net)을 차감, PARTIAL 취소행 생성.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_PARTCXL.h"
void ACQ_PARTCXL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long feeback;
EXEC SQL BEGIN DECLARE SECTION;
long h_cid, h_pid, h_cxl, h_amt = 0, h_fee = 0, h_fb;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_cxl = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT amount, fee INTO :h_amt, :h_fee FROM purchase
WHERE purchase_id = :h_pid AND status IN ('A', 'M');
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cxl <= 0 || h_cxl >= h_amt) { setl(b, T_RC, 3); FAIL(b); }
/* 수수료 비례 환급분: fee * 취소액 / 원금액 */
feeback = (h_fee * h_cxl) / h_amt;
h_fb = feeback;
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_cid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_cid, :h_pid, 'PARTIAL', :h_cxl, 'PARTCXL', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE purchase
SET amount = amount - :h_cxl,
fee = fee - :h_fb,
net = net - (:h_cxl - :h_fb),
updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) { userlog("ACQ_PARTCXL FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_CANCEL_ID, h_cid);
setl(b, T_AMT1, h_amt - h_cxl); /* 잔여 매입금액 */
setl(b, T_FEE, feeback);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_PARTRFND.h - ac ACQ_PARTRFND (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_AMOUNT( ), T_BIZDATE
* Response fields: T_CANCEL_ID, T_AMT2( ), T_RC(6=)
*/
#ifndef ACQ_PARTRFND_H
#define ACQ_PARTRFND_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long refund_amount; /* 이번 환불 요청액 */
char biz_date[16];
long cancel_id;
long refund_accum; /* 환불 누계 */
} ac_partrfnd_rec_t;
void ACQ_PARTRFND(TPSVCINFO *p);
#endif /* ACQ_PARTRFND_H */

View file

@ -0,0 +1,44 @@
/*
* ACQ_PARTRFND.pgc - ac 모듈 서비스 ACQ_PARTRFND (one service = one file).
* 부분환불: 기존 환불 누계 + 요청액이 매입금액을 넘지 않도록 검증하고
* REFUND 취소행과 음수 원장 분개(PARTRFND)를 함께 기록한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_PARTRFND.h"
void ACQ_PARTRFND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cid, h_pid, h_req, h_orig = 0, h_prev = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_req = 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 SELECT amount INTO :h_orig FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
/* 환불 누계 검증: 이미 환불된 합계 + 이번 요청액 <= 매입금액 */
EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_prev FROM ac_cancel
WHERE purchase_id = :h_pid AND cancel_type = 'REFUND';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_req <= 0 || h_prev + h_req > h_orig) { setl(b, T_RC, 6); FAIL(b); }
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_cid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_cid, :h_pid, 'REFUND', :h_req, 'PARTRFND', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (acdb_insert_ledger(h_pid, "PARTRFND", -h_req, bizdate) < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_cid);
setl(b, T_AMT2, h_prev + h_req); /* 환불 누계 */
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_PROMO.h - ac ACQ_PROMO (copybook / record header).
* .
* Request fields: T_MERCHANT, T_AMOUNT, T_STR1( PRM-*), T_BIZDATE
* Response fields: T_PURCHASE_ID, T_NET(), T_RC(7= )
*/
#ifndef ACQ_PROMO_H
#define ACQ_PROMO_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
long amount;
char promo_code[32]; /* PRM- 접두 행사코드 */
char biz_date[16];
long purchase_id;
} ac_promo_rec_t;
void ACQ_PROMO(TPSVCINFO *p);
#endif /* ACQ_PROMO_H */

View file

@ -0,0 +1,42 @@
/*
* ACQ_PROMO.pgc - ac 모듈 서비스 ACQ_PROMO (one service = one file).
* 프로모션 매입 등록: 행사 코드가 유효하면 수수료 0 의 프로모션 매입을
* 생성한다(분류 PROMO). 정산금 전액 지급, 원장에 행사 분개를 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_PROMO.h"
void ACQ_PROMO(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char promo[32], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_amt = getl(b, T_AMOUNT);
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_STR1, promo, sizeof(promo));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
/* 행사 코드 형식 검증: PRM- 접두 필수 */
if (strncmp(promo, "PRM-", 4) != 0) { setl(b, T_RC, 7); FAIL(b); }
if (h_amt <= 0) { setl(b, T_RC, 3); FAIL(b); }
EXEC SQL SELECT nextval('purchase_seq') INTO :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
/* 수수료 면제: fee 0, net 전액, 분류 PROMO */
EXEC SQL INSERT INTO purchase (purchase_id, merchant_id, amount, fee, net, status, biz_date, channel, category)
VALUES (:h_pid, :h_merch, :h_amt, 0, :h_amt, 'A', :h_bizdate, 'POS', 'PROMO');
if (sqlca.sqlcode < 0) { userlog("ACQ_PROMO FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
if (acdb_insert_ledger(h_pid, "PROMO", h_amt, bizdate) < 0) FAIL(b);
setl(b, T_PURCHASE_ID, h_pid);
setl(b, T_NET, h_amt);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_REBUY.h - ac ACQ_REBUY (copybook / record header).
* .
* Request fields: T_PURCHASE_ID( ), T_BIZDATE
* Response fields: T_ID1( ID), T_FEE, T_NET
*/
#ifndef ACQ_REBUY_H
#define ACQ_REBUY_H
#include "ac_svc.h"
typedef struct {
long orig_purchase_id; /* 취소 원매입 */
char biz_date[16];
long new_purchase_id; /* 재매입 신규 ID */
long fee; /* 현행 MDR 재산출 수수료 */
long net;
} ac_rebuy_rec_t;
void ACQ_REBUY(TPSVCINFO *p);
#endif /* ACQ_REBUY_H */

View file

@ -0,0 +1,42 @@
/*
* ACQ_REBUY.pgc - ac 모듈 서비스 ACQ_REBUY (one service = one file).
* 재매입: 취소(C)된 원매입의 가맹점/금액/채널을 승계해 신규 매입을 생성하고
* 현재 MDR 로 수수료를 재산출, 원장에 REBUY 연결 분개를 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_REBUY.h"
void ACQ_REBUY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long newpid, fee, net, bps = 0;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt = 0;
char h_merch[64], h_chan[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
/* 취소 상태의 원매입만 재매입 대상 */
EXEC SQL SELECT merchant_id, amount, channel INTO :h_merch, :h_amt, :h_chan
FROM purchase WHERE purchase_id = :h_pid AND status = 'C';
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (acdb_merchant_mdr(h_merch, &bps) < 0) FAIL(b);
fee = acq_fee(h_amt, bps);
net = h_amt - fee;
newpid = acdb_next_purchase_id();
if (newpid < 0) FAIL(b);
if (acdb_insert_purchase(newpid, h_merch, h_amt, fee, net, "A", h_chan, bizdate) < 0) FAIL(b);
/* 원매입 → 신매입 연결 분개 */
if (acdb_insert_ledger(h_pid, "REBUY", h_amt, bizdate) < 0) FAIL(b);
setl(b, T_ID1, newpid);
setl(b, T_FEE, fee);
setl(b, T_NET, net);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* ACQ_REMATCH.h - ac ACQ_REMATCH (copybook / record header).
* .
* Request fields: T_PURCHASE_ID
* Response fields: T_ARG1( ), T_ARG2( ), T_MSG, T_RC
*/
#ifndef ACQ_REMATCH_H
#define ACQ_REMATCH_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
long resolved; /* 해소된 미매입 행 수 */
long promoted; /* A→M 전이 건수 */
} ac_rematch_rec_t;
void ACQ_REMATCH(TPSVCINFO *p);
#endif /* ACQ_REMATCH_H */

View file

@ -0,0 +1,43 @@
/*
* ACQ_REMATCH.pgc - ac 모듈 서비스 ACQ_REMATCH (one service = one file).
* 승인미매칭 재처리: 미매입 원장에 남은 건에 유효 승인이 도착했는지 다시
* 확인하고, 승인이 있으면 미매입 행을 걷어내며 매입을 대사완료로 올린다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_REMATCH.h"
void ACQ_REMATCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_appr = 0, h_gone, h_up;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
/* 유효 승인 존재 여부 재확인 */
EXEC SQL SELECT count(*) INTO :h_appr FROM approval
WHERE purchase_id = :h_pid AND approved = true;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_appr == 0) {
Bchg(b, T_MSG, 0, "NO-APPROVAL", 0L);
setl(b, T_RC, 1);
OK(b); /* 승인 미도착: 미매입 유지 */
}
EXEC SQL DELETE FROM ac_unmatch
WHERE purchase_id = :h_pid AND reason NOT LIKE 'CLAIM:%';
if (sqlca.sqlcode < 0) FAIL(b);
h_gone = sqlca.sqlerrd[2];
EXEC SQL UPDATE purchase SET status = 'M', updated_at = now()
WHERE purchase_id = :h_pid AND status = 'A';
if (sqlca.sqlcode < 0) { userlog("ACQ_REMATCH FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
h_up = sqlca.sqlerrd[2];
setl(b, T_ARG1, h_gone); /* 해소된 미매입 행 수 */
setl(b, T_ARG2, h_up); /* 상태 전이 건수 */
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_RESERVE.h - ac ACQ_RESERVE (copybook / record header).
* .
* Request fields: T_MERCHANT, T_AMOUNT, T_BIZDATE()
* Response fields: T_PURCHASE_ID( ID), T_FEE, T_RC(6= )
*/
#ifndef ACQ_RESERVE_H
#define ACQ_RESERVE_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
long amount;
char rsv_date[16]; /* 예약 영업일 */
long purchase_id; /* 예약 매입 ID (status P) */
long fee; /* 선산출 수수료 */
} ac_reserve_rec_t;
void ACQ_RESERVE(TPSVCINFO *p);
#endif /* ACQ_RESERVE_H */

View file

@ -0,0 +1,41 @@
/*
* ACQ_RESERVE.pgc - ac 모듈 서비스 ACQ_RESERVE (one service = one file).
* 매입 예약: 활성(ACTIVE) 가맹점에 대해 미래 영업일 예약 매입(status P)을
* 만들어 둔다. 수수료는 현행 MDR 로 선산출하고 예약 채널(RSV)로 표시.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_RESERVE.h"
void ACQ_RESERVE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt, h_fee, h_net, h_bps = 0;
char h_merch[64], h_mst[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_amt = getl(b, T_AMOUNT);
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (h_amt <= 0) { setl(b, T_RC, 3); FAIL(b); }
/* 활성 가맹점만 예약 허용 + MDR 동시 조회 */
EXEC SQL SELECT status, mdr_bps INTO :h_mst, :h_bps FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (strcmp(h_mst, "ACTIVE") != 0) { setl(b, T_RC, 6); FAIL(b); }
h_fee = acq_fee(h_amt, h_bps);
h_net = h_amt - h_fee;
EXEC SQL SELECT nextval('purchase_seq') INTO :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO purchase (purchase_id, merchant_id, amount, fee, net, status, biz_date, channel)
VALUES (:h_pid, :h_merch, :h_amt, :h_fee, :h_net, 'P', :h_bizdate, 'RSV');
if (sqlca.sqlcode < 0) { userlog("ACQ_RESERVE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_PURCHASE_ID, h_pid);
setl(b, T_FEE, h_fee);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* ACQ_RESERVECXL.h - ac ACQ_RESERVECXL (copybook / record header).
* .
* Request fields: T_PURCHASE_ID( ID)
* Response fields: T_AMOUNT( ), T_RC(4= )
*/
#ifndef ACQ_RESERVECXL_H
#define ACQ_RESERVECXL_H
#include "ac_svc.h"
typedef struct {
long purchase_id; /* 예약(P) 매입 ID */
long amount; /* 취소된 예약 금액 */
} ac_reservecxl_rec_t;
void ACQ_RESERVECXL(TPSVCINFO *p);
#endif /* ACQ_RESERVECXL_H */

View file

@ -0,0 +1,31 @@
/*
* ACQ_RESERVECXL.pgc - ac 모듈 서비스 ACQ_RESERVECXL (one service = one file).
* 매입 예약취소: 아직 접수 전환되지 않은 예약(P) 건만 물리 삭제한다.
* 취소 전 예약 금액을 확보해 응답에 돌려주고 감사 로그를 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_RESERVECXL.h"
void ACQ_RESERVECXL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt = 0, h_del;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT amount INTO :h_amt FROM purchase
WHERE purchase_id = :h_pid AND status = 'P';
if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } /* 예약 아님/전환됨 */
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL DELETE FROM purchase WHERE purchase_id = :h_pid AND status = 'P';
if (sqlca.sqlcode < 0) { userlog("ACQ_RESERVECXL FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
h_del = sqlca.sqlerrd[2];
userlog("ACQ_RESERVECXL: pid=%ld amount=%ld deleted=%ld", h_pid, h_amt, h_del);
setl(b, T_AMOUNT, h_amt);
setl(b, T_RC, h_del > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* ACQ_RESERVEQRY.h - ac ACQ_RESERVEQRY (copybook / record header).
* .
* Request fields: T_MERCHANT
* Response fields: T_COUNT, T_GROSS, T_BIZDATE( ), T_RC
*/
#ifndef ACQ_RESERVEQRY_H
#define ACQ_RESERVEQRY_H
#include "ac_svc.h"
typedef struct {
char merchant_id[64];
long rsv_count;
long rsv_total;
char first_date[16]; /* 가장 빠른 예약일 */
} ac_reserveqry_rec_t;
void ACQ_RESERVEQRY(TPSVCINFO *p);
#endif /* ACQ_RESERVEQRY_H */

View file

@ -0,0 +1,42 @@
/*
* ACQ_RESERVEQRY.pgc - ac 모듈 서비스 ACQ_RESERVEQRY (one service = one file).
* 매입 예약 조회: 가맹점의 대기 중 예약(P)을 예약일 순 커서로 훑어 건수와
* 총액, 가장 빠른 예약일을 알려준다. 예약이 없으면 T_RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
*/
#include "ACQ_RESERVEQRY.h"
void ACQ_RESERVEQRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long cnt = 0, total = 0;
char first_date[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_amt;
char h_merch[64], h_date[16];
EXEC SQL END DECLARE SECTION;
first_date[0] = 0;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
EXEC SQL DECLARE rvq_cur CURSOR FOR
SELECT amount, to_char(biz_date, 'YYYY-MM-DD') FROM purchase
WHERE merchant_id = :h_merch AND status = 'P'
ORDER BY biz_date, purchase_id;
EXEC SQL OPEN rvq_cur;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH rvq_cur INTO :h_amt, :h_date;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rvq_cur; FAIL(b); }
if (cnt == 0) { strncpy(first_date, h_date, sizeof(first_date)-1); first_date[sizeof(first_date)-1] = 0; }
cnt++; total += h_amt;
}
EXEC SQL CLOSE rvq_cur;
if (first_date[0]) Bchg(b, T_BIZDATE, 0, first_date, 0L);
setl(b, T_COUNT, cnt);
setl(b, T_GROSS, total);
setl(b, T_RC, cnt > 0 ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* ACQ_RETURN.h - ac ACQ_RETURN (copybook / record header).
* .
* Request fields: T_PURCHASE_ID, T_REASON, T_BIZDATE
* Response fields: T_CANCEL_ID, T_AMOUNT(), T_RC
*/
#ifndef ACQ_RETURN_H
#define ACQ_RETURN_H
#include "ac_svc.h"
typedef struct {
long purchase_id;
char reason[110]; /* 반송 사유 */
char biz_date[16];
long cancel_id;
long amount; /* 전액 반송 금액 */
} ac_return_rec_t;
void ACQ_RETURN(TPSVCINFO *p);
#endif /* ACQ_RETURN_H */

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