규모확장 Wave B: 전 11모듈 svc 75→133·batch 35→46 (BAIS 온라인 1:1)

- svc 825→1,463 (모듈당 133), batch 385→506 (모듈당 46) — 전부 새 고유 실업무 로직
  + 카피북 헤더, 디스패처 133 advertise, 한글 기능설명(매니페스트 추출)
- BAIS As-Is 대비: 온라인 1,463 vs 1,466(99.8%), 배치 506 vs 503(초과)
- 화면 매니페스트 1,463 재생성
- 검증: build DONE modules=11 batches=506, 12서버 runok, xadmin psc 1,466 AVAIL,
  매입체인 XA 커밋, 신규서비스 실데이터 스모크, 전 코퍼스 .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:55:47 +00:00
parent ea90bef264
commit c62d91b7a7
1526 changed files with 59213 additions and 12 deletions

View file

@ -0,0 +1,45 @@
/*
* py_apprpend_batch.pgc - py 승인대기 리포트 배치 (커서 GROUP BY, XA).
*
* 아직 승인 전인 요청(R) 지급건을 가맹점별로 묶어 대기 건수/요청금액 합을
* 건수 내림차순 커서로 훑어 stdout 리포트로 출력한다 (승인 워크로드 파악).
* Usage: py_apprpend_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
long groups = 0, waiting = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64];
long h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE pap CURSOR FOR
SELECT merchant_id, count(*), coalesce(sum(amount),0)
FROM py_payout WHERE status = 'R'
GROUP BY merchant_id ORDER BY 2 DESC, 1;
EXEC SQL OPEN pap;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pap INTO :h_merch, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pap; tpabort(0); return 1; }
groups++; waiting += h_cnt;
printf(" %s: 대기 %ld건 요청 %ld원\n", h_merch, h_cnt, h_amt);
}
EXEC SQL CLOSE pap;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_apprpend_batch COMMIT: merchants=%ld waiting=%ld\n", groups, waiting);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,47 @@
/*
* py_bankverify_batch.pgc - py 지급파일 전건 대사 배치 (커서 검증, XA).
*
* 생성(G) 상태 파일을 커서로 훑어 파일 헤더의 건수/금액과 실제 편성 지급건의
* 건수/net 합을 비교해 불일치 파일을 리포트한다. 갱신 없이 검증만 수행한다.
* host 변수는 전용 카피북 py_bankverify_cpy.h 를 EXEC SQL INCLUDE.
* Usage: py_bankverify_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
long files = 0, mism = 0;
EXEC SQL INCLUDE py_bankverify_cpy;
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 pbv CURSOR FOR
SELECT file_id, record_count, total_amount FROM py_file WHERE status = 'G' ORDER BY file_id;
EXEC SQL OPEN pbv;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pbv INTO :c_fid, :c_hcnt, :c_hsum;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pbv; tpabort(0); return 1; }
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :c_acnt, :c_asum
FROM py_payout WHERE file_id = :c_fid;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pbv; tpabort(0); return 1; }
files++;
if (c_hcnt != c_acnt || c_hsum != c_asum) {
mism++;
printf(" 파일 %ld 불일치: hdr(%ld/%ld) act(%ld/%ld)\n", c_fid, c_hcnt, c_hsum, c_acnt, c_asum);
}
}
EXEC SQL CLOSE pbv;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_bankverify_batch COMMIT: files=%ld mismatch=%ld\n", files, mism);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,4 @@
/* py_bankverify_cpy.h - py_bankverify_batch 전용 카피북 (host 변수 선언). */
EXEC SQL BEGIN DECLARE SECTION;
long c_fid, c_hcnt, c_hsum, c_acnt, c_asum;
EXEC SQL END DECLARE SECTION;

View file

@ -0,0 +1,44 @@
/*
* py_carryover_batch.pgc - py 미지급 이월 배치 (SELECT 카운트 + UPDATE, XA).
*
* 지정 기준일 이전의 미지급 요청(R) 건 수를 먼저 집계한 뒤, 그 건들의
* biz_date 를 차기 영업일로 일괄 이월한다. ONE XA transaction.
* Usage: py_carryover_batch [CUTOFF-YYYY-MM-DD] [NEXT-YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *cut = (argc > 1) ? argv[1] : "2026-07-19";
const char *next = (argc > 2) ? argv[2] : "2026-07-20";
long moved;
EXEC SQL BEGIN DECLARE SECTION;
char h_cut[16], h_next[16];
long h_cnt;
EXEC SQL END DECLARE SECTION;
strncpy(h_cut, cut, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0;
strncpy(h_next, next, sizeof(h_next)-1); h_next[sizeof(h_next)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT count(*) INTO :h_cnt
FROM py_payout WHERE status = 'R' AND biz_date < :h_cut;
if (sqlca.sqlcode < 0) { fprintf(stderr, "CNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
EXEC SQL UPDATE py_payout SET biz_date = :h_next, updated_at = now()
WHERE status = 'R' AND biz_date < :h_cut;
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPD FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
moved = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_carryover_batch COMMIT: cutoff=%s next=%s candidates=%ld moved=%ld\n", cut, next, h_cnt, moved);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,39 @@
/*
* py_dormscan_batch.pgc - py 휴면 계좌 스캔 배치 (NOT EXISTS UPDATE, XA).
*
* 지정 기준일 이후 지급 이력이 전혀 없는 활성(ACTIVE) 계좌를 NOT EXISTS
* 서브쿼리로 골라 휴면(DORMANT)으로 일괄 전환한다. ONE XA transaction.
* Usage: py_dormscan_batch [SINCE-YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *since = (argc > 1) ? argv[1] : "2026-04-01";
long dormant;
EXEC SQL BEGIN DECLARE SECTION;
char h_since[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_since, since, sizeof(h_since)-1); h_since[sizeof(h_since)-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 UPDATE py_account SET status = 'DORMANT', updated_at = now()
WHERE status = 'ACTIVE'
AND NOT EXISTS (SELECT 1 FROM py_payout p
WHERE p.account_no = py_account.account_no AND p.biz_date >= :h_since);
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
dormant = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_dormscan_batch COMMIT: since=%s dormant=%ld\n", since, dormant);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,41 @@
/*
* py_feecap_batch.pgc - py 수수료 상한 일괄 적용 배치 (INSERT..SELECT + UPDATE, XA).
*
* 미지급(R/A) 건 중 수수료가 상한(인자)을 넘는 건을 대상으로, 초과분을
* py_adjust 에 양수 조정으로 적재한 뒤 fee 를 상한까지 낮추고 net 을 보정한다.
* host 변수는 전용 카피북 py_feecap_cpy.h 를 EXEC SQL INCLUDE.
* Usage: py_feecap_batch [YYYY-MM-DD] [CAP]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL INCLUDE py_feecap_cpy;
strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0;
c_cap = (argc > 2) ? atol(argv[2]) : 5000;
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 INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date)
SELECT nextval('py_adjust_seq'), payout_id, fee - :c_cap, '수수료상한배치', :c_bizdate
FROM py_payout WHERE status IN ('R','A') AND fee > :c_cap;
if (sqlca.sqlcode < 0) { fprintf(stderr, "ADJ FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
EXEC SQL UPDATE py_payout SET net = net + (fee - :c_cap), fee = :c_cap, updated_at = now()
WHERE status IN ('R','A') AND fee > :c_cap;
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPD FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
c_capped = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_feecap_batch COMMIT: bizdate=%s cap=%ld capped=%ld\n", bizdate, c_cap, c_capped);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,5 @@
/* py_feecap_cpy.h - py_feecap_batch 전용 카피북 (host 변수 선언). */
EXEC SQL BEGIN DECLARE SECTION;
char c_bizdate[16];
long c_cap, c_capped;
EXEC SQL END DECLARE SECTION;

View file

@ -0,0 +1,45 @@
/*
* py_garnish_batch.pgc - py 압류/가압류 계좌 지급보류 배치 (커서 + UPDATE, XA).
*
* 압류(SEIZED)/가압류(GARNISH) 상태 계좌를 커서로 훑어 각 계좌의 미지급(R/A)
* 건을 '압류보류'로 보류(H) 전이한다. 계좌별 보류 건수를 리포트한다.
* host 변수는 전용 카피북 py_garnish_cpy.h 를 EXEC SQL INCLUDE.
* Usage: py_garnish_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
long accts = 0, held = 0;
EXEC SQL INCLUDE py_garnish_cpy;
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 pgr CURSOR FOR
SELECT account_no FROM py_account WHERE status IN ('SEIZED','GARNISH') ORDER BY account_no;
EXEC SQL OPEN pgr;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pgr INTO :c_acct;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pgr; tpabort(0); return 1; }
EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '압류보류', updated_at = now()
WHERE account_no = :c_acct AND status IN ('R','A');
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pgr; tpabort(0); return 1; }
c_held = sqlca.sqlerrd[2];
accts++; held += c_held;
if (c_held > 0) printf(" 계좌 %s: %ld건 압류보류\n", c_acct, c_held);
}
EXEC SQL CLOSE pgr;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_garnish_batch COMMIT: accounts=%ld held=%ld\n", accts, held);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,5 @@
/* py_garnish_cpy.h - py_garnish_batch 전용 카피북 (host 변수 선언). */
EXEC SQL BEGIN DECLARE SECTION;
char c_acct[32];
long c_held;
EXEC SQL END DECLARE SECTION;

View file

@ -0,0 +1,47 @@
/*
* py_notifypush_batch.pgc - py 지급완료 푸시 통지 시뮬 배치 (조인 커서, XA).
*
* 당일 지급완료(P) 건을 py_account 조인 커서로 훑어 예금주/은행/금액으로
* 푸시 통지 발송을 stdout 으로 시뮬레이션한다 (실발송 없음). 은행별 집계 없이
* 건별 발송. host 변수는 전용 카피북 py_notifypush_cpy.h 를 EXEC SQL INCLUDE.
* Usage: py_notifypush_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long pushed = 0, total = 0;
EXEC SQL INCLUDE py_notifypush_cpy;
strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_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 pnp CURSOR FOR
SELECT p.payout_id, p.net, a.holder, a.bank_code
FROM py_payout p JOIN py_account a ON p.account_no = a.account_no
WHERE p.biz_date = :c_bizdate AND p.status = 'P'
ORDER BY a.bank_code, p.payout_id;
EXEC SQL OPEN pnp;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pnp INTO :c_pid, :c_net, :c_holder, :c_bank;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pnp; tpabort(0); return 1; }
printf(" [PUSH시뮬] %s(%s) %ld원 지급알림 (payout=%ld)\n", c_holder, c_bank, c_net, c_pid);
pushed++; total += c_net;
}
EXEC SQL CLOSE pnp;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_notifypush_batch COMMIT: bizdate=%s pushed=%ld total=%ld\n", bizdate, pushed, total);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,7 @@
/* py_notifypush_cpy.h - py_notifypush_batch 전용 카피북 (host 변수 선언). */
EXEC SQL BEGIN DECLARE SECTION;
char c_bizdate[16];
char c_holder[64];
char c_bank[8];
long c_pid, c_net;
EXEC SQL END DECLARE SECTION;

