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

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

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

View file

@ -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;