fix: 정산 배치 파이프라인 정합 — 매퍼 NULL 파라미터 + override_settle 제약 + 정산지급 데이터
정산/지급 화면이 전부 비어있던 원인: 운영 DB에서 정산 배치가 실행된 적 없음.
MONTHLY_SETTLEMENT 배치를 돌리는 과정에서 발견한 버그 수정 + 데이터 구성.
매퍼 버그 (PG: could not determine data type of parameter):
- RegulatoryLimitMapper/ChargebackGradeMapper/InstallmentRatioMapper:
#{productCategory} 가 IS NULL 비교에 쓰여 타입 추론 실패 -> jdbcType=VARCHAR 명시
- ForecastKpiMonthlyMapper: #{targetOrgId} 동일 -> jdbcType=BIGINT 명시
스키마 (V79):
- override_settle.settle_id NOT NULL 완화 — calcOverride(Step7)는 settle_master
생성(Step8) 전 단계라 settle_id 를 채울 수 없음
데이터 (V80):
- 배치 실행 결과: settle_master 150 / 원장 433 / 명세서 112 / 분개 433 (2026-02~04)
- payment 는 settle_master 파생 생성, 운영성 도메인(공제/마감/세금계산서/이의신청/
시책/정정)은 대표 샘플 시드 (멱등 NOT EXISTS)
검증: 배치 12스텝 COMPLETED, 정산/지급 10개 엔드포인트 200 OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
-- V79: 정산 배치 파이프라인 정합 보정 — override_settle.settle_id NOT NULL 완화
|
||||
--
|
||||
-- 배경:
|
||||
-- MONTHLY_SETTLEMENT 배치는 Step7(calcOverride) 에서 override_settle 을 적재한 뒤
|
||||
-- Step8(aggregate) 에서 settle_master 를 생성한다. 즉 오버라이드 계산 시점에는
|
||||
-- 아직 settle_master 행이 없어 settle_id 를 채울 수 없다.
|
||||
-- override_settle 은 (settle_month, upper_agent_id) 로 식별되며 AggregateStep 도
|
||||
-- 해당 키로 집계(aggregateByUpper)하므로 settle_id 는 선택적 역참조 컬럼이다.
|
||||
--
|
||||
-- 조치: settle_id 의 NOT NULL 제약을 해제 (FK 는 유지, NULL 허용).
|
||||
ALTER TABLE override_settle ALTER COLUMN settle_id DROP NOT NULL;
|
||||
|
||||
COMMENT ON COLUMN override_settle.settle_id IS '연결 settle_master (선택적 역참조 — 정산 집계 이후 백필 가능, 배치 적재 시점엔 NULL)';
|
||||
@@ -0,0 +1,134 @@
|
||||
-- V80: 정산/지급 영역 데이터 구성
|
||||
--
|
||||
-- MONTHLY_SETTLEMENT 배치(2026-02~04)로 settle_master / 원장 / 명세서 / 분개는 적재 완료.
|
||||
-- payment(지급) 는 settle_master 에서 파생 생성하고, 배치 파이프라인이 없는
|
||||
-- 운영성 도메인(공제/마감/세금계산서/이의신청/시책/정정)은 대표 샘플을 시드한다.
|
||||
-- 모든 블록은 멱등 (NOT EXISTS 가드).
|
||||
|
||||
-- ============================================================
|
||||
-- 1. payment — settle_master 파생 (정산 1건 → 지급 1건)
|
||||
-- 202602 = DONE(지급완료), 202603/202604 = PENDING(지급대기)
|
||||
-- ============================================================
|
||||
INSERT INTO payment (settle_id, agent_id, pay_amount, bank_code, account_no,
|
||||
pay_status, pay_date, income_tax_amount, local_tax_amount,
|
||||
vat_amount, deduction_amount, net_amount, created_at)
|
||||
SELECT sm.settle_id, sm.agent_id, sm.gross_amount, a.bank_code, a.account_no,
|
||||
CASE WHEN sm.settle_month = '202602' THEN 'DONE' ELSE 'PENDING' END,
|
||||
CASE WHEN sm.settle_month = '202602' THEN DATE '2026-03-10' END,
|
||||
ROUND(sm.tax_amount * 0.909, 0), -- 소득세분 (지방세 제외 근사)
|
||||
ROUND(sm.tax_amount * 0.091, 0), -- 지방소득세분
|
||||
0, 0, sm.net_amount, now()
|
||||
FROM settle_master sm
|
||||
JOIN agent a ON a.agent_id = sm.agent_id
|
||||
WHERE NOT EXISTS (SELECT 1 FROM payment p WHERE p.settle_id = sm.settle_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. agent_deduction — 공제 샘플 (settle_master 설계사 기준 12건)
|
||||
-- ============================================================
|
||||
INSERT INTO agent_deduction (agent_id, settle_month, deduction_type, amount, description, status, created_by)
|
||||
SELECT r.agent_id, '202604', t.dtype, t.amt, t.descr, t.st, 1
|
||||
FROM (
|
||||
SELECT agent_id, row_number() OVER (ORDER BY agent_id) AS rn
|
||||
FROM settle_master WHERE settle_month = '202604'
|
||||
) r
|
||||
JOIN (VALUES
|
||||
(1,'LOAN', 800000, '단기 대출 상환분', 'APPLIED'),
|
||||
(2,'ADVANCE', 500000, '선지급 수수료 환수', 'APPLIED'),
|
||||
(3,'GUARANTEE',300000, '영업보증금 분할 납입', 'PENDING'),
|
||||
(4,'DUES', 50000, '협회비', 'APPLIED'),
|
||||
(5,'AD', 120000, '공동 광고비 분담', 'PENDING'),
|
||||
(6,'CHARGEBACK_PENDING', 650000, '환수 보류분', 'PENDING'),
|
||||
(7,'LOAN', 400000, '대출 상환분', 'APPLIED'),
|
||||
(8,'OTHER', 90000, '기타 공제', 'PENDING'),
|
||||
(9,'ADVANCE', 350000, '선지급 환수', 'APPLIED'),
|
||||
(10,'DUES', 50000, '협회비', 'APPLIED'),
|
||||
(11,'GUARANTEE',300000,'보증금 납입', 'PENDING'),
|
||||
(12,'LOAN', 220000, '대출 상환분', 'APPLIED')
|
||||
) AS t(rn, dtype, amt, descr, st) ON t.rn = r.rn
|
||||
WHERE NOT EXISTS (SELECT 1 FROM agent_deduction);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. period_close — 마감 (2026-01 ~ 2026-03 마감 완료)
|
||||
-- ============================================================
|
||||
INSERT INTO period_close (close_month, closed_at, closed_by, note)
|
||||
SELECT m, ts, 1, note FROM (VALUES
|
||||
('202601', TIMESTAMP '2026-02-05 18:00:00', '2026년 1월 정산 마감'),
|
||||
('202602', TIMESTAMP '2026-03-05 18:00:00', '2026년 2월 정산 마감'),
|
||||
('202603', TIMESTAMP '2026-04-06 18:00:00', '2026년 3월 정산 마감')
|
||||
) AS v(m, ts, note)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM period_close);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. tax_invoice — 세금계산서 (보험사 대상 매출 5건)
|
||||
-- ============================================================
|
||||
INSERT INTO tax_invoice (invoice_no, invoice_type, issue_date, supply_amount, tax_amount,
|
||||
total_amount, counterpart_name, counterpart_biz_no, counterpart_rep,
|
||||
settle_month, description, issued_by, created_at)
|
||||
SELECT v.no, 'SUPPLY', v.dt, v.supply, ROUND(v.supply*0.1,0), v.supply+ROUND(v.supply*0.1,0),
|
||||
v.name, v.bizno, v.rep, v.sm, v.descr, 1, now()
|
||||
FROM (VALUES
|
||||
('2026-S-0001', DATE '2026-03-10', 11968527::numeric, '삼성생명', '124-81-00001', '홍길동', '202602', '2026년 2월 수수료 세금계산서'),
|
||||
('2026-S-0002', DATE '2026-04-10', 16019916::numeric, '한화생명', '124-81-00002', '김영수', '202603', '2026년 3월 수수료 세금계산서'),
|
||||
('2026-S-0003', DATE '2026-05-10', 18944495::numeric, '교보생명', '124-81-00003', '이철수', '202604', '2026년 4월 수수료 세금계산서'),
|
||||
('2026-S-0004', DATE '2026-04-10', 4200000::numeric, '메리츠화재','124-81-00004', '박민지', '202603', '2026년 3월 손보 수수료'),
|
||||
('2026-S-0005', DATE '2026-05-10', 3850000::numeric, 'DB손해보험','124-81-00005', '최지훈', '202604', '2026년 4월 손보 수수료')
|
||||
) AS v(no, dt, supply, name, bizno, rep, sm, descr)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM tax_invoice);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. commission_dispute — 수수료 이의신청 (6건)
|
||||
-- ============================================================
|
||||
INSERT INTO commission_dispute (agent_id, settle_month, settle_id, status,
|
||||
disputed_amount, claim_amount, reason, created_at)
|
||||
SELECT r.agent_id, r.settle_month, r.settle_id, t.st, t.damt, t.camt, t.reason, now()
|
||||
FROM (
|
||||
SELECT settle_id, agent_id, settle_month,
|
||||
row_number() OVER (ORDER BY settle_id) AS rn
|
||||
FROM settle_master WHERE settle_month = '202604'
|
||||
) r
|
||||
JOIN (VALUES
|
||||
(1,'OPEN', 300000, 300000, '모집수수료 누락 — 3월 신계약 2건 미반영 주장'),
|
||||
(2,'IN_REVIEW', 150000, 150000, '유지수수료 요율 오적용 이의'),
|
||||
(3,'OPEN', 500000, 450000, '환수 금액 과다 산정 이의'),
|
||||
(4,'APPROVED', 220000, 220000, '오버라이드 누락분 — 검토 후 인정'),
|
||||
(5,'REJECTED', 100000, 0, '예외 차감 이의 — 근거 부족으로 기각'),
|
||||
(6,'IN_REVIEW', 280000, 280000, '세금 공제액 과다 이의')
|
||||
) AS t(rn, st, damt, camt, reason) ON t.rn = r.rn
|
||||
WHERE NOT EXISTS (SELECT 1 FROM commission_dispute);
|
||||
|
||||
-- ============================================================
|
||||
-- 6. incentive_program — 시책 프로그램 (4건)
|
||||
-- ============================================================
|
||||
INSERT INTO incentive_program (program_name, program_type, target_type, start_month, end_month,
|
||||
condition_desc, pay_rate, pay_amount_fixed, pay_type,
|
||||
budget_amount, is_active, created_by, created_at)
|
||||
SELECT v.name, v.ptype, 'AGENT', v.sm, v.em, v.cond, v.rate, v.fixed, v.paytype,
|
||||
v.budget, v.active, 1, now()
|
||||
FROM (VALUES
|
||||
('2026 상반기 신인 모집왕', 'RECRUIT_BONUS', '202601','202606','월 신계약 5건 이상 시 모집수수료의 추가 지급', 0.10::numeric, NULL::numeric, 'RATE', 50000000::numeric, TRUE),
|
||||
('2분기 유지율 우수상', 'MAINTAIN_BONUS','202604','202606','13M 유지율 90% 이상 달성', NULL::numeric, 1000000::numeric, 'FIXED_AMOUNT', 30000000::numeric, TRUE),
|
||||
('봄맞이 특별 콘테스트', 'CONTEST', '202603','202605','기간 내 보장성 신계약 환산 300%P 달성', NULL::numeric, 2000000::numeric, 'FIXED_AMOUNT', 40000000::numeric, TRUE),
|
||||
('2025 하반기 정착 지원', 'SPECIAL', '202507','202512','신인 정착 지원금 (종료)', NULL::numeric, 500000::numeric, 'FIXED_AMOUNT', 20000000::numeric, FALSE)
|
||||
) AS v(name, ptype, sm, em, cond, rate, fixed, paytype, budget, active)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM incentive_program);
|
||||
|
||||
-- ============================================================
|
||||
-- 7. settle_correction — 정산 정정 (4건, 실제 settle_master 참조)
|
||||
-- ============================================================
|
||||
INSERT INTO settle_correction (original_settle_id, agent_id, settle_month, diff_amount,
|
||||
before_amount, after_amount, reason_code, reason_detail,
|
||||
status, requested_by, requested_at)
|
||||
SELECT r.settle_id, r.agent_id, r.settle_month, t.diff,
|
||||
r.net_amount, r.net_amount + t.diff, t.rcode, t.detail, t.st, 1, now()
|
||||
FROM (
|
||||
SELECT settle_id, agent_id, settle_month, net_amount,
|
||||
row_number() OVER (ORDER BY settle_id) AS rn
|
||||
FROM settle_master WHERE settle_month = '202603'
|
||||
) r
|
||||
JOIN (VALUES
|
||||
(1, 200000::numeric, 'DATA_ERROR', '신계약 데이터 누락 — 모집수수료 추가 반영', 'APPROVED'),
|
||||
(2, -150000::numeric, 'POLICY_CHANGE','요율 정책 변경 소급 적용', 'APPROVED'),
|
||||
(3, 80000::numeric, 'LATE_DATA', '지연 수신 데이터 반영', 'DRAFT'),
|
||||
(4, -50000::numeric, 'MANUAL_ADJ', '수기 조정 — 중복 지급분 회수', 'DRAFT')
|
||||
) AS t(rn, diff, rcode, detail, st) ON t.rn = r.rn
|
||||
WHERE NOT EXISTS (SELECT 1 FROM settle_correction);
|
||||
@@ -113,8 +113,8 @@
|
||||
FROM chargeback_grade g
|
||||
WHERE g.months_from <= #{monthsElapsed}
|
||||
AND (g.months_to IS NULL OR g.months_to >= #{monthsElapsed})
|
||||
AND (g.product_category = #{productCategory}
|
||||
OR (#{productCategory} IS NULL AND g.product_category IS NULL))
|
||||
AND (g.product_category = #{productCategory,jdbcType=VARCHAR}
|
||||
OR (#{productCategory,jdbcType=VARCHAR} IS NULL AND g.product_category IS NULL))
|
||||
<include refid="activeCondition"/>
|
||||
ORDER BY g.effective_from DESC
|
||||
LIMIT 1
|
||||
@@ -137,8 +137,8 @@
|
||||
chargeback_percent, effective_from, effective_to, is_active,
|
||||
created_at, created_by, updated_at, updated_by
|
||||
FROM chargeback_grade g
|
||||
WHERE (g.product_category = #{productCategory}
|
||||
OR (#{productCategory} IS NULL AND g.product_category IS NULL))
|
||||
WHERE (g.product_category = #{productCategory,jdbcType=VARCHAR}
|
||||
OR (#{productCategory,jdbcType=VARCHAR} IS NULL AND g.product_category IS NULL))
|
||||
<include refid="activeCondition"/>
|
||||
ORDER BY g.months_from
|
||||
</select>
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
created_at, created_by, updated_at, updated_by
|
||||
FROM installment_ratio
|
||||
WHERE plan_year = #{planYear}
|
||||
AND (product_category = #{productCategory}
|
||||
OR (#{productCategory} IS NULL AND product_category IS NULL))
|
||||
AND (product_category = #{productCategory,jdbcType=VARCHAR}
|
||||
OR (#{productCategory,jdbcType=VARCHAR} IS NULL AND product_category IS NULL))
|
||||
AND is_active = TRUE
|
||||
AND effective_from <= #{baseDate}::DATE
|
||||
AND (effective_to IS NULL OR effective_to >= #{baseDate}::DATE)
|
||||
@@ -111,8 +111,8 @@
|
||||
effective_from, effective_to, is_active,
|
||||
created_at, created_by, updated_at, updated_by
|
||||
FROM installment_ratio
|
||||
WHERE (product_category = #{productCategory}
|
||||
OR (#{productCategory} IS NULL AND product_category IS NULL))
|
||||
WHERE (product_category = #{productCategory,jdbcType=VARCHAR}
|
||||
OR (#{productCategory,jdbcType=VARCHAR} IS NULL AND product_category IS NULL))
|
||||
AND is_active = TRUE
|
||||
AND effective_from <= #{baseDate}::DATE
|
||||
AND (effective_to IS NULL OR effective_to >= #{baseDate}::DATE)
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
DELETE FROM forecast_kpi_monthly
|
||||
WHERE forecast_month = #{forecastMonth}
|
||||
AND metric_code = #{metricCode}
|
||||
AND ((#{targetOrgId} IS NULL AND target_org_id IS NULL)
|
||||
OR target_org_id = #{targetOrgId})
|
||||
AND ((#{targetOrgId,jdbcType=BIGINT} IS NULL AND target_org_id IS NULL)
|
||||
OR target_org_id = #{targetOrgId,jdbcType=BIGINT})
|
||||
</delete>
|
||||
|
||||
<insert id="upsertBatch">
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
created_at, created_by, updated_at, updated_by
|
||||
FROM regulatory_limit
|
||||
WHERE limit_type = #{limitType}
|
||||
AND (product_category = #{productCategory}
|
||||
OR (#{productCategory} IS NULL AND product_category IS NULL))
|
||||
AND (product_category = #{productCategory,jdbcType=VARCHAR}
|
||||
OR (#{productCategory,jdbcType=VARCHAR} IS NULL AND product_category IS NULL))
|
||||
AND is_active = TRUE
|
||||
AND effective_from <= #{baseDate}::DATE
|
||||
AND (effective_to IS NULL OR effective_to >= #{baseDate}::DATE)
|
||||
|
||||
Reference in New Issue
Block a user