View file

@ -0,0 +1,41 @@
/*
* py_pendaging_batch.pgc - py 미지급 경과일 집계 배치 (CASE 단일 집계, XA).
*
* 미지급(R/A/H) 건을 기준일 대비 경과일수로 당일/3일내/장기(초과) 3구간으로
* CASE 분류해 구간별 건수를 한 번의 집계 쿼리로 산출해 리포트한다.
* Usage: py_pendaging_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_fresh, h_mid, h_old;
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT
coalesce(sum(CASE WHEN :h_bizdate::date - biz_date <= 0 THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN :h_bizdate::date - biz_date BETWEEN 1 AND 3 THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN :h_bizdate::date - biz_date > 3 THEN 1 ELSE 0 END),0)
INTO :h_fresh, :h_mid, :h_old
FROM py_payout WHERE status IN ('R','A','H');
if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_pendaging_batch COMMIT: base=%s 당일=%ld 3일내=%ld 장기=%ld\n",
bizdate, h_fresh, h_mid, h_old);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,38 @@
/*
* py_requeue_batch.pgc - py 실패 재처리 일괄 적재 배치 (단일 UPDATE, XA).
*
* 당일 반려(J) 건 중 재시도 여력(retry_count<3)이 있는 건을 요청(R)으로
* 되돌리며 retry_count 를 +1 해 재처리 큐에 일괄 적재한다. ONE XA txn.
* Usage: py_requeue_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long requeued;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL UPDATE py_payout
SET status = 'R', retry_count = retry_count + 1, reject_reason = '', updated_at = now()
WHERE biz_date = :h_bizdate AND status = 'J' AND retry_count < 3;
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
requeued = sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_requeue_batch COMMIT: bizdate=%s requeued=%ld\n", bizdate, requeued);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,50 @@
/*
* py_statmonth_batch.pgc - py 월별 지급 집계 리포트 배치 (커서 GROUP BY, XA).
*
* 지정 기간의 지급건을 'YYYY-MM' 월 단위로 묶어 월별 건수/net 합을 커서로
* 훑어 stdout 리포트로 출력한다 (갱신 없음, 읽기 전용 리포트).
* Usage: py_statmonth_batch [FROM-YYYY-MM-DD] [TO-YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *from = (argc > 1) ? argv[1] : "2026-01-01";
const char *to = (argc > 2) ? argv[2] : "2026-12-31";
long months = 0, grand = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_from[16], h_to[16], h_mon[16];
long h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
strncpy(h_from, from, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0;
strncpy(h_to, to, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE psm CURSOR FOR
SELECT to_char(biz_date, 'YYYY-MM'), count(*), coalesce(sum(net),0)
FROM py_payout WHERE biz_date BETWEEN :h_from AND :h_to
GROUP BY 1 ORDER BY 1;
EXEC SQL OPEN psm;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH psm INTO :h_mon, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE psm; tpabort(0); return 1; }
months++; grand += h_amt;
printf(" %s: %ld건 %ld원\n", h_mon, h_cnt, h_amt);
}
EXEC SQL CLOSE psm;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_statmonth_batch COMMIT: months=%ld grand=%ld\n", months, grand);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,50 @@
/*
* py_xferconf_batch.pgc - py 이체 기표 일괄 확정 배치 (커서 + INSERT, XA).
*
* 당일 지급완료(P) 이면서 XFER 기표가 아직 없는 건을 NOT EXISTS 커서로 뽑아
* py_ledger 에 net 만큼 XFER 기표를 일괄 생성한다. ONE XA txn.
* host 변수는 전용 카피북 py_xferconf_cpy.h 를 EXEC SQL INCLUDE.
* Usage: py_xferconf_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long posted = 0;
EXEC SQL INCLUDE py_xferconf_cpy;
strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_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 pxc CURSOR FOR
SELECT payout_id, net FROM py_payout p
WHERE p.biz_date = :c_bizdate AND p.status = 'P'
AND NOT EXISTS (SELECT 1 FROM py_ledger l
WHERE l.payout_id = p.payout_id AND l.entry_type = 'XFER')
ORDER BY payout_id;
EXEC SQL OPEN pxc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pxc INTO :c_pid, :c_net;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pxc; tpabort(0); return 1; }
EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
VALUES (nextval('py_ledger_seq'), :c_pid, 'XFER', :c_net, :c_bizdate);
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pxc; tpabort(0); return 1; }
posted++;
}
EXEC SQL CLOSE pxc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> py_xferconf_batch COMMIT: bizdate=%s posted=%ld\n", bizdate, posted);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,5 @@
/* py_xferconf_cpy.h - py_xferconf_batch 전용 카피북 (host 변수 선언). */
EXEC SQL BEGIN DECLARE SECTION;
char c_bizdate[16];
long c_pid, c_net;
EXEC SQL END DECLARE SECTION;

View file

@ -2,7 +2,7 @@
* py_svr.pgc - py (지급) MODULE SERVER (thin dispatcher).
*
* The legacy "one module server, many services" pattern: this binary owns NO
* business logic. Each of py's 75 online services now lives in its own file
* business logic. Each of py's 133 online services now lives in its own file
* (app/src/py/svc/<SVCNAME>.pgc) with its own copybook header
* (app/src/py/svc/<SVCNAME>.h); those objects are archived into libpysvc.a and
* linked into this server. Here we only:
@ -94,6 +94,64 @@
#include "PAY_LEDGERSUM.h"
#include "PAY_ACCTLIST.h"
#include "PAY_NETRECALC.h"
#include "PAY_XFERFILE.h"
#include "PAY_FILECONF.h"
#include "PAY_FILECANCEL.h"
#include "PAY_BANKVERIFY.h"
#include "PAY_XFERTYPE.h"
#include "PAY_NOTIFYPUSH.h"
#include "PAY_NOTIFYKAKAO.h"
#include "PAY_NOTIFYALL.h"
#include "PAY_NOTIFYFAIL.h"
#include "PAY_NOTIFYRESEND.h"
#include "PAY_ACCTVERIFY.h"
#include "PAY_ACCTFREEZE.h"
#include "PAY_ACCTREOPEN.h"
#include "PAY_REPAYACCT.h"
#include "PAY_HOLDBYRSN.h"
#include "PAY_HOLDCLEAR.h"
#include "PAY_HOLDEXT.h"
#include "PAY_HOLDNOTE.h"
#include "PAY_SEIZELIST.h"
#include "PAY_SEIZEAMT.h"
#include "PAY_OFFSETAPPLY.h"
#include "PAY_GARNISH.h"
#include "PAY_ADVGRANT.h"
#include "PAY_URGENTQ.h"
#include "PAY_RESVLIST.h"
#include "PAY_RESVMODIFY.h"
#include "PAY_SPLITEVEN.h"
#include "PAY_CARRYOVER.h"
#include "PAY_COMBINE.h"
#include "PAY_ROLLFWD.h"
#include "PAY_FEEFIXED.h"
#include "PAY_FEECAP.h"
#include "PAY_FEEREFUND.h"
#include "PAY_FEESUM.h"
#include "PAY_FAILRSN.h"
#include "PAY_REQUEUE.h"
#include "PAY_FAILCLEAR.h"
#include "PAY_RETRYONE.h"
#include "PAY_XFERDIFF.h"
#include "PAY_XFERUNMATCH.h"
#include "PAY_VOIDONE.h"
#include "PAY_RECLAIM.h"
#include "PAY_CHARGEBACK.h"
#include "PAY_LIMITSET.h"
#include "PAY_LIMITLIST.h"
#include "PAY_LIMITWARN.h"
#include "PAY_APPRHIST.h"
#include "PAY_APPRCANCEL.h"
#include "PAY_CLOSEDSTOP.h"
#include "PAY_DORMLIST.h"
#include "PAY_REACTIVATE.h"
#include "PAY_STATSTATUS.h"
#include "PAY_STATAMT.h"
#include "PAY_STATFEE.h"
#include "PAY_STATMONTH.h"
#include "PAY_PENDLIST.h"
#include "PAY_APPRPEND.h"
#include "PAY_XFERCONF.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
@ -135,6 +193,64 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"PAY_STATRANGE", PAY_STATRANGE}, {"PAY_SCHEDCANCEL", PAY_SCHEDCANCEL},
{"PAY_LEDGERSUM", PAY_LEDGERSUM}, {"PAY_ACCTLIST", PAY_ACCTLIST},
{"PAY_NETRECALC", PAY_NETRECALC},
{"PAY_XFERFILE", PAY_XFERFILE},
{"PAY_FILECONF", PAY_FILECONF},
{"PAY_FILECANCEL", PAY_FILECANCEL},
{"PAY_BANKVERIFY", PAY_BANKVERIFY},
{"PAY_XFERTYPE", PAY_XFERTYPE},
{"PAY_NOTIFYPUSH", PAY_NOTIFYPUSH},
{"PAY_NOTIFYKAKAO", PAY_NOTIFYKAKAO},
{"PAY_NOTIFYALL", PAY_NOTIFYALL},
{"PAY_NOTIFYFAIL", PAY_NOTIFYFAIL},
{"PAY_NOTIFYRESEND", PAY_NOTIFYRESEND},
{"PAY_ACCTVERIFY", PAY_ACCTVERIFY},
{"PAY_ACCTFREEZE", PAY_ACCTFREEZE},
{"PAY_ACCTREOPEN", PAY_ACCTREOPEN},
{"PAY_REPAYACCT", PAY_REPAYACCT},
{"PAY_HOLDBYRSN", PAY_HOLDBYRSN},
{"PAY_HOLDCLEAR", PAY_HOLDCLEAR},
{"PAY_HOLDEXT", PAY_HOLDEXT},
{"PAY_HOLDNOTE", PAY_HOLDNOTE},
{"PAY_SEIZELIST", PAY_SEIZELIST},
{"PAY_SEIZEAMT", PAY_SEIZEAMT},
{"PAY_OFFSETAPPLY", PAY_OFFSETAPPLY},
{"PAY_GARNISH", PAY_GARNISH},
{"PAY_ADVGRANT", PAY_ADVGRANT},
{"PAY_URGENTQ", PAY_URGENTQ},
{"PAY_RESVLIST", PAY_RESVLIST},
{"PAY_RESVMODIFY", PAY_RESVMODIFY},
{"PAY_SPLITEVEN", PAY_SPLITEVEN},
{"PAY_CARRYOVER", PAY_CARRYOVER},
{"PAY_COMBINE", PAY_COMBINE},
{"PAY_ROLLFWD", PAY_ROLLFWD},
{"PAY_FEEFIXED", PAY_FEEFIXED},
{"PAY_FEECAP", PAY_FEECAP},
{"PAY_FEEREFUND", PAY_FEEREFUND},
{"PAY_FEESUM", PAY_FEESUM},
{"PAY_FAILRSN", PAY_FAILRSN},
{"PAY_REQUEUE", PAY_REQUEUE},
{"PAY_FAILCLEAR", PAY_FAILCLEAR},
{"PAY_RETRYONE", PAY_RETRYONE},
{"PAY_XFERDIFF", PAY_XFERDIFF},
{"PAY_XFERUNMATCH", PAY_XFERUNMATCH},
{"PAY_VOIDONE", PAY_VOIDONE},
{"PAY_RECLAIM", PAY_RECLAIM},
{"PAY_CHARGEBACK", PAY_CHARGEBACK},
{"PAY_LIMITSET", PAY_LIMITSET},
{"PAY_LIMITLIST", PAY_LIMITLIST},
{"PAY_LIMITWARN", PAY_LIMITWARN},
{"PAY_APPRHIST", PAY_APPRHIST},
{"PAY_APPRCANCEL", PAY_APPRCANCEL},
{"PAY_CLOSEDSTOP", PAY_CLOSEDSTOP},
{"PAY_DORMLIST", PAY_DORMLIST},
{"PAY_REACTIVATE", PAY_REACTIVATE},
{"PAY_STATSTATUS", PAY_STATSTATUS},
{"PAY_STATAMT", PAY_STATAMT},
{"PAY_STATFEE", PAY_STATFEE},
{"PAY_STATMONTH", PAY_STATMONTH},
{"PAY_PENDLIST", PAY_PENDLIST},
{"PAY_APPRPEND", PAY_APPRPEND},
{"PAY_XFERCONF", PAY_XFERCONF},
{NULL, NULL}
};

