Phase 2b: 11개 모듈 전량 실동작 (333 서비스, 전서버 부팅)

- 10개 모듈 추가/확장(au py rc st lg cl mm vl mg cm), 각 30 서비스 = 330 + tmsrv
- build.sh 자동발견으로 11 모듈서버 + dbio 라이브러리 23 + 배치 22 빌드
- schema.d 11파일 → 75 테이블, 모듈별 disjoint
- 부팅 리소스 규명: fs.mqueue.queues_max=8192(330큐>256), NDRX_MSGSIZEMAX 16000·
  MSGMAX 50(큐당 5.6MB→0.8MB), msgqueue ulimit 2GB → 전 12서버 runok
- 검증: xadmin psc 333 AVAIL, 매입체인 XA 커밋, prepared_xacts=0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
forge-bot 2026-07-19 09:58:39 +00:00
parent 8219b85f14
commit bec83c721e
73 changed files with 10705 additions and 35 deletions

View file

@ -0,0 +1,62 @@
/*
* vl_anomaly_batch.pgc - 이상거래(고액/0원) 탐지 배치 (커서 + XA).
*
* Cursors over a biz_date's 매입 whose amount exceeds the AMT_MAX rule threshold
* or is non-positive, and that is not already flagged; each is written to
* vl_suspect (via dbio) under ONE global XA transaction.
* Usage: vl_anomaly_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "vl_dbio.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long flagged = 0;
char sev[8] = "ERROR";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_pid, h_amt, thr = 50000000;
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; }
/* 규칙 임계 로드 (dbio) - 없으면 기본값 유지 */
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) { thr = 50000000; strcpy(sev, "ERROR"); }
EXEC SQL DECLARE vac CURSOR FOR
SELECT p.purchase_id, p.amount FROM purchase p
WHERE p.biz_date = :h_bizdate
AND (p.amount > :thr OR p.amount <= 0)
AND NOT EXISTS (SELECT 1 FROM vl_suspect s
WHERE s.purchase_id = p.purchase_id AND s.rule_code = 'AMT_MAX')
ORDER BY p.purchase_id;
EXEC SQL OPEN vac;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH vac INTO :h_pid, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE vac; tpabort(0); return 1; }
if (vldb_insert_suspect(h_pid, "AMT_MAX", sev,
(h_amt <= 0) ? "0원/음수 금액" : "고액 이상거래", bizdate) < 0) {
EXEC SQL CLOSE vac; tpabort(0); return 1;
}
flagged++;
}
EXEC SQL CLOSE vac;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> vl_anomaly_batch COMMIT: bizdate=%s 임계=%ld 적출=%ld\n", bizdate, thr, flagged);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,64 @@
/*
* vl_sales_batch.pgc - 매입-정산 대사검증 배치 (커서 JOIN + XA).
*
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
* transaction, an EXEC SQL cursor JOINs 정산완료(status='S') 매입 against its
* settlement rows for a biz_date; any purchase whose settled net != sum of its
* settlement net (or with no settlement) is written to vl_suspect (via the
* linked dbio lib), then tpcommit drives XA 2PC.
* Usage: vl_sales_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "vl_dbio.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long scanned = 0, flagged = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_pid, h_pnet, h_snet;
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; }
/* 매입(정산완료) LEFT JOIN 정산합계 - 불일치/누락 추출 */
EXEC SQL DECLARE vsc CURSOR FOR
SELECT p.purchase_id, p.net, coalesce(s.snet, 0)
FROM purchase p
LEFT JOIN (SELECT purchase_id, sum(net) AS snet FROM settlement
GROUP BY purchase_id) s ON s.purchase_id = p.purchase_id
WHERE p.biz_date = :h_bizdate AND p.status = 'S'
ORDER BY p.purchase_id;
EXEC SQL OPEN vsc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH vsc INTO :h_pid, :h_pnet, :h_snet;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE vsc; tpabort(0); return 1; }
scanned++;
if (h_snet != h_pnet) {
if (vldb_insert_suspect(h_pid, "LEDGER_MIS", "CRIT",
"매입-정산 대사 불일치", bizdate) < 0) {
EXEC SQL CLOSE vsc; tpabort(0); return 1;
}
flagged++;
}
}
EXEC SQL CLOSE vsc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> vl_sales_batch COMMIT: bizdate=%s 검사=%ld 적출=%ld\n", bizdate, scanned, flagged);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,92 @@
/*
* vl_check_dbio.pgc - 의심거래 적재 + 검증 로그 기록 (libvldbio.a).
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "vl_dbio.h"
long vldb_next_suspect_id(void)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT nextval('vl_suspect_seq') INTO :h_id;
if (sqlca.sqlcode < 0) {
userlog("vldb_next_suspect_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long vldb_insert_suspect(long purchase_id, const char *rule_code,
const char *severity, const char *detail,
const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid = purchase_id;
char h_rule[32], h_sev[8], h_detail[128], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
strncpy(h_sev, severity, sizeof(h_sev) - 1); h_sev[sizeof(h_sev)-1] = 0;
strncpy(h_detail, detail, sizeof(h_detail) - 1); h_detail[sizeof(h_detail)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1);h_bizdate[sizeof(h_bizdate)-1]= 0;
EXEC SQL SELECT nextval('vl_suspect_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO vl_suspect
(suspect_id, purchase_id, rule_code, severity, detail, biz_date)
VALUES (:h_id, :h_pid, :h_rule, :h_sev, :h_detail, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("vldb_insert_suspect FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long vldb_insert_check_log(const char *check_type, long target_id,
long result_code, const char *detail,
const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_target = target_id, h_rc = result_code;
char h_type[32], h_detail[128], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_type, check_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
strncpy(h_detail, detail, sizeof(h_detail) - 1);h_detail[sizeof(h_detail)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1);h_bizdate[sizeof(h_bizdate)-1]= 0;
EXEC SQL SELECT nextval('vl_checklog_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO vl_check_log
(log_id, check_type, target_id, result_code, detail, biz_date)
VALUES (:h_id, :h_type, :h_target, :h_rc, :h_detail, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("vldb_insert_check_log FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long vldb_suspect_count(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect
WHERE biz_date = :h_bizdate AND status = 'OPEN';
if (sqlca.sqlcode < 0) {
userlog("vldb_suspect_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_cnt;
}

27
app/src/vl/dbio/vl_dbio.h Normal file
View file

@ -0,0 +1,27 @@
/*
* vl_dbio.h - vl (/validation) DB (libvldbio.a).
* ECPG (EXEC SQL) functions compiled into a static library and linked into
* vl_svr and the vl batches. They run on the caller's XA branch/connection
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
* Return convention: 0/>=0 = ok, -1 = SQL error (caller may also inspect sqlca).
*/
#ifndef VL_DBIO_H
#define VL_DBIO_H
/* 의심거래/검증로그 (vl_check_dbio) */
long vldb_next_suspect_id(void);
long vldb_insert_suspect(long purchase_id, const char *rule_code,
const char *severity, const char *detail,
const char *bizdate);
long vldb_insert_check_log(const char *check_type, long target_id,
long result_code, const char *detail,
const char *bizdate);
long vldb_suspect_count(const char *bizdate);
/* 검증규칙 (vl_rule_dbio) */
int vldb_get_rule(const char *rule_code, long *threshold_out,
char *severity_out /* >= 8 */);
int vldb_upsert_rule(const char *rule_code, const char *rule_name,
long threshold, const char *severity);
#endif /* VL_DBIO_H */

