규모확장 Wave A: 전 11모듈 svc 30→75·batch 22→35 + 전 모듈 운영데이터 시드
- svc 330→825 (모듈당 +45 고유 실업무: chargeback·재매입·심사워크플로·전문유형별· 대사세분·정산시뮬·지급재처리·복식결산·마감워크플로·마스터심사·검증룰·전문라우팅 등) - batch 242→385 (모듈당 +13), 카피북 헤더·디스패처 advertise 전 75 반영 - db/schema.d/95-module-seed.sql: 전 모듈 테이블 운영데이터(au 8.4k·st 15.9k·mg 16.9k· lg 14.6k·rc 7.7k·py 2.9k·vl 8.5k·cm 10.4k 등, 총 ~171k행) — base 거래와 정합 - 화면 매니페스트 825 재생성, 큐 메모리 튜닝(NDRX_MSGMAX 50→30) - 검증: build DONE modules=11 batches=385, 12서버 runok, xadmin psc 828 AVAIL, 매입체인 XA 커밋(status=S), 새서비스 시드데이터 스모크(ACQ_MERCHSUM/RC_STATS 실값), 전 코퍼스 .pgc 클론 0, 헤드리스 스크린샷 확인 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
19acf4ed96
commit
ea90bef264
1292 changed files with 53968 additions and 20 deletions
912
db/schema.d/95-module-seed.sql
Normal file
912
db/schema.d/95-module-seed.sql
Normal file
|
|
@ -0,0 +1,912 @@
|
|||
-- ===========================================================================
|
||||
-- 95-module-seed.sql : 전 모듈 운영 이력 시드 (90-seed 기반 파생)
|
||||
-- 90-seed 의 purchase/settlement/merchant 이력에서 au/py/rc/st/lg/cl/mm/vl/
|
||||
-- mg/cm (+ac 상세) 모듈 테이블을 실운영계처럼 파생 적재한다.
|
||||
-- - 결정적(setseed) / 재실행 안전(ON CONFLICT DO NOTHING)
|
||||
-- - docker-entrypoint-initdb.d 에서 90-seed 직후 1회 실행
|
||||
-- ===========================================================================
|
||||
SELECT setseed(0.77);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 0. 공통 파생 베이스: 매입 + 카드번호/채널 매핑
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TEMP TABLE ms_px AS
|
||||
SELECT p.purchase_id, p.merchant_id, p.amount, p.fee, p.net, p.status,
|
||||
p.biz_date, p.channel, p.issuer, p.created_at,
|
||||
m.mdr_bps,
|
||||
(CASE p.issuer
|
||||
WHEN 'BC' THEN '457301' WHEN 'KB' THEN '945712'
|
||||
WHEN 'SHINHAN' THEN '451845' WHEN 'HYUNDAI' THEN '433428'
|
||||
WHEN 'SAMSUNG' THEN '537740' WHEN 'LOTTE' THEN '438676'
|
||||
WHEN 'NH' THEN '546551' ELSE '523430'
|
||||
END) || lpad(((p.purchase_id * 7919) % 2400)::text, 10, '0') AS card_no,
|
||||
CASE p.channel WHEN 'POS' THEN 'CH01' WHEN 'EDC' THEN 'CH02'
|
||||
WHEN 'EDI' THEN 'CH02' ELSE 'CH03' END AS mg_chan
|
||||
FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id;
|
||||
|
||||
-- 가맹점-정산일 단위 net 집계 (지급/전표/정산 파생의 공통 소스)
|
||||
CREATE TEMP TABLE ms_ms AS
|
||||
SELECT row_number() OVER (ORDER BY s.biz_date, s.merchant_id) AS rn,
|
||||
dense_rank() OVER (ORDER BY s.biz_date) AS day_rn,
|
||||
s.merchant_id, s.biz_date AS settle_date,
|
||||
(s.biz_date + 1)::date AS pay_date,
|
||||
min(s.settlement_id) AS ref_sid,
|
||||
count(*) AS cnt,
|
||||
sum(p.amount) AS gross_sum,
|
||||
sum(p.fee) AS fee_sum,
|
||||
sum(s.net) AS net_sum
|
||||
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
|
||||
GROUP BY s.merchant_id, s.biz_date;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 1. au 모듈 (승인/한도/부정거래)
|
||||
-- ===========================================================================
|
||||
-- 승인 원장: 매입 1:1 미러 (같은 가맹점/금액/일자, 상태 도메인 동일)
|
||||
INSERT INTO au_authorization (auth_id, card_no, merchant_id, amount, approval_code,
|
||||
auth_type, status, decline_reason, biz_date,
|
||||
captured_at, created_at, updated_at)
|
||||
SELECT purchase_id, card_no, merchant_id, amount,
|
||||
lpad(((purchase_id * 37) % 100000000)::text, 8, '0'),
|
||||
CASE WHEN purchase_id % 53 = 0 THEN 'PREAUTH'
|
||||
WHEN purchase_id % 11 = 0 AND amount >= 500000 THEN 'INSTALL'
|
||||
WHEN channel = 'FOREIGN' THEN 'STANDIN'
|
||||
ELSE 'AUTH' END,
|
||||
status,
|
||||
'',
|
||||
biz_date,
|
||||
CASE WHEN status IN ('M','S') THEN created_at + interval '70 minutes' END,
|
||||
created_at, created_at + interval '2 minutes'
|
||||
FROM ms_px
|
||||
ON CONFLICT (auth_id) DO NOTHING;
|
||||
|
||||
-- 거절 건 (~2%): 별도 auth_id 대역, 사유 코드 부여 → 승인율 98%
|
||||
INSERT INTO au_authorization (auth_id, card_no, merchant_id, amount, approval_code,
|
||||
auth_type, status, decline_reason, biz_date, created_at, updated_at)
|
||||
SELECT 500000 + row_number() OVER (ORDER BY purchase_id),
|
||||
card_no, merchant_id, (amount * 2) / 100 * 100 + 900, '',
|
||||
'AUTH', 'D',
|
||||
(ARRAY['LIMIT_EXCEEDED','INSUFFICIENT_FUNDS','INVALID_CARD','SUSPECT_FRAUD'])[purchase_id % 4 + 1],
|
||||
biz_date, created_at + interval '31 minutes', created_at + interval '31 minutes'
|
||||
FROM ms_px WHERE purchase_id % 48 = 0
|
||||
ON CONFLICT (auth_id) DO NOTHING;
|
||||
|
||||
-- 카드 한도: 시드 카드 전체 등록 (금일 사용액 = 최근 영업일 승인합)
|
||||
INSERT INTO card_limit (card_no, daily_limit, used_amount, temp_limit, status, updated_at)
|
||||
SELECT card_no,
|
||||
CASE (('x' || substr(md5(card_no), 1, 6))::bit(24)::int) % 4
|
||||
WHEN 0 THEN 1000000 WHEN 1 THEN 3000000 WHEN 2 THEN 5000000 ELSE 10000000 END,
|
||||
coalesce(sum(amount) FILTER (WHERE biz_date >= '2026-07-19'), 0),
|
||||
0, 'ACTIVE', now()
|
||||
FROM ms_px GROUP BY card_no
|
||||
ON CONFLICT (card_no) DO NOTHING;
|
||||
|
||||
-- 부정거래 로그: 고액(>=300만) + 다건(velocity) 이상거래
|
||||
INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id),
|
||||
purchase_id, card_no,
|
||||
700 + (purchase_id % 280),
|
||||
CASE WHEN amount >= 3000000 THEN 'HIGH_AMOUNT' ELSE 'VELOCITY' END,
|
||||
biz_date, created_at + interval '1 minute'
|
||||
FROM ms_px WHERE amount >= 3000000 OR purchase_id % 197 = 0
|
||||
ON CONFLICT (fraud_id) DO NOTHING;
|
||||
|
||||
-- 예비승인: 일부 거래는 PREAUTH 선행 (과거=확정 C, 최근=P, 극소수 취소 X)
|
||||
INSERT INTO au_preauth (preauth_id, card_no, merchant_id, amount, status, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id),
|
||||
card_no, merchant_id, amount,
|
||||
CASE WHEN purchase_id % 371 = 0 THEN 'X'
|
||||
WHEN biz_date < '2026-07-15' THEN 'C' ELSE 'P' END,
|
||||
biz_date, created_at - interval '2 hours'
|
||||
FROM ms_px WHERE purchase_id % 53 = 0
|
||||
ON CONFLICT (preauth_id) DO NOTHING;
|
||||
|
||||
-- 승인 취소: 매입 취소(C)/무효(V) 건 연동
|
||||
INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id),
|
||||
purchase_id,
|
||||
CASE status WHEN 'V' THEN 'VOID'
|
||||
ELSE CASE WHEN purchase_id % 5 = 0 THEN 'PARTIAL' ELSE 'FULL' END END,
|
||||
CASE WHEN status <> 'V' AND purchase_id % 5 = 0 THEN amount / 2 / 100 * 100 ELSE amount END,
|
||||
(ARRAY['고객 요청 취소','상품 반품','중복 승인 취소','가맹점 요청'])[purchase_id % 4 + 1],
|
||||
biz_date, created_at + interval '3 hours'
|
||||
FROM ms_px WHERE status IN ('C','V')
|
||||
ON CONFLICT (cancel_id) DO NOTHING;
|
||||
|
||||
-- 추가 블랙리스트
|
||||
INSERT INTO au_blacklist (card_no, reason) VALUES
|
||||
('4573010000000777', 'CHARGEBACK_ABUSE'),
|
||||
('5377400000001313', 'CONFIRMED_FRAUD'),
|
||||
('4518450000002099', 'CARD_LOST')
|
||||
ON CONFLICT (card_no) DO NOTHING;
|
||||
|
||||
-- 가맹점 일별 승인 집계
|
||||
INSERT INTO au_summary (merchant_id, biz_date, auth_count, approve_count, decline_count, auth_amount)
|
||||
SELECT merchant_id, biz_date, count(*),
|
||||
count(*) FILTER (WHERE status <> 'D'),
|
||||
count(*) FILTER (WHERE status = 'D'),
|
||||
coalesce(sum(amount) FILTER (WHERE status <> 'D'), 0)
|
||||
FROM au_authorization GROUP BY merchant_id, biz_date
|
||||
ON CONFLICT (merchant_id, biz_date) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 2. py 모듈 (지급) - 정산 T+1 지급
|
||||
-- ===========================================================================
|
||||
-- 지급계좌: 전 가맹점 (기존 M0001~3 시드 유지)
|
||||
INSERT INTO py_account (account_no, merchant_id, bank_code, holder, balance, status, updated_at)
|
||||
SELECT lpad((100 + n)::text, 3, '0') || '-' ||
|
||||
lpad(((n * 1237) % 10000)::text, 4, '0') || '-' ||
|
||||
lpad(((n * 98717) % 1000000)::text, 6, '0'),
|
||||
m.merchant_id,
|
||||
(ARRAY['004','011','020','081','088','090'])[n % 6 + 1],
|
||||
m.name,
|
||||
((n * 7654321) % 400000000) + 5000000,
|
||||
CASE m.status WHEN 'ACTIVE' THEN 'ACTIVE' ELSE 'HOLD' END,
|
||||
now() - interval '30 days'
|
||||
FROM merchant m CROSS JOIN LATERAL (SELECT substr(m.merchant_id, 2)::int AS n) v
|
||||
ON CONFLICT (account_no) DO NOTHING;
|
||||
|
||||
-- 지급 파일: 지급일별 1파일 (과거=확정 C, 최근=생성 G)
|
||||
INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status, created_at, confirmed_at)
|
||||
SELECT day_rn, pay_date,
|
||||
'PAYOUT_' || to_char(pay_date, 'YYYYMMDD') || '_01.dat',
|
||||
count(*), sum(net_sum),
|
||||
CASE WHEN pay_date < '2026-07-19' THEN 'C' ELSE 'G' END,
|
||||
pay_date::timestamptz + interval '7 hours',
|
||||
CASE WHEN pay_date < '2026-07-19' THEN pay_date::timestamptz + interval '9 hours' END
|
||||
FROM ms_ms GROUP BY day_rn, pay_date
|
||||
ON CONFLICT (file_id) DO NOTHING;
|
||||
|
||||
-- 지급 원장: 가맹점-정산일 합산 → T+1 지급, 이체수수료 500원, ~1.6% 반려/재처리
|
||||
INSERT INTO py_payout (payout_id, merchant_id, account_no, amount, fee, net, status,
|
||||
channel, file_id, retry_count, reject_reason, biz_date, created_at, updated_at)
|
||||
SELECT 10000 + ms.rn, ms.merchant_id,
|
||||
coalesce(a.account_no, ''),
|
||||
ms.net_sum, 500, ms.net_sum - 500,
|
||||
CASE WHEN ms.rn % 61 = 0 THEN 'J'
|
||||
WHEN ms.pay_date < '2026-07-18' THEN 'P'
|
||||
WHEN ms.pay_date = '2026-07-18' THEN 'F'
|
||||
WHEN ms.pay_date = '2026-07-19' THEN 'A'
|
||||
ELSE 'R' END,
|
||||
CASE WHEN ms.rn % 29 = 0 THEN 'BULK'
|
||||
WHEN ms.rn % 13 = 0 THEN 'ADVANCE' ELSE 'STD' END,
|
||||
ms.day_rn,
|
||||
CASE WHEN ms.rn % 61 = 0 THEN 1 + ms.rn % 2 ELSE 0 END,
|
||||
CASE WHEN ms.rn % 61 = 0
|
||||
THEN (ARRAY['계좌번호 오류','예금주 상이','지급정지 계좌'])[ms.rn % 3 + 1]
|
||||
ELSE '' END,
|
||||
ms.pay_date,
|
||||
ms.pay_date::timestamptz + interval '7 hours',
|
||||
ms.pay_date::timestamptz + interval '10 hours'
|
||||
FROM ms_ms ms
|
||||
LEFT JOIN LATERAL (SELECT account_no FROM py_account a
|
||||
WHERE a.merchant_id = ms.merchant_id
|
||||
ORDER BY account_no LIMIT 1) a ON true
|
||||
ON CONFLICT (payout_id) DO NOTHING;
|
||||
|
||||
-- 지급 상세원장: 완료건 REQ+PAID 복식 기록
|
||||
INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY payout_id, et.ord),
|
||||
payout_id, et.entry_type,
|
||||
CASE et.entry_type WHEN 'REQ' THEN amount ELSE net END,
|
||||
biz_date, updated_at
|
||||
FROM py_payout
|
||||
CROSS JOIN (VALUES (1, 'REQ'), (2, 'PAID')) AS et(ord, entry_type)
|
||||
WHERE status = 'P'
|
||||
ON CONFLICT (ledger_id) DO NOTHING;
|
||||
|
||||
-- 지급 예약: 미완료(요청/승인/파일편성)건 스케줄
|
||||
INSERT INTO py_schedule (schedule_id, payout_id, run_date, status, created_at)
|
||||
SELECT row_number() OVER (ORDER BY payout_id), payout_id, biz_date,
|
||||
CASE WHEN status = 'R' THEN 'S' ELSE 'D' END, created_at
|
||||
FROM py_payout WHERE status IN ('R','A','F')
|
||||
ON CONFLICT (schedule_id) DO NOTHING;
|
||||
|
||||
-- 지급 조정: 소수 가감 조정
|
||||
INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY payout_id), payout_id,
|
||||
CASE WHEN payout_id % 2 = 0 THEN 1 ELSE -1 END * ((payout_id % 7 + 1) * 1000),
|
||||
(ARRAY['수수료 정정 가감','반품 상계','민원 보상 지급'])[payout_id % 3 + 1],
|
||||
biz_date, updated_at
|
||||
FROM py_payout WHERE payout_id % 199 = 0
|
||||
ON CONFLICT (adj_id) DO NOTHING;
|
||||
|
||||
-- 가맹점 일별 지급 집계
|
||||
INSERT INTO py_summary (merchant_id, biz_date, payout_count, payout_amount)
|
||||
SELECT merchant_id, biz_date, count(*), sum(net)
|
||||
FROM py_payout GROUP BY merchant_id, biz_date
|
||||
ON CONFLICT (merchant_id, biz_date) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 3. rc 모듈 (대사)
|
||||
-- ===========================================================================
|
||||
-- 대사 로그: 대사완료(M)/정산완료(S) = MATCHED, 보류(H) = HOLD, ~1% 예외
|
||||
INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date, created_at, updated_at)
|
||||
SELECT purchase_id, purchase_id,
|
||||
CASE WHEN status = 'H' THEN 'DELAY'
|
||||
WHEN channel = 'EDI' THEN '3WAY'
|
||||
WHEN purchase_id % 5 = 0 THEN 'ACQ_DEP'
|
||||
ELSE 'APPR_ACQ' END,
|
||||
amount,
|
||||
CASE WHEN status = 'H' THEN 'HOLD'
|
||||
WHEN purchase_id % 97 = 0 THEN 'EXCEPTION'
|
||||
WHEN status = 'S' AND purchase_id % 3 = 0 THEN 'CONFIRMED'
|
||||
ELSE 'MATCHED' END,
|
||||
biz_date,
|
||||
biz_date::timestamptz + interval '19 hours',
|
||||
biz_date::timestamptz + interval '19 hours 30 minutes'
|
||||
FROM ms_px WHERE status IN ('M','S','H')
|
||||
ON CONFLICT (match_id) DO NOTHING;
|
||||
|
||||
-- 대사 예외: EXCEPTION 건 사유 적재 (과거 건은 해소 처리)
|
||||
INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date, resolved, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
(ARRAY['매입-승인 금액 불일치','입금액 부족','승인건 누락','중복 매입 접수'])[purchase_id % 4 + 1],
|
||||
biz_date, biz_date < '2026-07-01',
|
||||
biz_date::timestamptz + interval '20 hours'
|
||||
FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0
|
||||
ON CONFLICT (exc_id) DO NOTHING;
|
||||
|
||||
-- 대사 차이: 예외건 기대/실제 금액 차이
|
||||
INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
amount, amount - (purchase_id % 5 + 1) * 100, (purchase_id % 5 + 1) * 100,
|
||||
CASE WHEN purchase_id % 3 = 0 THEN 'CNT' ELSE 'AMT' END,
|
||||
biz_date, biz_date::timestamptz + interval '20 hours'
|
||||
FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0
|
||||
ON CONFLICT (diff_id) DO NOTHING;
|
||||
|
||||
-- 대사 경보: 예외건 등급별 경보
|
||||
INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
CASE WHEN amount >= 1000000 THEN 'CRIT' ELSE 'WARN' END,
|
||||
'대사 예외 발생: ' || merchant_id || ' 금액 ' || amount || '원',
|
||||
biz_date, biz_date::timestamptz + interval '20 hours 5 minutes'
|
||||
FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0
|
||||
ON CONFLICT (alert_id) DO NOTHING;
|
||||
|
||||
-- 대사 일별 요약
|
||||
INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount)
|
||||
SELECT biz_date,
|
||||
count(*) FILTER (WHERE status IN ('MATCHED','CONFIRMED')),
|
||||
count(*) FILTER (WHERE status IN ('EXCEPTION','HOLD')),
|
||||
sum(amount)
|
||||
FROM rc_match_log GROUP BY biz_date
|
||||
ON CONFLICT (biz_date) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 4. st 모듈 (정산/수수료)
|
||||
-- ===========================================================================
|
||||
-- 정산 상세: 정산건별 수수료 분해 (MDR + VAN, EDI 는 중계 RELAY 추가)
|
||||
INSERT INTO st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY s.settlement_id, ft.ord),
|
||||
s.settlement_id, s.purchase_id, s.merchant_id, ft.fee_type,
|
||||
CASE ft.fee_type WHEN 'MDR' THEN p.fee
|
||||
WHEN 'VAN' THEN p.amount * 10 / 10000
|
||||
ELSE p.amount * 5 / 10000 END,
|
||||
s.biz_date, s.settled_at
|
||||
FROM settlement s
|
||||
JOIN purchase p ON p.purchase_id = s.purchase_id
|
||||
CROSS JOIN (VALUES (1, 'MDR'), (2, 'VAN'), (3, 'RELAY')) AS ft(ord, fee_type)
|
||||
WHERE ft.fee_type <> 'RELAY' OR p.channel = 'EDI'
|
||||
ON CONFLICT (dtl_id) DO NOTHING;
|
||||
|
||||
-- 수수료 구간 요율 보강 (기존 시드 유지)
|
||||
INSERT INTO st_fee_rate (fee_type, band_min, rate_bps) VALUES
|
||||
('MDR', 50000000, 18),
|
||||
('VAN', 10000000, 8),
|
||||
('WHT', 0, 220),
|
||||
('DELAYINT', 0, 30)
|
||||
ON CONFLICT (fee_type, band_min) DO NOTHING;
|
||||
|
||||
-- 정산 조정: 소수 가감/상계
|
||||
INSERT INTO st_adjust (adj_id, settlement_id, purchase_id, delta, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY settlement_id), settlement_id, purchase_id,
|
||||
CASE WHEN settlement_id % 2 = 0 THEN 1 ELSE -1 END * ((settlement_id % 9 + 1) * 500),
|
||||
(ARRAY['요율 소급 정정','부분취소 상계','반올림 차액 조정','민원 보상'])[settlement_id % 4 + 1],
|
||||
biz_date, settled_at + interval '2 hours'
|
||||
FROM settlement WHERE settlement_id % 173 = 0
|
||||
ON CONFLICT (adj_id) DO NOTHING;
|
||||
|
||||
-- 가맹점 지급예정: 정산일 기준 T+1 지급 스케줄
|
||||
INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date, created_at)
|
||||
SELECT rn, merchant_id, net_sum, pay_date,
|
||||
CASE WHEN rn % 61 = 0 THEN 'HELD'
|
||||
WHEN pay_date < '2026-07-18' THEN 'PAID'
|
||||
ELSE 'SCHEDULED' END,
|
||||
settle_date, settle_date::timestamptz + interval '22 hours'
|
||||
FROM ms_ms
|
||||
ON CONFLICT (pay_id) DO NOTHING;
|
||||
|
||||
-- 가맹점 정산 집계 (월집계는 ST_BYMERCH/배치 형식과 동일한 일집계 소스)
|
||||
INSERT INTO st_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount)
|
||||
SELECT merchant_id, settle_date, cnt, gross_sum, fee_sum, net_sum
|
||||
FROM ms_ms
|
||||
ON CONFLICT (merchant_id, biz_date) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 5. lg 모듈 (원장 - 복식부기)
|
||||
-- ===========================================================================
|
||||
-- 전표 1: 정산 전기 (SETTLE) - 차변 RECV(총액) = 대변 MERCH_PAY(순액)+FEE_INC(수수료)
|
||||
INSERT INTO lg_voucher (voucher_id, voucher_type, ref_id, biz_date, status, memo, created_at, updated_at)
|
||||
SELECT 1000 + rn * 2 - 1, 'SETTLE', ref_sid, settle_date, 'P',
|
||||
merchant_id || ' 일정산 전기 (' || cnt || '건)',
|
||||
settle_date::timestamptz + interval '21 hours',
|
||||
settle_date::timestamptz + interval '21 hours'
|
||||
FROM ms_ms
|
||||
ON CONFLICT (voucher_id) DO NOTHING;
|
||||
|
||||
-- 전표 2: 지급 이체 (TRANSFER) - 차변 MERCH_PAY = 대변 CASH
|
||||
INSERT INTO lg_voucher (voucher_id, voucher_type, ref_id, biz_date, status, memo, created_at, updated_at)
|
||||
SELECT 1000 + rn * 2, 'TRANSFER', ref_sid, pay_date,
|
||||
CASE WHEN pay_date < '2026-07-19' THEN 'P' ELSE 'D' END,
|
||||
merchant_id || ' 지급 이체 전기',
|
||||
pay_date::timestamptz + interval '10 hours',
|
||||
pay_date::timestamptz + interval '10 hours'
|
||||
FROM ms_ms
|
||||
ON CONFLICT (voucher_id) DO NOTHING;
|
||||
|
||||
-- 원장 라인: SETTLE 전표 3분개 + TRANSFER 전표 2분개 (차대변 균형)
|
||||
INSERT INTO lg_ledger_line (line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status, created_at)
|
||||
SELECT row_number() OVER (ORDER BY rn, ln.ord),
|
||||
CASE WHEN ln.ord <= 3 THEN 1000 + rn * 2 - 1 ELSE 1000 + rn * 2 END,
|
||||
ln.acct, ln.dc,
|
||||
CASE ln.ord WHEN 1 THEN gross_sum WHEN 2 THEN net_sum WHEN 3 THEN fee_sum
|
||||
WHEN 4 THEN net_sum ELSE net_sum END,
|
||||
merchant_id,
|
||||
CASE WHEN ln.ord <= 3 THEN settle_date ELSE pay_date END,
|
||||
CASE WHEN settle_date < '2026-07-01' THEN 'closed' ELSE 'posted' END,
|
||||
CASE WHEN ln.ord <= 3 THEN settle_date::timestamptz + interval '21 hours'
|
||||
ELSE pay_date::timestamptz + interval '10 hours' END
|
||||
FROM ms_ms
|
||||
CROSS JOIN (VALUES (1, 'RECV', 'D'), (2, 'MERCH_PAY', 'C'), (3, 'FEE_INC', 'C'),
|
||||
(4, 'MERCH_PAY', 'D'), (5, 'CASH', 'C')) AS ln(ord, acct, dc)
|
||||
WHERE fee_sum > 0 OR ln.ord <> 3
|
||||
ON CONFLICT (line_id) DO NOTHING;
|
||||
|
||||
-- 계정 잔액 (계정 x 영업일)
|
||||
INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT acct, biz_date,
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0),
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0),
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0) - coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0)
|
||||
FROM lg_ledger_line GROUP BY acct, biz_date
|
||||
ON CONFLICT (acct, biz_date) DO NOTHING;
|
||||
|
||||
-- 가맹점 잔액 (가맹점 x 영업일)
|
||||
INSERT INTO lg_merch_bal (merchant_id, biz_date, debit_total, credit_total, balance)
|
||||
SELECT merchant_id, biz_date,
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0),
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0),
|
||||
coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0) - coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0)
|
||||
FROM lg_ledger_line GROUP BY merchant_id, biz_date
|
||||
ON CONFLICT (merchant_id, biz_date) DO NOTHING;
|
||||
|
||||
-- 시산표 (계정 x 영업일; 전 계정 합산 시 차대변 균형)
|
||||
INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff)
|
||||
SELECT acct, biz_date, debit_total, credit_total, balance
|
||||
FROM lg_account_bal
|
||||
ON CONFLICT (acct, biz_date) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 6. cl 모듈 (마감)
|
||||
-- ===========================================================================
|
||||
-- 일마감: 매 영업일 DAILY (최종 영업일만 개시 상태)
|
||||
INSERT INTO cl_close_log (close_id, close_type, period_key, biz_date, status,
|
||||
txn_count, total_amount, memo, created_at, updated_at)
|
||||
SELECT row_number() OVER (ORDER BY biz_date), 'DAILY',
|
||||
to_char(biz_date, 'YYYY-MM-DD'), biz_date,
|
||||
CASE WHEN biz_date >= '2026-07-19' THEN 'O' ELSE 'C' END,
|
||||
count(*), sum(amount),
|
||||
CASE WHEN biz_date >= '2026-07-19' THEN '일마감 개시' ELSE '일마감 완료' END,
|
||||
biz_date::timestamptz + interval '23 hours',
|
||||
biz_date::timestamptz + interval '23 hours 30 minutes'
|
||||
FROM ms_px GROUP BY biz_date
|
||||
ON CONFLICT (close_id) DO NOTHING;
|
||||
|
||||
-- 월마감: 4~6월 잠금(L), 7월 진행중(O) + 2분기 마감
|
||||
INSERT INTO cl_close_log (close_id, close_type, period_key, biz_date, status,
|
||||
txn_count, total_amount, memo, created_at, updated_at)
|
||||
SELECT 200 + row_number() OVER (ORDER BY pk), ct, pk, bd::date, st, cnt, amt, memo,
|
||||
bd::timestamptz + interval '5 hours', bd::timestamptz + interval '6 hours'
|
||||
FROM (
|
||||
SELECT 'MONTHLY' AS ct, to_char(date_trunc('month', biz_date), 'YYYY-MM') AS pk,
|
||||
(date_trunc('month', biz_date) + interval '1 month')::date AS bd,
|
||||
CASE WHEN date_trunc('month', biz_date) < '2026-06-01' THEN 'L'
|
||||
WHEN date_trunc('month', biz_date) = '2026-06-01' THEN 'A'
|
||||
ELSE 'O' END AS st,
|
||||
count(*) AS cnt, sum(amount) AS amt,
|
||||
CASE WHEN date_trunc('month', biz_date) < '2026-07-01'
|
||||
THEN '월마감 완료·승인' ELSE '월마감 진행중' END AS memo
|
||||
FROM ms_px GROUP BY date_trunc('month', biz_date)
|
||||
UNION ALL
|
||||
SELECT 'QUARTER', '2026-Q2', '2026-07-01', 'C', count(*), sum(amount), '2분기 마감 완료'
|
||||
FROM ms_px WHERE biz_date < '2026-07-01'
|
||||
) q
|
||||
ON CONFLICT (close_id) DO NOTHING;
|
||||
|
||||
-- 마감 스냅샷: 일마감 x 가맹점 집계 고정 (merchant_summary 파생)
|
||||
INSERT INTO cl_snapshot (snapshot_id, close_id, period_key, biz_date, merchant_id,
|
||||
gross_amount, fee_amount, net_amount, txn_count, created_at)
|
||||
SELECT row_number() OVER (ORDER BY s.biz_date, s.merchant_id),
|
||||
c.close_id, c.period_key, s.biz_date, s.merchant_id,
|
||||
s.gross_amount, s.fee_amount, s.net_amount, s.txn_count,
|
||||
s.biz_date::timestamptz + interval '23 hours'
|
||||
FROM merchant_summary s
|
||||
JOIN cl_close_log c ON c.close_type = 'DAILY' AND c.biz_date = s.biz_date
|
||||
ON CONFLICT (snapshot_id) DO NOTHING;
|
||||
|
||||
-- 기간 잠금 + 마감 채번 레지스트리
|
||||
INSERT INTO cl_period_lock (period_key, locked) VALUES
|
||||
('2026-04', true), ('2026-05', true), ('2026-06', true), ('2026-Q2', true)
|
||||
ON CONFLICT (period_key) DO NOTHING;
|
||||
|
||||
INSERT INTO cl_seq_reg (close_type, last_seq)
|
||||
SELECT close_type, count(*) FROM cl_close_log GROUP BY close_type
|
||||
ON CONFLICT (close_type) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 7. mm 모듈 (마스터)
|
||||
-- ===========================================================================
|
||||
-- 가맹점 확장 마스터: 전 40개 (사업자번호/등급/정산계좌/업종)
|
||||
INSERT INTO mm_merchant_ext (merchant_id, name, biz_reg_no, grade, settle_acct, mcc, status, created_at, updated_at)
|
||||
SELECT m.merchant_id, m.name,
|
||||
((v.n * 7) % 600 + 101)::text || '-' || lpad(((v.n * 13) % 88 + 10)::text, 2, '0')
|
||||
|| '-' || lpad(((v.n * 54321) % 100000)::text, 5, '0'),
|
||||
CASE WHEN m.daily_limit >= 400000000 THEN 'S'
|
||||
WHEN m.daily_limit >= 150000000 THEN 'A'
|
||||
WHEN m.daily_limit >= 50000000 THEN 'B' ELSE 'C' END,
|
||||
coalesce(a.account_no, ''),
|
||||
(ARRAY['5814','5411','5912','5651','7230','5812','5541','5942','7997','8062',
|
||||
'5732','5992','4722','7011','8299'])[v.n % 15 + 1],
|
||||
CASE m.status WHEN 'ACTIVE' THEN 'ACTIVE' ELSE 'HOLD' END,
|
||||
m.created_at, m.created_at + interval '1 day'
|
||||
FROM merchant m
|
||||
CROSS JOIN LATERAL (SELECT substr(m.merchant_id, 2)::int AS n) v
|
||||
LEFT JOIN LATERAL (SELECT account_no FROM py_account a
|
||||
WHERE a.merchant_id = m.merchant_id ORDER BY account_no LIMIT 1) a ON true
|
||||
ON CONFLICT (merchant_id) DO NOTHING;
|
||||
|
||||
-- 가맹점 변경 이력: 전건 등록 + 일부 변경
|
||||
INSERT INTO mm_merch_hist (hist_id, merchant_id, action, before_val, after_val, memo, created_at)
|
||||
SELECT row_number() OVER (ORDER BY merchant_id, ord), merchant_id, action, before_val, after_val, memo, ts
|
||||
FROM (
|
||||
SELECT m.merchant_id, 1 AS ord, 'REG' AS action, '' AS before_val, 'ACTIVE' AS after_val,
|
||||
'신규 가맹점 등록' AS memo, m.created_at AS ts
|
||||
FROM merchant m
|
||||
UNION ALL
|
||||
SELECT m.merchant_id, 2, 'GRADE', 'B', 'A', '분기 실적 평가 승급', m.created_at + interval '90 days'
|
||||
FROM merchant m WHERE substr(m.merchant_id, 2)::int % 7 = 0
|
||||
UNION ALL
|
||||
SELECT 'M0039', 3, 'STATUS', 'ACTIVE', 'HOLD', '정산계좌 오류로 거래 보류', now() - interval '12 days'
|
||||
UNION ALL
|
||||
SELECT 'M0012', 3, 'ACCT', '(구)계좌', '(신)계좌', '지급계좌 변경 요청 처리', now() - interval '40 days'
|
||||
) h
|
||||
ON CONFLICT (hist_id) DO NOTHING;
|
||||
|
||||
-- 수수료율 마스터: 가맹점 x 신용/체크
|
||||
INSERT INTO mm_fee_rate (merchant_id, card_type, rate_bps, eff_date)
|
||||
SELECT m.merchant_id, ct.card_type,
|
||||
CASE ct.card_type WHEN 'CREDIT' THEN m.mdr_bps ELSE greatest(m.mdr_bps - 7, 10) END,
|
||||
'2026-01-02'
|
||||
FROM merchant m CROSS JOIN (VALUES ('CREDIT'), ('CHECK')) AS ct(card_type)
|
||||
ON CONFLICT (merchant_id, card_type) DO NOTHING;
|
||||
|
||||
-- 수수료율 변경 이력 (직전 요율 + 현행)
|
||||
INSERT INTO mm_fee_rate_hist (hist_id, merchant_id, card_type, rate_bps, eff_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY merchant_id, eff), merchant_id, 'CREDIT', bps, eff, eff::timestamptz
|
||||
FROM (
|
||||
SELECT m.merchant_id, m.mdr_bps + 3 AS bps, '2025-07-01'::date AS eff FROM merchant m
|
||||
UNION ALL
|
||||
SELECT m.merchant_id, m.mdr_bps, '2026-01-02' FROM merchant m
|
||||
) h
|
||||
ON CONFLICT (hist_id) DO NOTHING;
|
||||
|
||||
-- BIN 마스터 (승인 카드번호 채번과 일치)
|
||||
INSERT INTO mm_bin (bin, issuer_cd, card_brand, card_type, status) VALUES
|
||||
('457301', 'BC', 'VISA', 'CREDIT', 'ACTIVE'),
|
||||
('945712', 'KB', 'LOCAL', 'CREDIT', 'ACTIVE'),
|
||||
('451845', 'SHINHAN', 'VISA', 'CREDIT', 'ACTIVE'),
|
||||
('433428', 'HYUNDAI', 'VISA', 'CREDIT', 'ACTIVE'),
|
||||
('537740', 'SAMSUNG', 'MASTER', 'CREDIT', 'ACTIVE'),
|
||||
('438676', 'LOTTE', 'VISA', 'CREDIT', 'ACTIVE'),
|
||||
('546551', 'NH', 'MASTER', 'CHECK', 'ACTIVE'),
|
||||
('523430', 'HANA', 'MASTER', 'CREDIT', 'ACTIVE'),
|
||||
('940000', 'BC', 'LOCAL', 'CHECK', 'ACTIVE'),
|
||||
('356789', 'JCB', 'LOCAL', 'CREDIT', 'INACTIVE')
|
||||
ON CONFLICT (bin) DO NOTHING;
|
||||
|
||||
-- 공통코드 보강
|
||||
INSERT INTO mm_code (code_group, code, code_name, sort_order) VALUES
|
||||
('GRADE', 'B', '일반', 3),
|
||||
('GRADE', 'C', '신규', 4),
|
||||
('CARD_TYPE', 'PREPAID','선불카드', 3),
|
||||
('MERCH_STATUS', 'ACTIVE', '정상영업', 1),
|
||||
('MERCH_STATUS', 'HOLD', '거래보류', 2),
|
||||
('MERCH_STATUS', 'DELETED','해지', 3),
|
||||
('PAYOUT_STATUS','P', '지급완료', 1),
|
||||
('PAYOUT_STATUS','R', '지급요청', 2),
|
||||
('PAYOUT_STATUS','J', '지급반려', 3)
|
||||
ON CONFLICT (code_group, code) DO NOTHING;
|
||||
|
||||
-- 가맹점 한도 마스터
|
||||
INSERT INTO mm_limit (merchant_id, daily_limit, monthly_limit, per_txn_limit)
|
||||
SELECT merchant_id, daily_limit, daily_limit * 25, greatest(daily_limit / 10, 5000000)
|
||||
FROM merchant
|
||||
ON CONFLICT (merchant_id) DO NOTHING;
|
||||
|
||||
-- 발급사 마스터 (매입 이력의 8개 발급사)
|
||||
INSERT INTO mm_issuer (issuer_cd, issuer_name, country) VALUES
|
||||
('KB', 'KB국민카드', 'KR'),
|
||||
('SHINHAN', '신한카드', 'KR'),
|
||||
('HYUNDAI', '현대카드', 'KR'),
|
||||
('SAMSUNG', '삼성카드', 'KR'),
|
||||
('LOTTE', '롯데카드', 'KR'),
|
||||
('NH', 'NH농협카드', 'KR'),
|
||||
('HANA', '하나카드', 'KR'),
|
||||
('JCB', 'JCB', 'JP')
|
||||
ON CONFLICT (issuer_cd) DO NOTHING;
|
||||
|
||||
-- 단말 마스터: 가맹점당 2대
|
||||
INSERT INTO mm_terminal (terminal_id, merchant_id, model, status)
|
||||
SELECT 'TM' || substr(m.merchant_id, 2) || '-0' || t.i, m.merchant_id,
|
||||
(ARRAY['IC-700','IC-700','CAT-9200','MPOS-X1'])[(substr(m.merchant_id, 2)::int + t.i) % 4 + 1],
|
||||
CASE WHEN m.status = 'ACTIVE' THEN 'ACTIVE' ELSE 'SUSPEND' END
|
||||
FROM merchant m CROSS JOIN (VALUES (1), (2)) AS t(i)
|
||||
ON CONFLICT (terminal_id) DO NOTHING;
|
||||
|
||||
-- MCC 업종 마스터
|
||||
INSERT INTO mm_mcc (mcc, mcc_name, risk_level) VALUES
|
||||
('5814', '카페/제과', 1), ('5411', '슈퍼마켓/마트', 1),
|
||||
('5912', '약국', 1), ('5651', '의류', 1),
|
||||
('7230', '미용실', 1), ('5812', '일반음식점', 1),
|
||||
('5541', '주유소', 1), ('5942', '서적/문구', 1),
|
||||
('7997', '스포츠/레저시설', 2), ('8062', '병원/의원', 1),
|
||||
('5732', '가전/통신기기', 2), ('5992', '화훼', 1),
|
||||
('4722', '여행사', 3), ('7011', '숙박', 2),
|
||||
('8299', '교육/학원', 2), ('7995', '사행성(차단)', 5)
|
||||
ON CONFLICT (mcc) DO NOTHING;
|
||||
|
||||
-- 공휴일 마스터 (2026 KR)
|
||||
INSERT INTO mm_holiday (holiday_date, holiday_name, nation) VALUES
|
||||
('2026-01-01', '신정', 'KR'), ('2026-02-16', '설날 연휴', 'KR'),
|
||||
('2026-02-17', '설날', 'KR'), ('2026-02-18', '설날 연휴', 'KR'),
|
||||
('2026-03-01', '삼일절', 'KR'), ('2026-05-05', '어린이날', 'KR'),
|
||||
('2026-05-24', '부처님오신날', 'KR'), ('2026-06-06', '현충일', 'KR'),
|
||||
('2026-08-15', '광복절', 'KR'), ('2026-09-24', '추석 연휴', 'KR'),
|
||||
('2026-09-25', '추석', 'KR'), ('2026-09-26', '추석 연휴', 'KR'),
|
||||
('2026-10-03', '개천절', 'KR'), ('2026-10-09', '한글날', 'KR'),
|
||||
('2026-12-25', '성탄절', 'KR')
|
||||
ON CONFLICT (holiday_date) DO NOTHING;
|
||||
|
||||
-- 환율 마스터
|
||||
INSERT INTO mm_fx_rate (ccy, rate_bps, base_date) VALUES
|
||||
('USD', 135000, '2026-07-19'), ('JPY', 900, '2026-07-19'),
|
||||
('EUR', 147000, '2026-07-19'), ('CNY', 19000, '2026-07-19'),
|
||||
('GBP', 171000, '2026-07-19')
|
||||
ON CONFLICT (ccy) DO NOTHING;
|
||||
|
||||
-- 등급 통계 (최근 7일)
|
||||
INSERT INTO mm_grade_stat (stat_date, grade, merch_cnt)
|
||||
SELECT d::date, e.grade, count(*)
|
||||
FROM generate_series('2026-07-13'::date, '2026-07-19'::date, '1 day') d
|
||||
CROSS JOIN LATERAL (SELECT grade FROM mm_merchant_ext) e
|
||||
GROUP BY d::date, e.grade
|
||||
ON CONFLICT (stat_date, grade) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 8. vl 모듈 (정합성 검증)
|
||||
-- ===========================================================================
|
||||
-- 검증 로그: 매입건별 온라인 검증 + 일배치 검증
|
||||
INSERT INTO vl_check_log (log_id, check_type, target_id, result_code, detail, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id),
|
||||
(ARRAY['VL_SALES','VL_DUPCHK','VL_LIMITCHK','VL_LEDGER'])[purchase_id % 4 + 1],
|
||||
purchase_id,
|
||||
CASE WHEN purchase_id % 211 = 0 THEN purchase_id % 3 + 1 ELSE 0 END,
|
||||
CASE WHEN purchase_id % 211 = 0
|
||||
THEN (ARRAY['금액 검증 실패','중복 의심','한도 초과 경고'])[purchase_id % 3 + 1]
|
||||
ELSE '정상' END,
|
||||
biz_date, created_at + interval '30 seconds'
|
||||
FROM ms_px
|
||||
ON CONFLICT (log_id) DO NOTHING;
|
||||
|
||||
-- 일배치 검증 이력 (영업일 x 배치 3종)
|
||||
INSERT INTO vl_check_log (log_id, check_type, target_id, result_code, detail, biz_date, created_at)
|
||||
SELECT 100000 + row_number() OVER (ORDER BY d, bt.ord),
|
||||
bt.check_type, 0, 0, '일배치 검증 완료', d::date,
|
||||
d::date::timestamptz + interval '22 hours' + (bt.ord || ' minutes')::interval
|
||||
FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d
|
||||
CROSS JOIN (VALUES (1, 'VL_DAILY_SALES'), (2, 'VL_DAILY_LEDGER'), (3, 'VL_DAILY_SETTLE')) AS bt(ord, check_type)
|
||||
ON CONFLICT (log_id) DO NOTHING;
|
||||
|
||||
-- 의심거래: 일별 최고액 2건(한도성 경보) + 주기적 중복 의심 (최근 건 OPEN 유지)
|
||||
INSERT INTO vl_suspect (suspect_id, purchase_id, rule_code, severity, detail, status, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, rule_code,
|
||||
CASE rule_code WHEN 'LIMIT_OVR' THEN 'WARN' WHEN 'AMT_MAX' THEN 'ERROR' ELSE 'WARN' END,
|
||||
detail, CASE WHEN biz_date >= '2026-07-10' THEN 'OPEN'
|
||||
WHEN purchase_id % 3 = 0 THEN 'CONFIRMED' ELSE 'CLOSED' END,
|
||||
biz_date, biz_date::timestamptz + interval '21 hours'
|
||||
FROM (
|
||||
SELECT purchase_id, biz_date, 'LIMIT_OVR' AS rule_code,
|
||||
'일 최고액 거래 검출: ' || merchant_id || ' ' || amount || '원' AS detail
|
||||
FROM (SELECT purchase_id, merchant_id, amount, biz_date,
|
||||
row_number() OVER (PARTITION BY biz_date ORDER BY amount DESC, purchase_id) AS rk
|
||||
FROM ms_px) t
|
||||
WHERE rk <= 2
|
||||
UNION ALL
|
||||
SELECT purchase_id, biz_date, 'DUP_TXN',
|
||||
'동일 가맹점/금액 중복 의심: ' || merchant_id || ' ' || amount || '원'
|
||||
FROM ms_px WHERE purchase_id % 523 = 0
|
||||
) s
|
||||
ON CONFLICT (suspect_id) DO NOTHING;
|
||||
|
||||
-- 검증 규칙 보강 (기존 5종 유지)
|
||||
INSERT INTO vl_rule (rule_code, rule_name, threshold, severity) VALUES
|
||||
('FX_RATE', '환율 급변동 검증', 5, 'WARN'),
|
||||
('SETTLE_T1','T+1 정산 지연', 1, 'ERROR')
|
||||
ON CONFLICT (rule_code) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 9. mg 모듈 (전문 게이트웨이)
|
||||
-- ===========================================================================
|
||||
-- 전문 로그: 매입건별 0200(IN)/0210(OUT) 페어, 미승인건 응답코드 05
|
||||
INSERT INTO mg_msg_log (msg_id, stan, mti, channel, direction, proc_code, bitmap,
|
||||
amount, pan, mac, rc, raw_msg, status, biz_date, created_at)
|
||||
SELECT x.purchase_id * 2 + d.ord - 2,
|
||||
x.purchase_id,
|
||||
d.mti, x.mg_chan, d.direction, '000000', 'F23A448108E08000',
|
||||
x.amount,
|
||||
substr(x.card_no, 1, 6) || '******' || substr(x.card_no, 13, 4),
|
||||
substr(md5(x.purchase_id::text || d.mti), 1, 16),
|
||||
CASE WHEN d.mti = '0200' THEN '00'
|
||||
WHEN NOT coalesce(a.approved, true) THEN '05'
|
||||
WHEN x.purchase_id % 331 = 0 THEN '91'
|
||||
ELSE '00' END,
|
||||
d.mti || '000000' || lpad(x.amount::text, 12, '0') || lpad((x.purchase_id % 1000000)::text, 6, '0'),
|
||||
CASE d.direction WHEN 'IN' THEN 'R' ELSE 'S' END,
|
||||
x.biz_date,
|
||||
x.created_at + (d.ord || ' seconds')::interval
|
||||
FROM ms_px x
|
||||
LEFT JOIN approval a ON a.purchase_id = x.purchase_id
|
||||
CROSS JOIN (VALUES (1, '0200', 'IN'), (2, '0210', 'OUT')) AS d(ord, mti, direction)
|
||||
ON CONFLICT (msg_id) DO NOTHING;
|
||||
|
||||
-- 망관리 전문 (0800/0810 에코, 채널 x 영업일)
|
||||
INSERT INTO mg_msg_log (msg_id, stan, mti, channel, direction, proc_code,
|
||||
amount, rc, status, biz_date, created_at)
|
||||
SELECT 900000 + row_number() OVER (ORDER BY d, c.channel, e.ord),
|
||||
800000 + row_number() OVER (ORDER BY d, c.channel, e.ord),
|
||||
e.mti, c.channel, e.direction, '990000', 0, '00',
|
||||
CASE e.direction WHEN 'OUT' THEN 'S' ELSE 'A' END,
|
||||
d::date, d::date::timestamptz + interval '6 hours' + (e.ord || ' seconds')::interval
|
||||
FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d
|
||||
CROSS JOIN (VALUES ('CH01'), ('CH02'), ('CH03')) AS c(channel)
|
||||
CROSS JOIN (VALUES (1, '0800', 'OUT'), (2, '0810', 'IN')) AS e(ord, mti, direction)
|
||||
ON CONFLICT (msg_id) DO NOTHING;
|
||||
|
||||
-- 재전송 큐: 응답 오류/타임아웃 건
|
||||
INSERT INTO mg_queue (queue_id, stan, channel, mti, payload, status, retry_cnt, biz_date, created_at, updated_at)
|
||||
SELECT row_number() OVER (ORDER BY msg_id), stan, channel, mti, raw_msg,
|
||||
CASE WHEN biz_date >= '2026-07-18' THEN 'Q'
|
||||
WHEN stan % 3 = 0 THEN 'F' ELSE 'D' END,
|
||||
stan % 3 + 1, biz_date, created_at, created_at + interval '10 minutes'
|
||||
FROM mg_msg_log WHERE rc = '91'
|
||||
ON CONFLICT (queue_id) DO NOTHING;
|
||||
|
||||
-- 채널 일별 통계
|
||||
INSERT INTO mg_stat (channel, biz_date, in_cnt, out_cnt, err_cnt, amount_sum)
|
||||
SELECT channel, biz_date,
|
||||
count(*) FILTER (WHERE direction = 'IN'),
|
||||
count(*) FILTER (WHERE direction = 'OUT'),
|
||||
count(*) FILTER (WHERE rc <> '00'),
|
||||
coalesce(sum(amount) FILTER (WHERE mti = '0200'), 0)
|
||||
FROM mg_msg_log GROUP BY channel, biz_date
|
||||
ON CONFLICT (channel, biz_date) DO NOTHING;
|
||||
|
||||
-- 라우팅 보강 + 채널 사인온 상태 + 세션 채번 반영
|
||||
INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date)
|
||||
SELECT nextval('mg_route_seq'), v.channel, v.mti, v.dest, v.pri, true, '2026-07-19'
|
||||
FROM (VALUES ('CH02', '0800', 'MG_ECHO', 20), ('CH03', '0200', 'ACQ_FOREIGN', 10),
|
||||
('CH03', '0800', 'MG_ECHO', 20), ('CH01', '0400', 'ACQ_CANCEL', 15)) AS v(channel, mti, dest, pri)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM mg_route r WHERE r.channel = v.channel AND r.mti = v.mti);
|
||||
|
||||
UPDATE mg_channel c SET status = 'UP',
|
||||
session_key = substr(md5('sess' || c.channel), 1, 16),
|
||||
last_stan = coalesce((SELECT max(stan) FROM mg_msg_log l WHERE l.channel = c.channel), 0),
|
||||
signon_at = '2026-07-19 06:00:00+09', updated_at = now();
|
||||
|
||||
UPDATE mg_seq SET seq_val = 273, updated_at = now() WHERE seq_name = 'SESSION' AND seq_val = 0;
|
||||
UPDATE mg_seq SET seq_val = 91, updated_at = now() WHERE seq_name = 'KEYVER' AND seq_val = 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 10. cm 모듈 (공통)
|
||||
-- ===========================================================================
|
||||
-- 운영 로그: 온라인 서비스 호출 + 배치 + 경고/오류 혼합 (~1.5만)
|
||||
INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY ord, id), svc_name, log_level, msg, biz_date, ts
|
||||
FROM (
|
||||
-- 매입 접수 로그 (2건 중 1건)
|
||||
SELECT 1 AS ord, purchase_id AS id, 'ACQUIRE' AS svc_name, 'I' AS log_level,
|
||||
'매입 접수 완료 pid=' || purchase_id || ' ' || merchant_id || ' ' || amount || '원' AS msg,
|
||||
biz_date, created_at AS ts
|
||||
FROM ms_px WHERE purchase_id % 2 = 0
|
||||
UNION ALL
|
||||
-- 지급 처리 로그
|
||||
SELECT 2, payout_id, 'PAY_REMIT',
|
||||
CASE WHEN status = 'J' THEN 'E' ELSE 'I' END,
|
||||
CASE WHEN status = 'J' THEN '지급 반려 payout=' || payout_id || ' 사유=' || reject_reason
|
||||
ELSE '지급 처리 payout=' || payout_id || ' ' || merchant_id || ' ' || net || '원' END,
|
||||
biz_date, created_at
|
||||
FROM py_payout
|
||||
UNION ALL
|
||||
-- 일배치 로그 (영업일 x 배치 6종)
|
||||
SELECT 3, row_number() OVER (ORDER BY d, b.ord), b.svc, 'I',
|
||||
b.svc || ' 배치 완료 (' || to_char(d, 'YYYY-MM-DD') || ')', d::date,
|
||||
d::date::timestamptz + interval '23 hours' + (b.ord || ' minutes')::interval
|
||||
FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d
|
||||
CROSS JOIN (VALUES (1, 'rc_recon_batch'), (2, 'st_settle_batch'), (3, 'lg_post_batch'),
|
||||
(4, 'cl_daily_batch'), (5, 'mg_stat_batch'), (6, 'cm_purge_batch')) AS b(ord, svc)
|
||||
UNION ALL
|
||||
-- 산발적 경고/오류
|
||||
SELECT 4, gs, (ARRAY['AUTH_LIMIT','RC_MATCH','ST_CALC','MG_SEND','VL_SALES'])[gs % 5 + 1],
|
||||
CASE WHEN gs % 9 = 0 THEN 'E' ELSE 'W' END,
|
||||
CASE WHEN gs % 9 = 0 THEN 'DB 응답 지연 재시도 후 성공 (attempt=2)'
|
||||
ELSE (ARRAY['한도 임계치 80% 도달','대사 지연 경고','수수료 재계산 수행',
|
||||
'채널 응답 지연 1.2s','검증 경고 검출'])[gs % 5 + 1] END,
|
||||
('2026-04-20'::date + (gs % 91)),
|
||||
('2026-04-20'::date + (gs % 91))::timestamptz + interval '9 hours' + (gs % 600 || ' minutes')::interval
|
||||
FROM generate_series(1, 2800) gs
|
||||
) l
|
||||
ON CONFLICT (log_id) DO NOTHING;
|
||||
|
||||
-- 공휴일 보강
|
||||
INSERT INTO cm_holiday (holi_date, holi_name, nation) VALUES
|
||||
('2026-02-16', '설날 연휴', 'KR'), ('2026-02-17', '설날', 'KR'),
|
||||
('2026-02-18', '설날 연휴', 'KR'), ('2026-05-24', '부처님오신날', 'KR'),
|
||||
('2026-06-06', '현충일', 'KR'), ('2026-09-24', '추석 연휴', 'KR'),
|
||||
('2026-09-25', '추석', 'KR'), ('2026-09-26', '추석 연휴', 'KR'),
|
||||
('2026-10-03', '개천절', 'KR'), ('2026-10-09', '한글날', 'KR')
|
||||
ON CONFLICT (holi_date) DO NOTHING;
|
||||
|
||||
-- 일별 환율 이력 (기존 2026-07-19 시드 유지)
|
||||
INSERT INTO cm_fx_rate (ccy, base_date, rate_bps)
|
||||
SELECT c.ccy, d::date,
|
||||
c.base + ((extract(day FROM d)::int * c.step) % c.span) - c.span / 2
|
||||
FROM generate_series('2026-04-20'::date, '2026-07-18'::date, '1 day') d
|
||||
CROSS JOIN (VALUES ('USD', 135000, 137, 2600), ('JPY', 900, 7, 40),
|
||||
('EUR', 147000, 211, 3200)) AS c(ccy, base, step, span)
|
||||
ON CONFLICT (ccy, base_date) DO NOTHING;
|
||||
|
||||
-- 공통코드/파라미터 보강
|
||||
INSERT INTO cm_code (code_grp, code, code_name, sort_ord) VALUES
|
||||
('STATUS', 'M', '대사완료', 4), ('STATUS', 'H', '보류', 5), ('STATUS', 'V', '무효', 6),
|
||||
('PAYCH', 'STD', '일반지급', 1), ('PAYCH', 'BULK', '대량지급', 2),
|
||||
('PAYCH', 'ADVANCE', '선지급', 3),
|
||||
('MTI', '0200', '승인요청', 1), ('MTI', '0210', '승인응답', 2),
|
||||
('MTI', '0800', '망관리요청', 3), ('MTI', '0810', '망관리응답', 4)
|
||||
ON CONFLICT (code_grp, code) DO NOTHING;
|
||||
|
||||
INSERT INTO cm_param (param_key, param_val, descr) VALUES
|
||||
('PAYOUT_FEE', '500', '건당 이체수수료(원)'),
|
||||
('PAYOUT_CYCLE', 'T+1', '지급 주기'),
|
||||
('CLOSE_TIME', '23:00', '일마감 기준시각'),
|
||||
('FRAUD_SCORE', '700', '부정거래 탐지 임계 점수'),
|
||||
('MAX_RETRY', '3', '전문 재전송 최대 횟수')
|
||||
ON CONFLICT (param_key) DO NOTHING;
|
||||
|
||||
UPDATE cm_seq SET seq_val = 1840, updated_at = now() WHERE seq_name = 'BATCH' AND seq_val = 0;
|
||||
UPDATE cm_seq SET seq_val = 9190, updated_at = now() WHERE seq_name = 'SLIP' AND seq_val = 0;
|
||||
UPDATE cm_seq SET seq_val = 47, updated_at = now() WHERE seq_name = 'TICKET' AND seq_val = 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 11. ac 상세 테이블 (00-base) - 업무화면 공백 방지 보강
|
||||
-- ===========================================================================
|
||||
INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
CASE WHEN status = 'V' THEN 'VOID'
|
||||
WHEN purchase_id % 5 = 0 THEN 'PARTIAL' ELSE 'FULL' END,
|
||||
CASE WHEN status <> 'V' AND purchase_id % 5 = 0 THEN amount / 2 / 100 * 100 ELSE amount END,
|
||||
(ARRAY['고객 변심 반품','이중 결제 취소','가맹점 요청 취소','민원 처리 환불'])[purchase_id % 4 + 1],
|
||||
biz_date, created_at + interval '4 hours'
|
||||
FROM ms_px WHERE status IN ('C','V')
|
||||
ON CONFLICT (cancel_id) DO NOTHING;
|
||||
|
||||
-- 할부: 고액 일부 건 3/6/12개월 회차 전개
|
||||
INSERT INTO ac_install (install_id, purchase_id, seq_no, months, month_amount, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY t.purchase_id, gs), t.purchase_id, gs, t.months,
|
||||
t.amount / t.months / 100 * 100, t.biz_date
|
||||
FROM (SELECT purchase_id, amount, biz_date,
|
||||
(ARRAY[3, 6, 12])[purchase_id % 3 + 1] AS months
|
||||
FROM ms_px WHERE purchase_id % 11 = 0 AND amount >= 500000) t
|
||||
CROSS JOIN LATERAL generate_series(1, t.months) gs
|
||||
ON CONFLICT (install_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ac_fee_adj (adj_id, purchase_id, delta, reason, biz_date, created_at)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
CASE WHEN purchase_id % 2 = 0 THEN 1 ELSE -1 END * ((purchase_id % 8 + 1) * 100),
|
||||
(ARRAY['요율 소급 적용','프로모션 수수료 감면','정정 재계산'])[purchase_id % 3 + 1],
|
||||
biz_date, created_at + interval '1 day'
|
||||
FROM ms_px WHERE purchase_id % 201 = 0
|
||||
ON CONFLICT (adj_id) DO NOTHING;
|
||||
|
||||
-- 원천징수: 의료/약국 가맹점 정산건
|
||||
INSERT INTO ac_wht (wht_id, purchase_id, tax_amount, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, net * 220 / 10000, biz_date
|
||||
FROM ms_px
|
||||
WHERE status = 'S' AND merchant_id IN ('M0005','M0008','M0022','M0024')
|
||||
ON CONFLICT (wht_id) DO NOTHING;
|
||||
|
||||
-- 세금계산서: EDI 청구 채널
|
||||
INSERT INTO ac_tax_invoice (inv_id, purchase_id, merchant_id, supply, vat, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, merchant_id,
|
||||
amount * 10 / 11, amount - amount * 10 / 11, biz_date
|
||||
FROM ms_px WHERE channel = 'EDI'
|
||||
ON CONFLICT (inv_id) DO NOTHING;
|
||||
|
||||
-- 해외 매입 환산
|
||||
INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, c.ccy,
|
||||
amount * 100 / c.rate, amount, c.rate, biz_date
|
||||
FROM ms_px
|
||||
CROSS JOIN LATERAL (SELECT (ARRAY['USD','JPY','EUR'])[purchase_id % 3 + 1] AS ccy,
|
||||
(ARRAY[135000, 900, 147000])[purchase_id % 3 + 1]::bigint AS rate) c
|
||||
WHERE channel = 'FOREIGN'
|
||||
ON CONFLICT (fx_id) DO NOTHING;
|
||||
|
||||
-- EDI 청구 문서
|
||||
INSERT INTO ac_edi (edi_id, merchant_id, doc_no, amount, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), merchant_id,
|
||||
'EDI' || to_char(biz_date, 'YYYYMMDD') || '-' || lpad((purchase_id % 10000)::text, 4, '0'),
|
||||
amount, biz_date
|
||||
FROM ms_px WHERE channel = 'EDI'
|
||||
ON CONFLICT (edi_id) DO NOTHING;
|
||||
|
||||
-- 미매입 (보류건)
|
||||
INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date)
|
||||
SELECT row_number() OVER (ORDER BY purchase_id), purchase_id,
|
||||
(ARRAY['승인 매핑 대기','매입 전문 미도래','금액 확인 필요'])[purchase_id % 3 + 1],
|
||||
biz_date
|
||||
FROM ms_px WHERE status = 'H'
|
||||
ON CONFLICT (unmatch_id) DO NOTHING;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 12. 시퀀스 정렬 (라이브 트래픽이 시드 뒤 번호를 받도록)
|
||||
-- ===========================================================================
|
||||
SELECT setval('au_auth_seq', coalesce((SELECT max(auth_id) FROM au_authorization), 1));
|
||||
SELECT setval('au_fraud_seq', coalesce((SELECT max(fraud_id) FROM au_fraud_log), 1));
|
||||
SELECT setval('au_preauth_seq', coalesce((SELECT max(preauth_id) FROM au_preauth), 1));
|
||||
SELECT setval('au_cancel_seq', coalesce((SELECT max(cancel_id) FROM au_cancel), 1));
|
||||
SELECT setval('py_payout_seq', coalesce((SELECT max(payout_id) FROM py_payout), 1));
|
||||
SELECT setval('py_file_seq', coalesce((SELECT max(file_id) FROM py_file), 1));
|
||||
SELECT setval('py_schedule_seq', coalesce((SELECT max(schedule_id) FROM py_schedule), 1));
|
||||
SELECT setval('py_adjust_seq', coalesce((SELECT max(adj_id) FROM py_adjust), 1));
|
||||
SELECT setval('py_ledger_seq', coalesce((SELECT max(ledger_id) FROM py_ledger), 1));
|
||||
SELECT setval('rc_match_seq', coalesce((SELECT max(match_id) FROM rc_match_log), 1));
|
||||
SELECT setval('rc_exc_seq', coalesce((SELECT max(exc_id) FROM rc_exception), 1));
|
||||
SELECT setval('rc_diff_seq', coalesce((SELECT max(diff_id) FROM rc_diff), 1));
|
||||
SELECT setval('rc_alert_seq', coalesce((SELECT max(alert_id) FROM rc_alert), 1));
|
||||
SELECT setval('st_dtl_seq', coalesce((SELECT max(dtl_id) FROM st_settle_dtl), 1));
|
||||
SELECT setval('st_adj_seq', coalesce((SELECT max(adj_id) FROM st_adjust), 1));
|
||||
SELECT setval('st_pay_seq', coalesce((SELECT max(pay_id) FROM st_payment), 1));
|
||||
SELECT setval('lg_voucher_seq', coalesce((SELECT max(voucher_id) FROM lg_voucher), 1));
|
||||
SELECT setval('lg_line_seq', coalesce((SELECT max(line_id) FROM lg_ledger_line), 1));
|
||||
SELECT setval('cl_close_seq', coalesce((SELECT max(close_id) FROM cl_close_log), 1));
|
||||
SELECT setval('cl_snapshot_seq', coalesce((SELECT max(snapshot_id) FROM cl_snapshot), 1));
|
||||
SELECT setval('mm_merch_hist_seq', coalesce((SELECT max(hist_id) FROM mm_merch_hist), 1));
|
||||
SELECT setval('mm_feerate_hist_seq', coalesce((SELECT max(hist_id) FROM mm_fee_rate_hist), 1));
|
||||
SELECT setval('vl_suspect_seq', coalesce((SELECT max(suspect_id) FROM vl_suspect), 1));
|
||||
SELECT setval('vl_checklog_seq', coalesce((SELECT max(log_id) FROM vl_check_log), 1));
|
||||
SELECT setval('mg_msg_seq', coalesce((SELECT max(msg_id) FROM mg_msg_log), 1));
|
||||
SELECT setval('mg_stan_seq', coalesce((SELECT max(stan) FROM mg_msg_log), 1));
|
||||
SELECT setval('mg_queue_seq', coalesce((SELECT max(queue_id) FROM mg_queue), 1));
|
||||
SELECT setval('cm_log_seq', coalesce((SELECT max(log_id) FROM cm_log), 1));
|
||||
SELECT setval('ac_cancel_seq', coalesce((SELECT max(cancel_id) FROM ac_cancel), 1));
|
||||
SELECT setval('ac_install_seq', coalesce((SELECT max(install_id) FROM ac_install), 1));
|
||||
SELECT setval('ac_feeadj_seq', coalesce((SELECT max(adj_id) FROM ac_fee_adj), 1));
|
||||
SELECT setval('ac_wht_seq', coalesce((SELECT max(wht_id) FROM ac_wht), 1));
|
||||
SELECT setval('ac_taxinv_seq', coalesce((SELECT max(inv_id) FROM ac_tax_invoice), 1));
|
||||
SELECT setval('ac_fx_seq', coalesce((SELECT max(fx_id) FROM ac_fx), 1));
|
||||
SELECT setval('ac_edi_seq', coalesce((SELECT max(edi_id) FROM ac_edi), 1));
|
||||
SELECT setval('ac_unmatch_seq', coalesce((SELECT max(unmatch_id) FROM ac_unmatch), 1));
|
||||
|
||||
DROP TABLE ms_px;
|
||||
DROP TABLE ms_ms;
|
||||
Loading…
Add table
Add a link
Reference in a new issue