View file

@ -0,0 +1,20 @@
/*
* PAY_ACCTFREEZE.h - py PAY_ACCTFREEZE (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_ACCTFREEZE.pgc get/set .
*/
#ifndef PAY_ACCTFREEZE_H
#define PAY_ACCTFREEZE_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_acctfreeze_rec_t;
void PAY_ACCTFREEZE(TPSVCINFO *p);
#endif /* PAY_ACCTFREEZE_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_ACCTFREEZE.pgc - py 모듈 서비스 PAY_ACCTFREEZE (one service = one file).
* 지급계좌 동결: 활성(ACTIVE) 계좌만 FROZEN 으로 전환한다. 먼저 해당 계좌의
* 미지급(R/A) 건수를 조회해 함께 회신하고, 동결 대상이 아니면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_ACCTFREEZE.h"
void PAY_ACCTFREEZE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32];
long h_pend;
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
EXEC SQL SELECT count(*) INTO :h_pend
FROM py_payout WHERE account_no = :h_acct AND status IN ('R','A');
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE py_account SET status = 'FROZEN', updated_at = now()
WHERE account_no = :h_acct AND status = 'ACTIVE';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) {
setl(b, T_RC, 1);
Bchg(b, T_MSG, 0, "동결 대상 아님(활성계좌 아님)", 0L);
FAIL(b);
}
setl(b, T_RC, 0);
setl(b, T_COUNT, h_pend);
Bchg(b, T_STATUS, 0, "FROZEN", 0L);
userlog("PAY_ACCTFREEZE acct=%s pending=%ld", h_acct, h_pend);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_ACCTREOPEN.h - py PAY_ACCTREOPEN (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_ACCTREOPEN.pgc get/set .
*/
#ifndef PAY_ACCTREOPEN_H
#define PAY_ACCTREOPEN_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_acctreopen_rec_t;
void PAY_ACCTREOPEN(TPSVCINFO *p);
#endif /* PAY_ACCTREOPEN_H */

View file

@ -0,0 +1,36 @@
/*
* PAY_ACCTREOPEN.pgc - py 모듈 서비스 PAY_ACCTREOPEN (one service = one file).
* 동결 해제: 동결(FROZEN) 계좌를 ACTIVE 로 복구하고, 동결 사유로 보류(H)됐던
* 그 계좌의 지급건을 승인(A)으로 되돌린다. 동결 계좌가 아니면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_ACCTREOPEN.h"
void PAY_ACCTREOPEN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32];
long h_rel;
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
EXEC SQL UPDATE py_account SET status = 'ACTIVE', updated_at = now()
WHERE account_no = :h_acct AND status = 'FROZEN';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) {
setl(b, T_RC, 1);
Bchg(b, T_MSG, 0, "동결 계좌 아님", 0L);
FAIL(b);
}
EXEC SQL UPDATE py_payout SET status = 'A', reject_reason = '', updated_at = now()
WHERE account_no = :h_acct AND status = 'H' AND reject_reason LIKE '동결%';
if (sqlca.sqlcode < 0) FAIL(b);
h_rel = sqlca.sqlerrd[2];
setl(b, T_RC, 0);
setl(b, T_COUNT, h_rel);
Bchg(b, T_STATUS, 0, "ACTIVE", 0L);
userlog("PAY_ACCTREOPEN acct=%s released=%ld", h_acct, h_rel);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_ACCTVERIFY.h - py PAY_ACCTVERIFY (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_ACCTVERIFY.pgc get/set .
*/
#ifndef PAY_ACCTVERIFY_H
#define PAY_ACCTVERIFY_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_acctverify_rec_t;
void PAY_ACCTVERIFY(TPSVCINFO *p);
#endif /* PAY_ACCTVERIFY_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_ACCTVERIFY.pgc - py 모듈 서비스 PAY_ACCTVERIFY (one service = one file).
* 지급계좌 검증: 계좌의 상태/예금주/잔액을 조회하고, 요청에 예금주가 주어지면
* 일치 여부까지 확인한다. 미등록/불일치는 사유코드로 구분해 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_ACCTVERIFY.h"
void PAY_ACCTVERIFY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char inh[64];
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32], h_st[16], h_holder[64];
long h_bal;
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
gets_(b, T_STR1, inh, sizeof(inh));
EXEC SQL SELECT status, holder, balance INTO :h_st, :h_holder, :h_bal
FROM py_account WHERE account_no = :h_acct;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "미등록 계좌", 0L); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (inh[0] != 0 && strcmp(inh, h_holder) != 0) {
setl(b, T_RC, 1);
Bchg(b, T_MSG, 0, "예금주 불일치", 0L);
FAIL(b);
}
setl(b, T_RC, 0);
setl(b, T_AMT1, h_bal);
Bchg(b, T_STATUS, 0, h_st, 0L);
Bchg(b, T_STR2, 0, h_holder, 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_ADVGRANT.h - py PAY_ADVGRANT (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_ADVGRANT.pgc get/set .
*/
#ifndef PAY_ADVGRANT_H
#define PAY_ADVGRANT_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_advgrant_rec_t;
void PAY_ADVGRANT(TPSVCINFO *p);
#endif /* PAY_ADVGRANT_H */

View file

@ -0,0 +1,32 @@
/*
* PAY_ADVGRANT.pgc - py 모듈 서비스 PAY_ADVGRANT (one service = one file).
* 선지급 승인: 선지급(ADVANCE) 채널의 요청(R) 건만 승인(A)으로 전이한다.
* 채널/상태를 함께 가드하며, 대상이 아니면 사유코드로 구분해 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_ADVGRANT.h"
void PAY_ADVGRANT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt;
char h_st[4], h_chan[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT status, channel, amount INTO :h_st, :h_chan, :h_amt
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (strcmp(h_chan, "ADVANCE") != 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "선지급 건 아님", 0L); FAIL(b); }
if (h_st[0] != 'R') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "요청 상태만 승인 가능", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET status = 'A', updated_at = now() WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_AMOUNT, h_amt);
Bchg(b, T_STATUS, 0, "A", 0L);
userlog("PAY_ADVGRANT payout=%ld amt=%ld", h_pid, h_amt);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_APPRCANCEL.h - py PAY_APPRCANCEL (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_APPRCANCEL.pgc get/set .
*/
#ifndef PAY_APPRCANCEL_H
#define PAY_APPRCANCEL_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_apprcancel_rec_t;
void PAY_APPRCANCEL(TPSVCINFO *p);
#endif /* PAY_APPRCANCEL_H */

View file

@ -0,0 +1,28 @@
/*
* PAY_APPRCANCEL.pgc - py 모듈 서비스 PAY_APPRCANCEL (one service = one file).
* 지급 승인 취소: 아직 파일에 편성되지 않은 승인(A) 건을 요청(R)으로 되돌린다.
* 파일편성(F)/지급완료(P) 이후에는 승인 취소가 불가하다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_APPRCANCEL.h"
void PAY_APPRCANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long reverted;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL UPDATE py_payout SET status = 'R', updated_at = now()
WHERE payout_id = :h_pid AND status = 'A';
if (sqlca.sqlcode < 0) FAIL(b);
reverted = sqlca.sqlerrd[2];
if (reverted == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "승인 상태 건만 취소 가능", 0L); FAIL(b); }
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "R", 0L);
userlog("PAY_APPRCANCEL payout=%ld -> R", h_pid);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_APPRHIST.h - py PAY_APPRHIST (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_APPRHIST.pgc get/set .
*/
#ifndef PAY_APPRHIST_H
#define PAY_APPRHIST_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_apprhist_rec_t;
void PAY_APPRHIST(TPSVCINFO *p);
#endif /* PAY_APPRHIST_H */

View file

@ -0,0 +1,41 @@
/*
* PAY_APPRHIST.pgc - py 모듈 서비스 PAY_APPRHIST (one service = one file).
* 지급 승인/원장 이력: 지정 지급건의 py_ledger 기표를 시간순 커서로 훑어
* 기표유형/금액/일자를 occurrence 로 회신한다 (승인·이체·환입 흐름 추적).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_APPRHIST.h"
void PAY_APPRHIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long rows = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt;
char h_type[16], h_date[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL DECLARE cur_apprhist CURSOR FOR
SELECT entry_type, amount, CAST(biz_date AS text)
FROM py_ledger WHERE payout_id = :h_pid ORDER BY ledger_id;
EXEC SQL OPEN cur_apprhist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_apprhist INTO :h_type, :h_amt, :h_date;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_apprhist; FAIL(b); }
rows++;
if (i < 12) {
Bchg(b, T_STR3, i, h_type, 0L);
Bchg(b, T_AMT1, i, (char *)&h_amt, 0L);
Bchg(b, T_STR4, i, h_date, 0L);
i++;
}
}
EXEC SQL CLOSE cur_apprhist;
setl(b, T_COUNT, rows);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_APPRPEND.h - py PAY_APPRPEND (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_APPRPEND.pgc get/set .
*/
#ifndef PAY_APPRPEND_H
#define PAY_APPRPEND_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_apprpend_rec_t;
void PAY_APPRPEND(TPSVCINFO *p);
#endif /* PAY_APPRPEND_H */

View file

@ -0,0 +1,43 @@
/*
* PAY_APPRPEND.pgc - py 모듈 서비스 PAY_APPRPEND (one service = one file).
* 승인 대기 집계: 아직 승인 전인 요청(R) 지급건을 가맹점별로 묶어 대기 건수/
* 요청금액 합을 건수 내림차순 occurrence 로 회신한다 (승인 워크로드 파악).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_APPRPEND.h"
void PAY_APPRPEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long grp = 0, waiting = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64];
long h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
EXEC SQL DECLARE cur_apprpend CURSOR FOR
SELECT merchant_id, count(*), coalesce(sum(amount),0)
FROM py_payout WHERE status = 'R'
GROUP BY merchant_id ORDER BY 2 DESC, 1;
EXEC SQL OPEN cur_apprpend;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_apprpend INTO :h_merch, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_apprpend; FAIL(b); }
grp++;
waiting += h_cnt;
if (i < 10) {
Bchg(b, T_STR2, i, h_merch, 0L);
Bchg(b, T_ARG3, i, (char *)&h_cnt, 0L);
Bchg(b, T_AMT1, i, (char *)&h_amt, 0L);
i++;
}
}
EXEC SQL CLOSE cur_apprpend;
setl(b, T_COUNT, grp);
setl(b, T_ARG2, waiting);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_BANKVERIFY.h - py PAY_BANKVERIFY (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_BANKVERIFY.pgc get/set .
*/
#ifndef PAY_BANKVERIFY_H
#define PAY_BANKVERIFY_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_bankverify_rec_t;
void PAY_BANKVERIFY(TPSVCINFO *p);
#endif /* PAY_BANKVERIFY_H */

View file

@ -0,0 +1,37 @@
/*
* PAY_BANKVERIFY.pgc - py 모듈 서비스 PAY_BANKVERIFY (one service = one file).
* 지급파일 검증: 파일 헤더(record_count/total_amount)와 실제 편성 지급건의
* 건수/net 합을 대사해 일치 여부와 금액 차액(T_DELTA)을 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_BANKVERIFY.h"
void PAY_BANKVERIFY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_fid, h_hcnt, h_hsum, h_acnt, h_asum;
EXEC SQL END DECLARE SECTION;
h_fid = getl(b, T_ID2);
EXEC SQL SELECT record_count, total_amount INTO :h_hcnt, :h_hsum
FROM py_file WHERE file_id = :h_fid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_acnt, :h_asum
FROM py_payout WHERE file_id = :h_fid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_AMT1, h_hsum);
setl(b, T_AMT2, h_asum);
setl(b, T_COUNT, h_acnt);
setl(b, T_DELTA, h_hsum - h_asum);
if (h_hcnt == h_acnt && h_hsum == h_asum) {
setl(b, T_RC, 0);
Bchg(b, T_MSG, 0, "파일 대사 일치", 0L);
} else {
setl(b, T_RC, 1);
Bchg(b, T_MSG, 0, "파일 대사 불일치", 0L);
userlog("PAY_BANKVERIFY 불일치 file=%ld hcnt=%ld acnt=%ld", h_fid, h_hcnt, h_acnt);
}
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_CARRYOVER.h - py PAY_CARRYOVER (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_CARRYOVER.pgc get/set .
*/
#ifndef PAY_CARRYOVER_H
#define PAY_CARRYOVER_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_carryover_rec_t;
void PAY_CARRYOVER(TPSVCINFO *p);
#endif /* PAY_CARRYOVER_H */

View file

@ -0,0 +1,40 @@
/*
* PAY_CARRYOVER.pgc - py 모듈 서비스 PAY_CARRYOVER (one service = one file).
* 지급 이월(단건): 미지급(R/A) 건의 지급기준일(biz_date)을 지정 차기일자로
* 이월하고, 이월 사유를 py_adjust 에 0원 조정으로 증적으로 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_CARRYOVER.h"
void PAY_CARRYOVER(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char nextdate[16], reason[96];
long adj;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_st[4], h_next[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
gets_(b, T_KEY2, nextdate, sizeof(nextdate));
gets_(b, T_REASON, reason, sizeof(reason));
if (nextdate[0] == 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "이월일자 미지정", 0L); FAIL(b); }
if (reason[0] == 0) strcpy(reason, "지급이월");
strncpy(h_next, nextdate, sizeof(h_next)-1); h_next[sizeof(h_next)-1] = 0;
EXEC SQL SELECT status INTO :h_st FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'R' && h_st[0] != 'A') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "미지급 건만 이월 가능", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET biz_date = :h_next, updated_at = now() WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
adj = pydb_insert_adjust(h_pid, 0, reason, nextdate);
if (adj < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ADJ, adj);
Bchg(b, T_STR1, 0, nextdate, 0L);
userlog("PAY_CARRYOVER id=%ld to=%s", h_pid, nextdate);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_CHARGEBACK.h - py PAY_CHARGEBACK (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_CHARGEBACK.pgc get/set .
*/
#ifndef PAY_CHARGEBACK_H
#define PAY_CHARGEBACK_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_chargeback_rec_t;
void PAY_CHARGEBACK(TPSVCINFO *p);
#endif /* PAY_CHARGEBACK_H */

View file

@ -0,0 +1,36 @@
/*
* PAY_CHARGEBACK.pgc - py 모듈 서비스 PAY_CHARGEBACK (one service = one file).
* 지급 되물림: 분쟁 등으로 지급완료(P) 건을 되물릴 때, 원건은 보존한 채
* 음수 금액의 상계 지급건(CHGBK 채널)을 신규 생성해 상쇄 처리한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_CHARGEBACK.h"
void PAY_CHARGEBACK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long newid, neg;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
char h_merch[64], h_acct[32], h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT merchant_id, account_no, net, status
INTO :h_merch, :h_acct, :h_net, :h_st FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "지급완료 건만 되물림 가능", 0L); FAIL(b); }
neg = -h_net;
newid = pydb_next_payout_id();
if (newid < 0) FAIL(b);
if (pydb_insert_payout(newid, h_merch, h_acct, neg, 0, neg, "R", "CHGBK", bizdate) < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ID2, newid);
setl(b, T_NET, neg);
userlog("PAY_CHARGEBACK src=%ld offset=%ld net=%ld", h_pid, newid, neg);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_CLOSEDSTOP.h - py PAY_CLOSEDSTOP (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_CLOSEDSTOP.pgc get/set .
*/
#ifndef PAY_CLOSEDSTOP_H
#define PAY_CLOSEDSTOP_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_closedstop_rec_t;
void PAY_CLOSEDSTOP(TPSVCINFO *p);
#endif /* PAY_CLOSEDSTOP_H */

View file

@ -0,0 +1,32 @@
/*
* PAY_CLOSEDSTOP.pgc - py 모듈 서비스 PAY_CLOSEDSTOP (one service = one file).
* 폐업 가맹점 지급정지: 가맹점 상태가 CLOSED 인 가맹점의 미지급(R/A) 건을
* 모두 보류(H)+'폐업정지' 사유로 전이한다. 지급망 유출을 사전 차단한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_CLOSEDSTOP.h"
void PAY_CLOSEDSTOP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long stopped;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64];
char h_mst[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
EXEC SQL SELECT status INTO :h_mst FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (strcmp(h_mst, "CLOSED") != 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "폐업 가맹점 아님", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '폐업정지', updated_at = now()
WHERE merchant_id = :h_merch AND status IN ('R','A');
if (sqlca.sqlcode < 0) FAIL(b);
stopped = sqlca.sqlerrd[2];
setl(b, T_RC, 0);
setl(b, T_COUNT, stopped);
userlog("PAY_CLOSEDSTOP merch=%s stopped=%ld", h_merch, stopped);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_COMBINE.h - py PAY_COMBINE (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_COMBINE.pgc get/set .
*/
#ifndef PAY_COMBINE_H
#define PAY_COMBINE_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_combine_rec_t;
void PAY_COMBINE(TPSVCINFO *p);
#endif /* PAY_COMBINE_H */

View file

@ -0,0 +1,42 @@
/*
* PAY_COMBINE.pgc - py 모듈 서비스 PAY_COMBINE (one service = one file).
* 지급 합산: 같은 계좌의 당일 요청(R) 건들을 하나로 합산 지급한다. 합계
* 금액으로 신규 합산건(BULK)을 만들고 원건들은 취소(C) 처리한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_COMBINE.h"
void PAY_COMBINE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long newid;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32], h_bizdate[16], h_merch[64];
long h_cnt, h_sum;
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(max(merchant_id),'')
INTO :h_cnt, :h_sum, :h_merch
FROM py_payout WHERE account_no = :h_acct AND biz_date = :h_bizdate AND status = 'R';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt < 2) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "합산 대상(2건 이상) 없음", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET status = 'C', updated_at = now()
WHERE account_no = :h_acct AND biz_date = :h_bizdate AND status = 'R';
if (sqlca.sqlcode < 0) FAIL(b);
newid = pydb_next_payout_id();
if (newid < 0) FAIL(b);
if (pydb_insert_payout(newid, h_merch, h_acct, h_sum, 0, h_sum, "R", "BULK", bizdate) < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ID2, newid);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
userlog("PAY_COMBINE acct=%s merged=%ld sum=%ld new=%ld", h_acct, h_cnt, h_sum, newid);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_DORMLIST.h - py PAY_DORMLIST (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_DORMLIST.pgc get/set .
*/
#ifndef PAY_DORMLIST_H
#define PAY_DORMLIST_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_dormlist_rec_t;
void PAY_DORMLIST(TPSVCINFO *p);
#endif /* PAY_DORMLIST_H */

View file

@ -0,0 +1,44 @@
/*
* PAY_DORMLIST.pgc - py 모듈 서비스 PAY_DORMLIST (one service = one file).
* 휴면 계좌 목록: 지정 기준일 이후 지급 이력이 전혀 없는 활성(ACTIVE) 계좌를
* NOT EXISTS 커서로 뽑아 계좌/예금주/잔액을 occurrence 로 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_DORMLIST.h"
void PAY_DORMLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long n = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_since[16], h_acct[32], h_holder[64];
long h_bal;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_since, sizeof(h_since));
EXEC SQL DECLARE cur_dormlist CURSOR FOR
SELECT a.account_no, a.holder, a.balance FROM py_account a
WHERE a.status = 'ACTIVE'
AND NOT EXISTS (SELECT 1 FROM py_payout p
WHERE p.account_no = a.account_no AND p.biz_date >= :h_since)
ORDER BY a.account_no;
EXEC SQL OPEN cur_dormlist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_dormlist INTO :h_acct, :h_holder, :h_bal;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_dormlist; FAIL(b); }
n++;
if (i < 10) {
Bchg(b, T_STR5, i, h_acct, 0L);
Bchg(b, T_STR6, i, h_holder, 0L);
Bchg(b, T_AMT1, i, (char *)&h_bal, 0L);
i++;
}
}
EXEC SQL CLOSE cur_dormlist;
setl(b, T_COUNT, n);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FAILCLEAR.h - py PAY_FAILCLEAR (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FAILCLEAR.pgc get/set .
*/
#ifndef PAY_FAILCLEAR_H
#define PAY_FAILCLEAR_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_failclear_rec_t;
void PAY_FAILCLEAR(TPSVCINFO *p);
#endif /* PAY_FAILCLEAR_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_FAILCLEAR.pgc - py 모듈 서비스 PAY_FAILCLEAR (one service = one file).
* 실패 종결: 재처리 불가한 반려(J) 단건을 취소(C)로 종결하고 종결 사유를
* reject_reason 에 남긴다. 반려 상태가 아니면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FAILCLEAR.h"
void PAY_FAILCLEAR(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long payout_id = getl(b, T_ID1);
char st[4], reason[128];
gets_(b, T_REASON, reason, sizeof(reason));
if (reason[0] == 0) strcpy(reason, "재처리불가종결");
if (pydb_get_status(payout_id, st) < 0) FAIL(b);
if (st[0] != 'J') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "반려 건만 종결 가능", 0L); FAIL(b); }
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = payout_id;
char h_rsn[128];
EXEC SQL END DECLARE SECTION;
strncpy(h_rsn, reason, sizeof(h_rsn)-1); h_rsn[sizeof(h_rsn)-1] = 0;
EXEC SQL UPDATE py_payout SET status = 'C', reject_reason = :h_rsn, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
}
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "C", 0L);
Bchg(b, T_REASON, 0, reason, 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FAILRSN.h - py PAY_FAILRSN (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FAILRSN.pgc get/set .
*/
#ifndef PAY_FAILRSN_H
#define PAY_FAILRSN_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_failrsn_rec_t;
void PAY_FAILRSN(TPSVCINFO *p);
#endif /* PAY_FAILRSN_H */

View file

@ -0,0 +1,42 @@
/*
* PAY_FAILRSN.pgc - py 모듈 서비스 PAY_FAILRSN (one service = one file).
* 실패 재시도횟수별 분포: 당일 반려(J) 건을 retry_count 별 커서로 건수/net
* 집계해 재시도 횟수 오름차순 occurrence 로 회신한다 (재처리 난이도 파악).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FAILRSN.h"
void PAY_FAILRSN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long buckets = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_rc, h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE cur_failrsn CURSOR FOR
SELECT retry_count, count(*), coalesce(sum(net),0)
FROM py_payout WHERE status = 'J' AND biz_date = :h_bizdate
GROUP BY retry_count ORDER BY retry_count;
EXEC SQL OPEN cur_failrsn;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_failrsn INTO :h_rc, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_failrsn; FAIL(b); }
buckets++;
if (i < 8) {
Bchg(b, T_ARG6, i, (char *)&h_rc, 0L);
Bchg(b, T_ARG7, i, (char *)&h_cnt, 0L);
Bchg(b, T_AMT2, i, (char *)&h_amt, 0L);
i++;
}
}
EXEC SQL CLOSE cur_failrsn;
setl(b, T_COUNT, buckets);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FEECAP.h - py PAY_FEECAP (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FEECAP.pgc get/set .
*/
#ifndef PAY_FEECAP_H
#define PAY_FEECAP_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_feecap_rec_t;
void PAY_FEECAP(TPSVCINFO *p);
#endif /* PAY_FEECAP_H */

View file

@ -0,0 +1,45 @@
/*
* PAY_FEECAP.pgc - py 모듈 서비스 PAY_FEECAP (one service = one file).
* 수수료 상한 적용: 미지급 건 수수료가 상한(T_AMOUNT)을 넘으면 상한까지
* 낮추고 초과분만큼 net 을 늘린다. 초과분은 py_adjust 에 양수로 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FEECAP.h"
void PAY_FEECAP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char reason[96], bizdate[16];
long cap, cut, adj;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_fee, h_net, h_newfee, h_newnet;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
cap = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason));
if (reason[0] == 0) strcpy(reason, "수수료상한");
EXEC SQL SELECT fee, net, status INTO :h_fee, :h_net, :h_st FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'R' && h_st[0] != 'A') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "미지급 건만 적용 가능", 0L); FAIL(b); }
if (h_fee <= cap) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "수수료가 상한 이내", 0L); FAIL(b); }
cut = h_fee - cap;
h_newfee = cap;
h_newnet = h_net + cut;
adj = pydb_insert_adjust(h_pid, cut, reason, bizdate);
if (adj < 0) FAIL(b);
EXEC SQL UPDATE py_payout SET fee = :h_newfee, net = :h_newnet, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ADJ, adj);
setl(b, T_FEE, h_newfee);
setl(b, T_NET, h_newnet);
userlog("PAY_FEECAP id=%ld cap=%ld cut=%ld", h_pid, cap, cut);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FEEFIXED.h - py PAY_FEEFIXED (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FEEFIXED.pgc get/set .
*/
#ifndef PAY_FEEFIXED_H
#define PAY_FEEFIXED_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_feefixed_rec_t;
void PAY_FEEFIXED(TPSVCINFO *p);
#endif /* PAY_FEEFIXED_H */

View file

@ -0,0 +1,36 @@
/*
* PAY_FEEFIXED.pgc - py 모듈 서비스 PAY_FEEFIXED (one service = one file).
* 정액 수수료 적용: 미지급(R/A) 건에 요청 정액 수수료(T_AMOUNT)를 부과하고
* net 을 amount-fee 로 재계산한다. 수수료가 원금을 넘으면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FEEFIXED.h"
void PAY_FEEFIXED(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long fixed;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt, h_fee, h_net;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
fixed = getl(b, T_AMOUNT);
EXEC SQL SELECT amount, status INTO :h_amt, :h_st FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'R' && h_st[0] != 'A') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "미지급 건만 적용 가능", 0L); FAIL(b); }
if (fixed > h_amt) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "수수료가 원금 초과", 0L); FAIL(b); }
h_fee = fixed;
h_net = h_amt - fixed;
EXEC SQL UPDATE py_payout SET fee = :h_fee, net = :h_net, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_FEE, h_fee);
setl(b, T_NET, h_net);
userlog("PAY_FEEFIXED id=%ld fee=%ld net=%ld", h_pid, h_fee, h_net);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FEEREFUND.h - py PAY_FEEREFUND (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FEEREFUND.pgc get/set .
*/
#ifndef PAY_FEEREFUND_H
#define PAY_FEEREFUND_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_feerefund_rec_t;
void PAY_FEEREFUND(TPSVCINFO *p);
#endif /* PAY_FEEREFUND_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_FEEREFUND.pgc - py 모듈 서비스 PAY_FEEREFUND (one service = one file).
* 수수료 환급: 지급완료(P) 건의 수수료를 지급계좌에 되돌려 입금하고,
* py_ledger 에 양수 FEEREFUND 기표를 남긴다. 수수료가 없으면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FEEREFUND.h"
void PAY_FEEREFUND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_fee;
char h_acct[32], h_st[4], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT account_no, fee, status, CAST(biz_date AS text)
INTO :h_acct, :h_fee, :h_st, :h_bizdate FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "지급완료 건만 환급 가능", 0L); FAIL(b); }
if (h_fee <= 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "환급할 수수료 없음", 0L); FAIL(b); }
if (pydb_account_add(h_acct, h_fee) < 0) FAIL(b);
EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
VALUES (nextval('py_ledger_seq'), :h_pid, 'FEEREFUND', :h_fee, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_FEE, h_fee);
Bchg(b, T_STR1, 0, h_acct, 0L);
userlog("PAY_FEEREFUND id=%ld fee=%ld acct=%s", h_pid, h_fee, h_acct);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FEESUM.h - py PAY_FEESUM (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FEESUM.pgc get/set .
*/
#ifndef PAY_FEESUM_H
#define PAY_FEESUM_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_feesum_rec_t;
void PAY_FEESUM(TPSVCINFO *p);
#endif /* PAY_FEESUM_H */

View file

@ -0,0 +1,32 @@
/*
* PAY_FEESUM.pgc - py 모듈 서비스 PAY_FEESUM (one service = one file).
* 수수료 집계: 당일 지급건의 수수료 합/건수와, py_adjust 에 쌓인 조정 합을
* 각각 조회해 순수수료(수수료-조정)를 산출해 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FEESUM.h"
void PAY_FEESUM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_cnt, h_fee, h_adj;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*), coalesce(sum(fee),0) INTO :h_cnt, :h_fee
FROM py_payout WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT coalesce(sum(delta),0) INTO :h_adj
FROM py_adjust WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_COUNT, h_cnt);
setl(b, T_FEE, h_fee);
setl(b, T_ADJ, h_adj);
setl(b, T_NET, h_fee - h_adj);
userlog("PAY_FEESUM date=%s fee=%ld adj=%ld", h_bizdate, h_fee, h_adj);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FILECANCEL.h - py PAY_FILECANCEL (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FILECANCEL.pgc get/set .
*/
#ifndef PAY_FILECANCEL_H
#define PAY_FILECANCEL_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_filecancel_rec_t;
void PAY_FILECANCEL(TPSVCINFO *p);
#endif /* PAY_FILECANCEL_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_FILECANCEL.pgc - py 모듈 서비스 PAY_FILECANCEL (one service = one file).
* 지급파일 취소: 확정(C) 전 파일만 취소 가능. 편성(F) 건을 승인(A)+file_id=0
* 으로 되돌린 뒤 파일 헤더를 삭제한다. 확정/부재 파일은 사유코드로 거절.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FILECANCEL.h"
void PAY_FILECANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_fid, h_back;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_fid = getl(b, T_ID2);
EXEC SQL SELECT status INTO :h_st FROM py_file WHERE file_id = :h_fid;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "파일 없음", 0L); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (h_st[0] == 'C') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "확정 파일은 취소 불가", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET status = 'A', file_id = 0, updated_at = now()
WHERE file_id = :h_fid AND status = 'F';
if (sqlca.sqlcode < 0) FAIL(b);
h_back = sqlca.sqlerrd[2];
EXEC SQL DELETE FROM py_file WHERE file_id = :h_fid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_COUNT, h_back);
userlog("PAY_FILECANCEL file=%ld reverted=%ld", h_fid, h_back);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_FILECONF.h - py PAY_FILECONF (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_FILECONF.pgc get/set .
*/
#ifndef PAY_FILECONF_H
#define PAY_FILECONF_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_fileconf_rec_t;
void PAY_FILECONF(TPSVCINFO *p);
#endif /* PAY_FILECONF_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_FILECONF.pgc - py 모듈 서비스 PAY_FILECONF (one service = one file).
* 지급파일 확정: 생성(G) 파일만 확정(C)+confirmed_at 로 전이하고, 그 파일에
* 편성(F)된 지급건을 지급완료(P)로 확정한다. 이미 확정 파일은 거절.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_FILECONF.h"
void PAY_FILECONF(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_fid, h_paid;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_fid = getl(b, T_ID2);
EXEC SQL SELECT status INTO :h_st FROM py_file WHERE file_id = :h_fid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] == 'C') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "이미 확정된 파일", 0L); FAIL(b); }
EXEC SQL UPDATE py_file SET status = 'C', confirmed_at = now() WHERE file_id = :h_fid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE py_payout SET status = 'P', updated_at = now()
WHERE file_id = :h_fid AND status = 'F';
if (sqlca.sqlcode < 0) FAIL(b);
h_paid = sqlca.sqlerrd[2];
setl(b, T_RC, 0);
setl(b, T_COUNT, h_paid);
Bchg(b, T_STATUS, 0, "C", 0L);
userlog("PAY_FILECONF file=%ld paid=%ld", h_fid, h_paid);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_GARNISH.h - py PAY_GARNISH (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_GARNISH.pgc get/set .
*/
#ifndef PAY_GARNISH_H
#define PAY_GARNISH_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_garnish_rec_t;
void PAY_GARNISH(TPSVCINFO *p);
#endif /* PAY_GARNISH_H */

View file

@ -0,0 +1,38 @@
/*
* PAY_GARNISH.pgc - py 모듈 서비스 PAY_GARNISH (one service = one file).
* 가압류 등록: 활성 계좌를 GARNISH 로 표시하고, 그 계좌 미지급(R/A) 잔액을
* 집계해 가압류 채권으로 py_ledger 에 음수 GARNISH 기표를 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_GARNISH.h"
void PAY_GARNISH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32], h_bizdate[16];
long h_cnt, h_sum, h_neg;
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL UPDATE py_account SET status = 'GARNISH', updated_at = now()
WHERE account_no = :h_acct AND status = 'ACTIVE';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "활성 계좌 아님", 0L); FAIL(b); }
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
FROM py_payout WHERE account_no = :h_acct AND status IN ('R','A');
if (sqlca.sqlcode < 0) FAIL(b);
h_neg = -h_sum;
EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
VALUES (nextval('py_ledger_seq'), 0, 'GARNISH', :h_neg, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
userlog("PAY_GARNISH acct=%s frozen=%ld amt=%ld", h_acct, h_cnt, h_sum);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_HOLDBYRSN.h - py PAY_HOLDBYRSN (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_HOLDBYRSN.pgc get/set .
*/
#ifndef PAY_HOLDBYRSN_H
#define PAY_HOLDBYRSN_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_holdbyrsn_rec_t;
void PAY_HOLDBYRSN(TPSVCINFO *p);
#endif /* PAY_HOLDBYRSN_H */

View file

@ -0,0 +1,42 @@
/*
* PAY_HOLDBYRSN.pgc - py 모듈 서비스 PAY_HOLDBYRSN (one service = one file).
* 보류 사유별 집계: 당일 보류(H) 건을 reject_reason 별 커서로 건수/net 합
* 집계해 건수 내림차순 occurrence 로 회신한다 (보류 원인 분포 파악).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_HOLDBYRSN.h"
void PAY_HOLDBYRSN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long rows = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_rsn[128];
long h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE cur_holdbyrsn CURSOR FOR
SELECT coalesce(nullif(reject_reason,''),'미지정'), count(*), coalesce(sum(net),0)
FROM py_payout WHERE status = 'H' AND biz_date = :h_bizdate
GROUP BY 1 ORDER BY 2 DESC, 1;
EXEC SQL OPEN cur_holdbyrsn;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_holdbyrsn INTO :h_rsn, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_holdbyrsn; FAIL(b); }
rows++;
if (i < 10) {
Bchg(b, T_STR4, i, h_rsn, 0L);
Bchg(b, T_ARG5, i, (char *)&h_cnt, 0L);
Bchg(b, T_AMT4, i, (char *)&h_amt, 0L);
i++;
}
}
EXEC SQL CLOSE cur_holdbyrsn;
setl(b, T_COUNT, rows);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_HOLDCLEAR.h - py PAY_HOLDCLEAR (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_HOLDCLEAR.pgc get/set .
*/
#ifndef PAY_HOLDCLEAR_H
#define PAY_HOLDCLEAR_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_holdclear_rec_t;
void PAY_HOLDCLEAR(TPSVCINFO *p);
#endif /* PAY_HOLDCLEAR_H */

View file

@ -0,0 +1,33 @@
/*
* PAY_HOLDCLEAR.pgc - py 모듈 서비스 PAY_HOLDCLEAR (one service = one file).
* 보류 일괄 해제: 지정 계좌의 보류(H) 지급건을 모두 승인(A)으로 되돌리고
* 반려사유를 비운다. 해제 대상이 없으면 사유코드 1 로 안내한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_HOLDCLEAR.h"
void PAY_HOLDCLEAR(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long cleared;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
EXEC SQL UPDATE py_payout SET status = 'A', reject_reason = '', updated_at = now()
WHERE account_no = :h_acct AND status = 'H';
if (sqlca.sqlcode < 0) FAIL(b);
cleared = sqlca.sqlerrd[2];
setl(b, T_COUNT, cleared);
if (cleared == 0) {
setl(b, T_RC, 1);
Bchg(b, T_MSG, 0, "해제할 보류 건 없음", 0L);
} else {
setl(b, T_RC, 0);
Bchg(b, T_MSG, 0, "보류 일괄 해제 완료", 0L);
userlog("PAY_HOLDCLEAR acct=%s cleared=%ld", h_acct, cleared);
}
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_HOLDEXT.h - py PAY_HOLDEXT (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_HOLDEXT.pgc get/set .
*/
#ifndef PAY_HOLDEXT_H
#define PAY_HOLDEXT_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_holdext_rec_t;
void PAY_HOLDEXT(TPSVCINFO *p);
#endif /* PAY_HOLDEXT_H */

View file

@ -0,0 +1,32 @@
/*
* PAY_HOLDEXT.pgc - py 모듈 서비스 PAY_HOLDEXT (one service = one file).
* 보류 연장: 보류(H) 건의 반려사유 뒤에 '(연장)' 표식을 덧붙여 갱신한다.
* 보류 상태가 아니면 거절한다. 재보류 없이 사유 텍스트만 확장한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_HOLDEXT.h"
void PAY_HOLDEXT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_st[4], h_rsn[128], newrsn[160];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT status, reject_reason INTO :h_st, :h_rsn
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'H') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "보류 건만 연장 가능", 0L); FAIL(b); }
snprintf(newrsn, sizeof(newrsn), "%s(연장)", h_rsn[0] ? h_rsn : "보류");
EXEC SQL UPDATE py_payout SET reject_reason = :newrsn, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
Bchg(b, T_REASON, 0, newrsn, 0L);
userlog("PAY_HOLDEXT payout=%ld reason=%s", h_pid, newrsn);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_HOLDNOTE.h - py PAY_HOLDNOTE (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_HOLDNOTE.pgc get/set .
*/
#ifndef PAY_HOLDNOTE_H
#define PAY_HOLDNOTE_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_holdnote_rec_t;
void PAY_HOLDNOTE(TPSVCINFO *p);
#endif /* PAY_HOLDNOTE_H */

View file

@ -0,0 +1,33 @@
/*
* PAY_HOLDNOTE.pgc - py 모듈 서비스 PAY_HOLDNOTE (one service = one file).
* 보류 사유 갱신: 보류(H) 단건의 반려사유를 요청 사유로 교체한다. dbio 로
* 현재 상태를 확인해 보류 건만 허용하고, 사유 미입력 시 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_HOLDNOTE.h"
void PAY_HOLDNOTE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long payout_id = getl(b, T_ID1);
char st[4], reason[128];
gets_(b, T_REASON, reason, sizeof(reason));
if (reason[0] == 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "사유 미입력", 0L); FAIL(b); }
if (pydb_get_status(payout_id, st) < 0) FAIL(b);
if (st[0] != 'H') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "보류 건만 갱신 가능", 0L); FAIL(b); }
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = payout_id;
char h_rsn[128];
EXEC SQL END DECLARE SECTION;
strncpy(h_rsn, reason, sizeof(h_rsn)-1); h_rsn[sizeof(h_rsn)-1] = 0;
EXEC SQL UPDATE py_payout SET reject_reason = :h_rsn, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
}
setl(b, T_RC, 0);
Bchg(b, T_REASON, 0, reason, 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_LIMITLIST.h - py PAY_LIMITLIST (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_LIMITLIST.pgc get/set .
*/
#ifndef PAY_LIMITLIST_H
#define PAY_LIMITLIST_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_limitlist_rec_t;
void PAY_LIMITLIST(TPSVCINFO *p);
#endif /* PAY_LIMITLIST_H */

View file

@ -0,0 +1,44 @@
/*
* PAY_LIMITLIST.pgc - py 모듈 서비스 PAY_LIMITLIST (one service = one file).
* 지급 한도 사용현황 목록: 가맹점별 일한도와 당일 확정성(A/F/P) 사용액을
* 조인 커서로 훑어 가맹점/한도/사용액을 occurrence 로 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_LIMITLIST.h"
void PAY_LIMITLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long n = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_lim, h_used;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE cur_limitlist CURSOR FOR
SELECT m.merchant_id, m.daily_limit,
coalesce((SELECT sum(net) FROM py_payout p
WHERE p.merchant_id = m.merchant_id AND p.biz_date = :h_bizdate
AND p.status IN ('A','F','P')), 0)
FROM merchant m ORDER BY m.merchant_id;
EXEC SQL OPEN cur_limitlist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_limitlist INTO :h_merch, :h_lim, :h_used;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_limitlist; FAIL(b); }
n++;
if (i < 10) {
Bchg(b, T_STR2, i, h_merch, 0L);
Bchg(b, T_AMT1, i, (char *)&h_lim, 0L);
Bchg(b, T_AMT2, i, (char *)&h_used, 0L);
i++;
}
}
EXEC SQL CLOSE cur_limitlist;
setl(b, T_COUNT, n);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_LIMITSET.h - py PAY_LIMITSET (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_LIMITSET.pgc get/set .
*/
#ifndef PAY_LIMITSET_H
#define PAY_LIMITSET_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_limitset_rec_t;
void PAY_LIMITSET(TPSVCINFO *p);
#endif /* PAY_LIMITSET_H */

View file

@ -0,0 +1,31 @@
/*
* PAY_LIMITSET.pgc - py 모듈 서비스 PAY_LIMITSET (one service = one file).
* 지급 일한도 설정: 가맹점의 일 지급한도(merchant.daily_limit)를 새 값으로
* 갱신한다. 존재하지 않는 가맹점이거나 음수 한도면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_LIMITSET.h"
void PAY_LIMITSET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long newlim;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64];
long h_lim;
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
newlim = getl(b, T_AMOUNT);
if (newlim < 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "한도는 0 이상이어야 함", 0L); FAIL(b); }
h_lim = newlim;
EXEC SQL UPDATE merchant SET daily_limit = :h_lim WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "가맹점 없음", 0L); FAIL(b); }
setl(b, T_RC, 0);
setl(b, T_AMT1, h_lim);
userlog("PAY_LIMITSET merch=%s limit=%ld", h_merch, h_lim);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_LIMITWARN.h - py PAY_LIMITWARN (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_LIMITWARN.pgc get/set .
*/
#ifndef PAY_LIMITWARN_H
#define PAY_LIMITWARN_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_limitwarn_rec_t;
void PAY_LIMITWARN(TPSVCINFO *p);
#endif /* PAY_LIMITWARN_H */

View file

@ -0,0 +1,49 @@
/*
* PAY_LIMITWARN.pgc - py 모듈 서비스 PAY_LIMITWARN (one service = one file).
* 지급 한도 근접 경보: 당일 사용액이 일한도의 지정 비율(T_ARG1 %, 기본 80)
* 이상인 가맹점을 커서로 뽑아 경보 목록을 occurrence 로 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_LIMITWARN.h"
void PAY_LIMITWARN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long warns = 0, ratio;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_ratio, h_lim, h_used;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
ratio = getl(b, T_ARG1);
h_ratio = (ratio > 0) ? ratio : 80;
EXEC SQL DECLARE cur_limitwarn CURSOR FOR
SELECT m.merchant_id, m.daily_limit, u.used
FROM merchant m JOIN (
SELECT merchant_id, sum(net) AS used FROM py_payout
WHERE biz_date = :h_bizdate AND status IN ('A','F','P')
GROUP BY merchant_id) u ON u.merchant_id = m.merchant_id
WHERE u.used * 100 >= m.daily_limit * :h_ratio
ORDER BY u.used DESC;
EXEC SQL OPEN cur_limitwarn;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_limitwarn INTO :h_merch, :h_lim, :h_used;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_limitwarn; FAIL(b); }
warns++;
if (i < 10) {
Bchg(b, T_STR2, i, h_merch, 0L);
Bchg(b, T_AMT1, i, (char *)&h_used, 0L);
Bchg(b, T_AMT2, i, (char *)&h_lim, 0L);
i++;
}
}
EXEC SQL CLOSE cur_limitwarn;
setl(b, T_COUNT, warns);
setl(b, T_RC, warns > 0 ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_NOTIFYALL.h - py PAY_NOTIFYALL (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_NOTIFYALL.pgc get/set .
*/
#ifndef PAY_NOTIFYALL_H
#define PAY_NOTIFYALL_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_notifyall_rec_t;
void PAY_NOTIFYALL(TPSVCINFO *p);
#endif /* PAY_NOTIFYALL_H */

View file

@ -0,0 +1,34 @@
/*
* PAY_NOTIFYALL.pgc - py 모듈 서비스 PAY_NOTIFYALL (one service = one file).
* 전 채널 일괄 통지(시뮬): 지급완료(P) 단건을 SMS/EMAIL/PUSH 세 채널로 각각
* 발송 시뮬레이션하고 발송 채널 수를 회신한다 (C 배열 순회).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_NOTIFYALL.h"
void PAY_NOTIFYALL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
const char *chans[3] = { "SMS", "EMAIL", "PUSH" };
int k, sent = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
char h_merch[64], h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT merchant_id, net, status INTO :h_merch, :h_net, :h_st
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "지급완료 건만 통지", 0L); FAIL(b); }
for (k = 0; k < 3; k++) {
userlog("PAY_NOTIFYALL ch=%s payout=%ld to=%s", chans[k], h_pid, h_merch);
sent++;
}
setl(b, T_RC, 0);
setl(b, T_COUNT, sent);
setl(b, T_NET, h_net);
Bchg(b, T_MSG, 0, "전 채널 통지 완료", 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_NOTIFYFAIL.h - py PAY_NOTIFYFAIL (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_NOTIFYFAIL.pgc get/set .
*/
#ifndef PAY_NOTIFYFAIL_H
#define PAY_NOTIFYFAIL_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_notifyfail_rec_t;
void PAY_NOTIFYFAIL(TPSVCINFO *p);
#endif /* PAY_NOTIFYFAIL_H */

View file

@ -0,0 +1,31 @@
/*
* PAY_NOTIFYFAIL.pgc - py 모듈 서비스 PAY_NOTIFYFAIL (one service = one file).
* 지급실패 통지(시뮬): 반려(J) 건의 가맹점/반려사유를 읽어 실패 안내 본문을
* 조립해 발송 시뮬레이션한다. 반려 건이 아니면 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_NOTIFYFAIL.h"
void PAY_NOTIFYFAIL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char msg[224];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_merch[64], h_st[4], h_rsn[128];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT merchant_id, status, reject_reason INTO :h_merch, :h_st, :h_rsn
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'J') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "반려 건만 실패통지 대상", 0L); FAIL(b); }
if (h_rsn[0] == 0) strcpy(h_rsn, "사유미상");
snprintf(msg, sizeof(msg), "[Forge] %s 지급실패: %s", h_merch, h_rsn);
userlog("PAY_NOTIFYFAIL payout=%ld reason=%s", h_pid, h_rsn);
setl(b, T_RC, 0);
Bchg(b, T_REASON, 0, h_rsn, 0L);
Bchg(b, T_MSG, 0, msg, 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_NOTIFYKAKAO.h - py PAY_NOTIFYKAKAO (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_NOTIFYKAKAO.pgc get/set .
*/
#ifndef PAY_NOTIFYKAKAO_H
#define PAY_NOTIFYKAKAO_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_notifykakao_rec_t;
void PAY_NOTIFYKAKAO(TPSVCINFO *p);
#endif /* PAY_NOTIFYKAKAO_H */

View file

@ -0,0 +1,29 @@
/*
* PAY_NOTIFYKAKAO.pgc - py 모듈 서비스 PAY_NOTIFYKAKAO (one service = one file).
* 알림톡 일괄 통지(시뮬): 당일 지급완료(P) 건수/금액을 한 번에 집계해 알림톡
* 대량발송을 시뮬레이션한다. 대상이 없으면 안내 후 정상 종료한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_NOTIFYKAKAO.h"
void PAY_NOTIFYKAKAO(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_cnt, h_sum;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "통지 대상 없음", 0L); OK(b); }
userlog("PAY_NOTIFYKAKAO 일괄발송시뮬 bizdate=%s targets=%ld amt=%ld", h_bizdate, h_cnt, h_sum);
setl(b, T_RC, 0);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
Bchg(b, T_MSG, 0, "알림톡 발송 완료", 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_NOTIFYPUSH.h - py PAY_NOTIFYPUSH (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_NOTIFYPUSH.pgc get/set .
*/
#ifndef PAY_NOTIFYPUSH_H
#define PAY_NOTIFYPUSH_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_notifypush_rec_t;
void PAY_NOTIFYPUSH(TPSVCINFO *p);
#endif /* PAY_NOTIFYPUSH_H */

View file

@ -0,0 +1,33 @@
/*
* PAY_NOTIFYPUSH.pgc - py 모듈 서비스 PAY_NOTIFYPUSH (one service = one file).
* 지급완료 푸시 통지(시뮬): 지급완료(P)건을 py_account 조인으로 예금주/은행을
* 읽어 푸시 본문을 조립하고 발송은 userlog 로 시뮬레이션한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_NOTIFYPUSH.h"
void PAY_NOTIFYPUSH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char msg[192];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
char h_holder[64], h_st[4], h_bank[8];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT p.net, p.status, a.holder, a.bank_code
INTO :h_net, :h_st, :h_holder, :h_bank
FROM py_payout p JOIN py_account a ON p.account_no = a.account_no
WHERE p.payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "지급완료 건만 푸시 가능", 0L); FAIL(b); }
snprintf(msg, sizeof(msg), "[Forge] %s(%s) %ld원 지급 알림", h_holder, h_bank, h_net);
userlog("PAY_NOTIFYPUSH 발송시뮬 payout=%ld holder=%s", h_pid, h_holder);
setl(b, T_RC, 0);
setl(b, T_NET, h_net);
Bchg(b, T_STR2, 0, h_bank, 0L);
Bchg(b, T_MSG, 0, msg, 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_NOTIFYRESEND.h - py PAY_NOTIFYRESEND (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_NOTIFYRESEND.pgc get/set .
*/
#ifndef PAY_NOTIFYRESEND_H
#define PAY_NOTIFYRESEND_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_notifyresend_rec_t;
void PAY_NOTIFYRESEND(TPSVCINFO *p);
#endif /* PAY_NOTIFYRESEND_H */

View file

@ -0,0 +1,32 @@
/*
* PAY_NOTIFYRESEND.pgc - py 모듈 서비스 PAY_NOTIFYRESEND (one service = one file).
* 통지 재발송: 지급완료(P) 건의 retry_count 를 재발송 횟수로 삼아 3회까지만
* 허용하며, 재발송 시 카운터를 +1 하고 현재 시도 회차를 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_NOTIFYRESEND.h"
void PAY_NOTIFYRESEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_rc;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
EXEC SQL SELECT status, retry_count INTO :h_st, :h_rc
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "지급완료 건만 재발송", 0L); FAIL(b); }
if (h_rc >= 3) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "재발송 한도 초과", 0L); FAIL(b); }
EXEC SQL UPDATE py_payout SET retry_count = retry_count + 1, updated_at = now()
WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
userlog("PAY_NOTIFYRESEND payout=%ld attempt=%ld", h_pid, h_rc + 1);
setl(b, T_RC, 0);
setl(b, T_COUNT, h_rc + 1);
Bchg(b, T_MSG, 0, "통지 재발송 완료", 0L);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_OFFSETAPPLY.h - py PAY_OFFSETAPPLY (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_OFFSETAPPLY.pgc get/set .
*/
#ifndef PAY_OFFSETAPPLY_H
#define PAY_OFFSETAPPLY_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_offsetapply_rec_t;
void PAY_OFFSETAPPLY(TPSVCINFO *p);
#endif /* PAY_OFFSETAPPLY_H */

View file

@ -0,0 +1,41 @@
/*
* PAY_OFFSETAPPLY.pgc - py 모듈 서비스 PAY_OFFSETAPPLY (one service = one file).
* 상계 적용: 미지급(R/A) 건의 net 에서 상계액을 차감(net 미만으로 캡)하고,
* 차감분을 py_adjust 에 음수 조정으로 남긴다. 지급 전 채권 상계 반영.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_OFFSETAPPLY.h"
void PAY_OFFSETAPPLY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char reason[96], bizdate[16];
long off, adj;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net, h_cut;
char h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
off = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason));
if (reason[0] == 0) strcpy(reason, "상계");
EXEC SQL SELECT net, status INTO :h_net, :h_st FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'R' && h_st[0] != 'A') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "미지급 건만 상계 가능", 0L); FAIL(b); }
h_cut = (off > h_net) ? h_net : off;
if (h_cut <= 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "상계액 무효", 0L); FAIL(b); }
adj = pydb_insert_adjust(h_pid, -h_cut, reason, bizdate);
if (adj < 0) FAIL(b);
EXEC SQL UPDATE py_payout SET net = net - :h_cut, updated_at = now() WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ADJ, adj);
setl(b, T_NET, h_net - h_cut);
userlog("PAY_OFFSETAPPLY id=%ld off=%ld", h_pid, h_cut);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_PENDLIST.h - py PAY_PENDLIST (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_PENDLIST.pgc get/set .
*/
#ifndef PAY_PENDLIST_H
#define PAY_PENDLIST_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_pendlist_rec_t;
void PAY_PENDLIST(TPSVCINFO *p);
#endif /* PAY_PENDLIST_H */

View file

@ -0,0 +1,43 @@
/*
* PAY_PENDLIST.pgc - py 모듈 서비스 PAY_PENDLIST (one service = one file).
* 미지급 목록: 당일 미완결(요청R/승인A/보류H) 지급건을 커서로 훑어 지급ID/
* 가맹점/net/상태를 occurrence 로 회신한다 (집계 없는 상세 리스트).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_PENDLIST.h"
void PAY_PENDLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long n = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64], h_st[4];
long h_pid, h_net;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL DECLARE cur_pendlist CURSOR FOR
SELECT payout_id, merchant_id, net, status FROM py_payout
WHERE biz_date = :h_bizdate AND status IN ('R','A','H')
ORDER BY payout_id;
EXEC SQL OPEN cur_pendlist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_pendlist INTO :h_pid, :h_merch, :h_net, :h_st;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_pendlist; FAIL(b); }
n++;
if (i < 12) {
Bchg(b, T_ARG1, i, (char *)&h_pid, 0L);
Bchg(b, T_STR2, i, h_merch, 0L);
Bchg(b, T_AMT1, i, (char *)&h_net, 0L);
Bchg(b, T_STR3, i, h_st, 0L);
i++;
}
}
EXEC SQL CLOSE cur_pendlist;
setl(b, T_COUNT, n);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_REACTIVATE.h - py PAY_REACTIVATE (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_REACTIVATE.pgc get/set .
*/
#ifndef PAY_REACTIVATE_H
#define PAY_REACTIVATE_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_reactivate_rec_t;
void PAY_REACTIVATE(TPSVCINFO *p);
#endif /* PAY_REACTIVATE_H */

View file

@ -0,0 +1,26 @@
/*
* PAY_REACTIVATE.pgc - py 모듈 서비스 PAY_REACTIVATE (one service = one file).
* 휴면 계좌 재활성: 휴면(DORMANT) 처리된 지급계좌를 다시 활성(ACTIVE)으로
* 되돌린다. 휴면 상태가 아니면 재활성 대상이 아니므로 거절한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_REACTIVATE.h"
void PAY_REACTIVATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_acct[32];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, h_acct, sizeof(h_acct));
EXEC SQL UPDATE py_account SET status = 'ACTIVE', updated_at = now()
WHERE account_no = :h_acct AND status = 'DORMANT';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "휴면 계좌 아님", 0L); FAIL(b); }
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "ACTIVE", 0L);
userlog("PAY_REACTIVATE acct=%s -> ACTIVE", h_acct);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_RECLAIM.h - py PAY_RECLAIM (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_RECLAIM.pgc get/set .
*/
#ifndef PAY_RECLAIM_H
#define PAY_RECLAIM_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_reclaim_rec_t;
void PAY_RECLAIM(TPSVCINFO *p);
#endif /* PAY_RECLAIM_H */

View file

@ -0,0 +1,38 @@
/*
* PAY_RECLAIM.pgc - py 모듈 서비스 PAY_RECLAIM (one service = one file).
* 과지급 환수: 지급완료(P) 건에서 요청 환수액(T_AMOUNT, net 한도)만큼을
* 지급계좌에서 차감(음수 account_add)하고 py_ledger 에 음수 RECLAIM 기표.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_RECLAIM.h"
void PAY_RECLAIM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long want, recl;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net, h_neg;
char h_acct[32], h_st[4], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
want = getl(b, T_AMOUNT);
EXEC SQL SELECT account_no, net, status, CAST(biz_date AS text)
INTO :h_acct, :h_net, :h_st, :h_bizdate FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'P') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "지급완료 건만 환수 가능", 0L); FAIL(b); }
recl = (want > 0 && want < h_net) ? want : h_net;
if (recl <= 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "환수액 무효", 0L); FAIL(b); }
if (pydb_account_add(h_acct, -recl) < 0) FAIL(b);
h_neg = -recl;
EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
VALUES (nextval('py_ledger_seq'), :h_pid, 'RECLAIM', :h_neg, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_AMT1, recl);
Bchg(b, T_STR1, 0, h_acct, 0L);
userlog("PAY_RECLAIM id=%ld reclaimed=%ld acct=%s", h_pid, recl, h_acct);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_REPAYACCT.h - py PAY_REPAYACCT (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_REPAYACCT.pgc get/set .
*/
#ifndef PAY_REPAYACCT_H
#define PAY_REPAYACCT_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_repayacct_rec_t;
void PAY_REPAYACCT(TPSVCINFO *p);
#endif /* PAY_REPAYACCT_H */

View file

@ -0,0 +1,39 @@
/*
* PAY_REPAYACCT.pgc - py 모듈 서비스 PAY_REPAYACCT (one service = one file).
* 계좌변경 재지급: 반려(J)된 지급건의 금액/수수료를 승계해 새 계좌(T_KEY2)로
* 신규 요청(R) 지급건을 복제 생성한다 (원건은 그대로 두어 이력 보존).
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_REPAYACCT.h"
void PAY_REPAYACCT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char newacct[32], bizdate[16];
long newid;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt, h_fee, h_net;
char h_merch[64], h_st[4];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_ID1);
gets_(b, T_KEY2, newacct, sizeof(newacct));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (newacct[0] == 0) { setl(b, T_RC, 3); Bchg(b, T_MSG, 0, "새 계좌 미지정", 0L); FAIL(b); }
EXEC SQL SELECT merchant_id, amount, fee, net, status
INTO :h_merch, :h_amt, :h_fee, :h_net, :h_st
FROM py_payout WHERE payout_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_st[0] != 'J') { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "반려 건만 재지급 대상", 0L); FAIL(b); }
newid = pydb_next_payout_id();
if (newid < 0) FAIL(b);
if (pydb_insert_payout(newid, h_merch, newacct, h_amt, h_fee, h_net, "R", "STD", bizdate) < 0) FAIL(b);
setl(b, T_RC, 0);
setl(b, T_ID2, newid);
setl(b, T_NET, h_net);
userlog("PAY_REPAYACCT old=%ld new=%ld acct=%s", h_pid, newid, newacct);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_REQUEUE.h - py PAY_REQUEUE (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_REQUEUE.pgc get/set .
*/
#ifndef PAY_REQUEUE_H
#define PAY_REQUEUE_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_requeue_rec_t;
void PAY_REQUEUE(TPSVCINFO *p);
#endif /* PAY_REQUEUE_H */

View file

@ -0,0 +1,28 @@
/*
* PAY_REQUEUE.pgc - py 모듈 서비스 PAY_REQUEUE (one service = one file).
* 실패 일괄 재처리 적재: 당일 반려(J) 건 중 재시도 여력(retry_count<3)이
* 있는 건들을 요청(R)으로 되돌리고 retry_count 를 +1 해 재처리 큐에 올린다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_REQUEUE.h"
void PAY_REQUEUE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long requeued;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL UPDATE py_payout
SET status = 'R', retry_count = retry_count + 1, reject_reason = '', updated_at = now()
WHERE biz_date = :h_bizdate AND status = 'J' AND retry_count < 3;
if (sqlca.sqlcode < 0) FAIL(b);
requeued = sqlca.sqlerrd[2];
setl(b, T_RC, requeued > 0 ? 0 : 1);
setl(b, T_COUNT, requeued);
userlog("PAY_REQUEUE date=%s requeued=%ld", h_bizdate, requeued);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_RESVLIST.h - py PAY_RESVLIST (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_RESVLIST.pgc get/set .
*/
#ifndef PAY_RESVLIST_H
#define PAY_RESVLIST_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_resvlist_rec_t;
void PAY_RESVLIST(TPSVCINFO *p);
#endif /* PAY_RESVLIST_H */

View file

@ -0,0 +1,43 @@
/*
* PAY_RESVLIST.pgc - py 모듈 서비스 PAY_RESVLIST (one service = one file).
* 예약 지급 목록: 지정 실행일(T_BIZDATE)에 걸린 예약(S) 을 py_schedule 과
* py_payout 조인 커서로 훑어 예약ID/지급ID/금액을 occurrence 로 회신한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
*/
#include "PAY_RESVLIST.h"
void PAY_RESVLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
int i = 0;
long n = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_rundate[16];
long h_sid, h_pid, h_amt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_rundate, sizeof(h_rundate));
EXEC SQL DECLARE cur_resvlist CURSOR FOR
SELECT s.schedule_id, s.payout_id, coalesce(p.net,0)
FROM py_schedule s LEFT JOIN py_payout p ON s.payout_id = p.payout_id
WHERE s.run_date = :h_rundate AND s.status = 'S'
ORDER BY s.schedule_id;
EXEC SQL OPEN cur_resvlist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_resvlist INTO :h_sid, :h_pid, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_resvlist; FAIL(b); }
n++;
if (i < 12) {
Bchg(b, T_ARG1, i, (char *)&h_sid, 0L);
Bchg(b, T_ARG2, i, (char *)&h_pid, 0L);
Bchg(b, T_AMT1, i, (char *)&h_amt, 0L);
i++;
}
}
EXEC SQL CLOSE cur_resvlist;
setl(b, T_COUNT, n);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* PAY_RESVMODIFY.h - py PAY_RESVMODIFY (copybook / record header).
* Shared base(py_svc.h) + + .
* Request/Response PAY_RESVMODIFY.pgc get/set .
*/
#ifndef PAY_RESVMODIFY_H
#define PAY_RESVMODIFY_H
#include "py_svc.h"
typedef struct {
long payout_id;
long amount;
long net;
char status[8];
} pay_resvmodify_rec_t;
void PAY_RESVMODIFY(TPSVCINFO *p);
#endif /* PAY_RESVMODIFY_H */

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