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:
parent
8219b85f14
commit
bec83c721e
73 changed files with 10705 additions and 35 deletions
62
app/src/vl/batch/vl_anomaly_batch.pgc
Normal file
62
app/src/vl/batch/vl_anomaly_batch.pgc
Normal 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;
|
||||
}
|
||||
64
app/src/vl/batch/vl_sales_batch.pgc
Normal file
64
app/src/vl/batch/vl_sales_batch.pgc
Normal 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;
|
||||
}
|
||||
92
app/src/vl/dbio/vl_check_dbio.pgc
Normal file
92
app/src/vl/dbio/vl_check_dbio.pgc
Normal 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
27
app/src/vl/dbio/vl_dbio.h
Normal 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 */
|
||||
51
app/src/vl/dbio/vl_rule_dbio.pgc
Normal file
51
app/src/vl/dbio/vl_rule_dbio.pgc
Normal 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
674
app/src/vl/vl_svr.pgc
Normal 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: */
|
||||
Loading…
Add table
Add a link
Reference in a new issue