View file

@ -0,0 +1,51 @@
/*
* vl_rule_dbio.pgc - 검증 규칙 조회 + 등록 (libvldbio.a).
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "vl_dbio.h"
int vldb_get_rule(const char *rule_code, long *threshold_out, char *severity_out)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_thr;
char h_rule[32], h_sev[8];
EXEC SQL END DECLARE SECTION;
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
EXEC SQL SELECT threshold, severity INTO :h_thr, :h_sev
FROM vl_rule WHERE rule_code = :h_rule AND status = 'ACTIVE';
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
userlog("vldb_get_rule FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
*threshold_out = h_thr;
strncpy(severity_out, h_sev, 7); severity_out[7] = 0;
return 0;
}
int vldb_upsert_rule(const char *rule_code, const char *rule_name,
long threshold, const char *severity)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_thr = threshold;
char h_rule[32], h_name[64], h_sev[8];
EXEC SQL END DECLARE SECTION;
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
strncpy(h_name, rule_name, sizeof(h_name) - 1); h_name[sizeof(h_name)-1] = 0;
strncpy(h_sev, severity, sizeof(h_sev) - 1); h_sev[sizeof(h_sev)-1] = 0;
EXEC SQL INSERT INTO vl_rule (rule_code, rule_name, threshold, severity)
VALUES (:h_rule, :h_name, :h_thr, :h_sev)
ON CONFLICT (rule_code) DO UPDATE
SET rule_name = EXCLUDED.rule_name, threshold = EXCLUDED.threshold,
severity = EXCLUDED.severity, updated_at = now();
if (sqlca.sqlcode < 0) {
userlog("vldb_upsert_rule FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}

674
app/src/vl/vl_svr.pgc Normal file
View file

@ -0,0 +1,674 @@
/*
* vl_svr.pgc - vl (정합성검증/validation) MODULE SERVER.
*
* ONE binary that tpadvertise()s ALL ~30 online vl services (the real Tuxedo
* "one module server, many services" pattern). Every service is a distinct
* 검증 operation: it SELECTs the target rows, applies a validation branch, and
* returns an 에러코드 in T_RC (0=정상, >0=검증 실패). Genuine EXEC SQL directly
* or via the linked libvldbio.a dbio layer, plus common math from libacqcommon.a.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
* EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include <ubf.h>
#include <userlog.h>
#include <ndebug.h>
#include "acq.fd.h"
#include "vl_dbio.h"
#include "acq_common.h"
/* ----- small UBF helpers ------------------------------------------------- */
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
/* ----- service prototypes ------------------------------------------------ */
void VL_SALES(TPSVCINFO *p); void VL_CARD(TPSVCINFO *p);
void VL_LIMIT(TPSVCINFO *p); void VL_AMOUNT(TPSVCINFO *p);
void VL_ANOMALY(TPSVCINFO *p); void VL_REQUIRED(TPSVCINFO *p);
void VL_CODEVAL(TPSVCINFO *p); void VL_APPR_ACQ(TPSVCINFO *p);
void VL_DUP(TPSVCINFO *p); void VL_DATE(TPSVCINFO *p);
void VL_FEE(TPSVCINFO *p); void VL_SETTLE(TPSVCINFO *p);
void VL_LEDGER(TPSVCINFO *p); void VL_FORMAT(TPSVCINFO *p);
void VL_REFINTEG(TPSVCINFO *p); void VL_DUPAUTH(TPSVCINFO *p);
void VL_BIN(TPSVCINFO *p); void VL_EXPIRY(TPSVCINFO *p);
void VL_TOLERANCE(TPSVCINFO *p); void VL_MERCH(TPSVCINFO *p);
void VL_STATUS_CHK(TPSVCINFO *p); void VL_BALANCE(TPSVCINFO *p);
void VL_SUMMARY(TPSVCINFO *p); void VL_REPORT(TPSVCINFO *p);
void VL_BATCH_CHK(TPSVCINFO *p); void VL_THRESHOLD(TPSVCINFO *p);
void VL_RULE(TPSVCINFO *p); void VL_CROSSCHK(TPSVCINFO *p);
void VL_STATUS(TPSVCINFO *p); void VL_CONFIRM(TPSVCINFO *p);
/* ----- advertise table --------------------------------------------------- */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"VL_SALES", VL_SALES}, {"VL_CARD", VL_CARD},
{"VL_LIMIT", VL_LIMIT}, {"VL_AMOUNT", VL_AMOUNT},
{"VL_ANOMALY", VL_ANOMALY}, {"VL_REQUIRED", VL_REQUIRED},
{"VL_CODEVAL", VL_CODEVAL}, {"VL_APPR_ACQ", VL_APPR_ACQ},
{"VL_DUP", VL_DUP}, {"VL_DATE", VL_DATE},
{"VL_FEE", VL_FEE}, {"VL_SETTLE", VL_SETTLE},
{"VL_LEDGER", VL_LEDGER}, {"VL_FORMAT", VL_FORMAT},
{"VL_REFINTEG", VL_REFINTEG}, {"VL_DUPAUTH", VL_DUPAUTH},
{"VL_BIN", VL_BIN}, {"VL_EXPIRY", VL_EXPIRY},
{"VL_TOLERANCE", VL_TOLERANCE}, {"VL_MERCH", VL_MERCH},
{"VL_STATUS_CHK", VL_STATUS_CHK},{"VL_BALANCE", VL_BALANCE},
{"VL_SUMMARY", VL_SUMMARY}, {"VL_REPORT", VL_REPORT},
{"VL_BATCH_CHK", VL_BATCH_CHK}, {"VL_THRESHOLD", VL_THRESHOLD},
{"VL_RULE", VL_RULE}, {"VL_CROSSCHK", VL_CROSSCHK},
{"VL_STATUS", VL_STATUS}, {"VL_CONFIRM", VL_CONFIRM},
{NULL, NULL}
};
int tpsvrinit(int argc, char **argv)
{
int i;
if (tpopen() < 0) {
userlog("vl_svr: tpopen 실패: %s", tpstrerror(tperrno));
return -1;
}
for (i = 0; SVCS[i].name != NULL; i++) {
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
userlog("vl_svr: tpadvertise(%s) 실패: %s", SVCS[i].name, tpstrerror(tperrno));
return -1;
}
}
userlog("vl_svr: vl 검증 서비스 %d개 광고 완료, RM open", i);
return 0;
}
void tpsvrdone(void)
{
tpclose();
userlog("vl_svr: tpsvrdone");
}
/* ========================================================================= */
/* 1. VL_SALES - 매입 원거래 존재 검증 (RC 1=미존재). */
/* ========================================================================= */
void VL_SALES(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) rc = 1;
vldb_insert_check_log("VL_SALES", h_pid, rc, rc ? "매입 미존재" : "OK", bizdate);
setl(b, T_RC, rc);
OK(b);
}
/* 2. VL_CARD - 카드번호 Luhn 검증 (RC 1=체크섬 불일치). */
void VL_CARD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char pan[32];
long rc;
gets_(b, T_STR1, pan, sizeof(pan));
if (pan[0] == 0) { setl(b, T_RC, 2); OK(b); } /* 2=번호 없음 */
rc = acq_luhn_ok(pan) ? 0 : 1;
setl(b, T_RC, rc);
OK(b);
}
/* 3. VL_LIMIT - 매입금액 vs 가맹점 일한도 검증 (RC 1=초과). */
void VL_LIMIT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_amt, h_lim;
char h_merch[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
h_amt = getl(b, T_AMOUNT);
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=가맹점 없음 */
if (h_amt > h_lim) rc = 1;
setl(b, T_RC, rc);
OK(b);
}
/* 4. VL_AMOUNT - 금액 유효성(0<amt<=규칙 AMT_MAX) 검증 (RC 1=0/음수,2=상한초과). */
void VL_AMOUNT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long amt, rc = 0, thr = 50000000;
char sev[8];
amt = getl(b, T_AMOUNT);
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) thr = 50000000;
if (amt <= 0) rc = 1;
else if (amt > thr) rc = 2;
setl(b, T_RC, rc);
setl(b, T_ARG1, thr);
OK(b);
}
/* 5. VL_ANOMALY - 이상거래 탐지 후 의심거래 적재 (dbio). */
void VL_ANOMALY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long rc = 0, sid = 0, thr = 50000000;
char sev[8];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) { thr = 50000000; strcpy(sev, "ERROR"); }
if (h_amt > thr) {
rc = 1;
sid = vldb_insert_suspect(h_pid, "AMT_MAX", sev, "고액 이상거래", bizdate);
if (sid < 0) FAIL(b);
}
setl(b, T_RC, rc);
setl(b, T_ID1, sid);
OK(b);
}
/* 6. VL_REQUIRED - 필수필드(가맹점/금액) 존재 검증 (RC 비트합). */
void VL_REQUIRED(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long amt, rc = 0;
gets_(b, T_MERCHANT, merch, sizeof(merch));
amt = getl(b, T_AMOUNT);
if (merch[0] == 0) rc |= 1;
if (amt == 0) rc |= 2;
setl(b, T_RC, rc);
OK(b);
}
/* 7. VL_CODEVAL - 공통코드 유효성 검증 vs mm_code (RC 1=미존재). */
void VL_CODEVAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char grp[32], code[32];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_grp[32], h_code[32];
EXEC SQL END DECLARE SECTION;
gets_(b, T_STR3, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CARD_TYPE");
gets_(b, T_KEY1, code, sizeof(code));
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_code
WHERE code_group = :h_grp AND code = :h_code AND status = 'ACTIVE';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) rc = 1;
setl(b, T_RC, rc);
OK(b);
}
/* 8. VL_APPR_ACQ - 승인-매입 매칭 검증 (RC 1=승인 없음). */
void VL_APPR_ACQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval
WHERE purchase_id = :h_pid AND approved = true;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) rc = 1;
setl(b, T_COUNT, h_cnt);
setl(b, T_RC, rc);
OK(b);
}
/* 9. VL_DUP - 중복 매입 검증 (동일 가맹점/금액/영업일 2건 이상 → RC 1). */
void VL_DUP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_amt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_amt = getl(b, T_AMOUNT);
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase
WHERE merchant_id = :h_merch AND amount = :h_amt AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 1) rc = 1;
setl(b, T_COUNT, h_cnt);
setl(b, T_RC, rc);
OK(b);
}
/* 10. VL_DATE - 영업일 유효성(미래일자 금지) 검증 (RC 1=미래). */
void VL_DATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_future;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (bizdate[0] == 0) { setl(b, T_RC, 2); OK(b); } /* 2=일자 없음 */
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT CASE WHEN :h_bizdate::date > current_date THEN 1 ELSE 0 END INTO :h_future;
if (sqlca.sqlcode < 0) { setl(b, T_RC, 3); OK(b); } /* 3=형식 오류 */
if (h_future) rc = 1;
setl(b, T_RC, rc);
OK(b);
}
/* 11. VL_FEE - 수수료 재계산 정합 검증 (저장 fee vs MDR 재계산, RC 1=불일치). */
void VL_FEE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0, recompute;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amount, h_fee, h_bps;
char h_merch[64];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT amount, fee, merchant_id INTO :h_amount, :h_fee, :h_merch
FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
EXEC SQL SELECT mdr_bps INTO :h_bps FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) h_bps = 25;
recompute = acq_fee(h_amount, h_bps);
if (recompute != h_fee) rc = 1;
setl(b, T_FEE, recompute);
setl(b, T_DELTA, h_fee - recompute);
setl(b, T_RC, rc);
OK(b);
}
/* 12. VL_SETTLE - 정산금액 정합 검증 (settlement.net vs purchase.net, RC 1=불일치). */
void VL_SETTLE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_pnet, h_snet, h_scnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_scnt, :h_snet
FROM settlement WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_scnt == 0) rc = 2; /* 정산 미존재 */
else if (h_snet != h_pnet) rc = 1; /* 금액 불일치 */
setl(b, T_NET, h_pnet);
setl(b, T_RC, rc);
OK(b);
}
/* 13. VL_LEDGER - 원장 합계 정합 검증 (sum(ledger) vs purchase.net, RC 1=불일치). */
void VL_LEDGER(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_pnet, h_lsum, h_lcnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_lcnt, :h_lsum
FROM ledger WHERE purchase_id = :h_pid AND entry_type = 'SETTLELINK';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_lcnt == 0) rc = 2; /* 원장 미기록 */
else if (h_lsum != h_pnet) {
rc = 1;
vldb_insert_suspect(h_pid, "LEDGER_MIS", "CRIT", "원장 합계 불일치", bizdate);
}
setl(b, T_GROSS, h_lsum);
setl(b, T_RC, rc);
OK(b);
}
/* 14. VL_FORMAT - 가맹점ID 형식(길이 4~16) 검증 (RC 1=형식 위반). */
void VL_FORMAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long rc = 0, len;
gets_(b, T_MERCHANT, merch, sizeof(merch));
len = (long)strlen(merch);
if (len < 4 || len > 16) rc = 1;
setl(b, T_COUNT, len);
setl(b, T_RC, rc);
OK(b);
}
/* 15. VL_REFINTEG - 참조무결성(purchase.merchant_id ∈ merchant) 검증. */
void VL_REFINTEG(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase p
WHERE p.purchase_id = :h_pid
AND NOT EXISTS (SELECT 1 FROM merchant m WHERE m.merchant_id = p.merchant_id);
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 0) rc = 1; /* 참조 깨짐 */
setl(b, T_RC, rc);
OK(b);
}
/* 16. VL_DUPAUTH - 중복 승인 검증 (approval 2건 이상 → RC 1). */
void VL_DUPAUTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 1) rc = 1;
setl(b, T_COUNT, h_cnt);
setl(b, T_RC, rc);
OK(b);
}
/* 17. VL_BIN - BIN 유효성 검증 vs mm_bin (RC 1=미등록,2=비활성). */
void VL_BIN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bin[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_active;
char h_bin[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, bin, sizeof(bin));
strncpy(h_bin, bin, sizeof(h_bin)-1); h_bin[sizeof(h_bin)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END),0)
INTO :h_cnt, :h_active FROM mm_bin WHERE bin = :h_bin;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) rc = 1;
else if (h_active == 0) rc = 2;
setl(b, T_RC, rc);
OK(b);
}
/* 18. VL_EXPIRY - 카드 유효기간(YYYYMM) vs 영업일 검증 (RC 1=만료). */
void VL_EXPIRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char expiry[8], bizdate[16];
long rc = 0, exp_ym, biz_ym, y, m, d;
gets_(b, T_STR2, expiry, sizeof(expiry));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (strlen(expiry) < 6 || sscanf(bizdate, "%ld-%ld-%ld", &y, &m, &d) != 3) {
setl(b, T_RC, 2); OK(b); /* 2=형식 오류 */
}
exp_ym = atol(expiry); /* YYYYMM */
biz_ym = y * 100 + m;
if (exp_ym < biz_ym) rc = 1;
setl(b, T_RC, rc);
OK(b);
}
/* 19. VL_TOLERANCE - 금액 허용오차(amount == fee+net ± tol) 검증 (RC 1=초과). */
void VL_TOLERANCE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0, tol, diff;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amount, h_fee, h_net;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
tol = getl(b, T_ARG1); if (tol < 0) tol = 0;
EXEC SQL SELECT amount, fee, net INTO :h_amount, :h_fee, :h_net
FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
diff = h_amount - (h_fee + h_net);
if (diff < 0) diff = -diff;
if (diff > tol) rc = 1;
setl(b, T_DELTA, diff);
setl(b, T_RC, rc);
OK(b);
}
/* 20. VL_MERCH - 가맹점 상태 검증 (merchant.status != ACTIVE → RC 1). */
void VL_MERCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_status[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
EXEC SQL SELECT status INTO :h_status FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 2); OK(b); }
if (strcmp(h_status, "ACTIVE") != 0) rc = 1;
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_RC, rc);
OK(b);
}
/* 21. VL_STATUS_CHK - 매입 상태 유효값 검증 (허용집합 밖 → RC 1). */
void VL_STATUS_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
char h_status[8];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT status INTO :h_status FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
EXEC SQL SELECT count(*) INTO :h_cnt FROM (SELECT :h_status AS s) t
WHERE t.s IN ('A','M','S','C','H','V');
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt == 0) rc = 1; /* 허용되지 않은 상태값 */
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_RC, rc);
OK(b);
}
/* 22. VL_BALANCE - 가맹점 순액 대차 검증 (sum(purchase.net) vs sum(settlement.net)). */
void VL_BALANCE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pnet, h_snet;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'S';
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_pnet != h_snet) rc = 1;
setl(b, T_GROSS, h_pnet);
setl(b, T_NET, h_snet);
setl(b, T_DELTA, h_pnet - h_snet);
setl(b, T_RC, rc);
OK(b);
}
/* 23. VL_SUMMARY - 당일 미결(OPEN) 의심거래 요약 (dbio). */
void VL_SUMMARY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long cnt;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
cnt = vldb_suspect_count(bizdate);
if (cnt < 0) FAIL(b);
setl(b, T_COUNT, cnt);
setl(b, T_RC, cnt > 0 ? 1 : 0);
OK(b);
}
/* 24. VL_REPORT - 심각도별 의심거래 건수 리포트. */
void VL_REPORT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char sev[8], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_sev[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_STR4, sev, sizeof(sev)); if (sev[0] == 0) strcpy(sev, "CRIT");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_sev, sev, sizeof(h_sev)-1); h_sev[sizeof(h_sev)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect
WHERE severity = :h_sev AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
OK(b);
}
/* 25. VL_BATCH_CHK - 배치 정합(purchase 건수 vs merchant_summary.txn_count). */
void VL_BATCH_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pcnt, h_scnt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_pcnt FROM purchase WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT coalesce(sum(txn_count),0) INTO :h_scnt FROM merchant_summary
WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_pcnt != h_scnt) rc = 1; /* 집계 누락/불일치 */
setl(b, T_COUNT, h_pcnt);
setl(b, T_GROSS, h_scnt);
setl(b, T_RC, rc);
OK(b);
}
/* 26. VL_THRESHOLD - 규칙 임계치 검사 (dbio get_rule; amount>threshold → RC 1). */
void VL_THRESHOLD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char rule[32], sev[8];
long amt, thr = 0, rc = 0;
gets_(b, T_STR3, rule, sizeof(rule)); if (rule[0] == 0) strcpy(rule, "AMT_MAX");
amt = getl(b, T_AMOUNT);
if (vldb_get_rule(rule, &thr, sev) < 0) { setl(b, T_RC, 3); OK(b); } /* 3=규칙 없음 */
if (thr > 0 && amt > thr) rc = 1;
setl(b, T_ARG1, thr);
Bchg(b, T_STR4, 0, sev, 0L);
setl(b, T_RC, rc);
OK(b);
}
/* 27. VL_RULE - 검증 규칙 등록/갱신 (dbio upsert). */
void VL_RULE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char rule[32], name[64], sev[8];
long thr;
gets_(b, T_STR3, rule, sizeof(rule));
if (rule[0] == 0) { userlog("VL_RULE: T_STR3(rule_code) 누락"); FAIL(b); }
gets_(b, T_STR1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "사용자 규칙");
gets_(b, T_STR4, sev, sizeof(sev)); if (sev[0] == 0) strcpy(sev, "WARN");
thr = getl(b, T_ARG1);
if (vldb_upsert_rule(rule, name, thr, sev) < 0) FAIL(b);
OK(b);
}
/* 28. VL_CROSSCHK - 교차검증(매입+가맹점+승인 동시 존재, RC 비트합). */
void VL_CROSSCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rc = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_pcnt, h_mcnt, h_acnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*) INTO :h_pcnt FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*) INTO :h_mcnt FROM purchase p
JOIN merchant m ON m.merchant_id = p.merchant_id WHERE p.purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*) INTO :h_acnt FROM approval WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_pcnt == 0) rc |= 1; /* 매입 없음 */
if (h_mcnt == 0) rc |= 2; /* 가맹점 참조 깨짐 */
if (h_acnt == 0) rc |= 4; /* 승인 없음 */
setl(b, T_RC, rc);
OK(b);
}
/* 29. VL_STATUS - 서비스 상태 (전체 OPEN 의심거래 수, RC=0). */
void VL_STATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect WHERE status = 'OPEN';
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_RC, 0);
Bchg(b, T_MSG, 0, "vl 검증 서비스 정상", 0L);
OK(b);
}
/* 30. VL_CONFIRM - 의심거래 종결 확정 (status CLOSED by suspect_id). */
void VL_CONFIRM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_sid;
EXEC SQL END DECLARE SECTION;
h_sid = getl(b, T_ID1);
EXEC SQL UPDATE vl_suspect SET status = 'CLOSED'
WHERE suspect_id = :h_sid AND status = 'OPEN';
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); }
setl(b, T_RC, 0);
OK(b);
}
/* vim: set ts=4 sw=4 et smartindent: */