Files
ga-commission-system/docs/시나리오_E2E_보험인입_수수료정산.md
GA Pro 3d50286f4a test: E2E 수수료 정산 시나리오 테스트 + 엑셀업로드/분급 버그 수정
- 시나리오 문서: 보험 인입→수수료 지급 8단계 (docs/시나리오_E2E_보험인입_수수료정산.md)
- SettlementScenarioTest(9건): 계약1건 생애주기를 실제 계산기로 단계별 검증
- ExcelServiceImportTest(2건): 실 .xlsx 업로드 라운드트립 검증
- fix(CRITICAL): 엑셀 업로드 런타임 깨짐 — xlsx-streamer 2.2.0 ↔ poi 5.2.5
  비호환(NoSuchMethodError). excel-streaming-reader 4.3.0(유지보수 fork)로 교체.
  영향: ExcelService.importLargeExcel + UploadService(/api/upload 실엔드포인트)
- fix: 분급 계획 반올림 잔차로 분급합계≠원금(정산 무결성 위반). 비율합 100%
  정규스케줄일 때 마지막 회차가 잔차 흡수. ga-api/ga-batch 양쪽.
- 전체 build+test GREEN: 148건 0실패

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:02:55 +09:00

169 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# E2E 시나리오 — 보험이 어떻게 들어와서 수수료가 어떻게 나가는가
> 이 문서는 **계약 1건이 인입되어 설계사 통장에 돈이 꽂힐 때까지**의 전체 순서를
> "등장인물 + 숫자"로 따라가는 시나리오입니다.
> 각 단계는 실제 계산 코드(`CommissionCalculator`, `RegulatoryLimitChecker`,
> `InstallmentPlanGenerator`, `ChargebackCalculator`, `TaxCalculator`)와 1:1로 대응하며,
> 이 시나리오를 그대로 검증하는 단위테스트가 `SettlementScenarioTest` 입니다.
---
## 0. 등장인물 (이 시나리오의 테스트 데이터)
| 역할 | 식별자 | 속성 |
|---|---|---|
| 원 모집설계사 **김설계** | `agentId=10`, `gradeId=5` | 사업소득(BUSINESS_INCOME) · 과세사업자(TAXABLE) |
| 후임 승계설계사 **박승계** | `agentId=20`, `gradeId=3` | (계약 승계 단계에서 등장) |
| 상위 직책자 **이지점** | `agentId=30` | 오버라이드 수령 |
| 상품 **무배당 종신보험** | `productId=1`, `WHOLE_LIFE`, 카테고리 `LIFE` | |
| 계약 | `contractId=100`, `policyNo=POL-0001` | 연납보험료 **1,000,000원**, 계약일 **2024-01-15** |
기준 룰(테스트에서 Mock으로 주입하는 데이터):
| 룰 | 값 |
|---|---|
| 보험사요율(company_rate) 1차년 | 60% |
| 보험사요율 2차년 | 10% |
| 지급률(payout_rate) 등급5·WHOLE_LIFE·1차년 | 75% |
| 지급률 등급5·2차년 | 50% |
| 지급률 등급3(후임)·2차년 | 40% |
| 분급 비율(7년) | 35 / 25 / 15 / 10 / 7 / 5 / 3 (%) |
| 차등환수 13~24개월 구간 | 50% |
| 1200% 한도(TOTAL_RATIO) | 12,000,000원 (연납 1,000,000 × 1200%) |
| 원천세 | 사업소득 3.3% · 지방세 = 소득세 10% · 부가세 10%(과세사업자) |
---
## 1. 한눈에 보는 순서
```
[보험사] 실적데이터 인입
│ (Step1 receive → Step2 transform → Step3 reconcile)
① 모집수수료 원장 계산 (Step4 calcRecruit) ← premium × company_rate × payout_rate
② 1200% 룰 한도 체크 (RegulatoryLimitChecker) ← 누적이 한도를 넘으면 잘라냄
③ 분급(7년) 계획 생성 (InstallmentPlanGenerator) ← 큰 모집수수료를 7년에 나눠 지급
④ 유지수수료 원장 계산 (Step5 calcMaintain) ← 2차년 이후 매년
│ └ ⑤ 계약 승계 시 후임에게 귀속 전환 (모집은 원설계사 유지)
⑥ 오버라이드 계산 (Step7 calcOverride) ← 하위실적 × 율 (cap 제한)
⑦ 조기 해지 환수 (Step6 chargeback) ← 유지개월 짧을수록 많이 환수
⑧ 설계사×월 집계 (Step8 aggregate) ← gross 합산
│ └ 세금 원천징수 + 공제 차감 → net(실지급액)
[설계사 통장] ← 펌뱅킹 이체(KFTC)
```
---
## 2. 단계별 숫자 (시나리오 본문)
### ① 모집수수료 원장 계산 — 보험이 "들어오는" 첫 돈
계약이 인입되면 설계사 김설계(등급5)의 모집수수료 원장 1줄이 생긴다.
```
① 보험사지급액 = 보험료 1,000,000 × 보험사요율 60% = 600,000
② 설계사수수료 = 600,000 × 지급률 75% = 450,000 ← gross
③ 원장 원천세 = 450,000 × 3.3% = 14,850
```
→ 원장: `agent_id=10`, `commission_year=1`, `agent_amount=450,000`, `tax_amount=14,850`
### ② 1200% 룰 한도 체크
초년도 모집수수료 누적이 연납보험료의 1200%(=12,000,000)를 넘으면 안 된다.
- 현재 누적 0 + 450,000 = 450,000 ≤ 12,000,000 → **통과**
- 만약 추가분이 한도를 넘기면 `ViolationResult.violated` 로 막고 초과분을 잘라낸다.
### ③ 분급(7년 분할) 계획 생성
모집수수료 450,000을 한 번에 안 주고 7년에 나눈다.
| 차년 | 비율 | 지급액 |
|---|---|---|
| 1 | 35% | 157,500 |
| 2 | 25% | 112,500 |
| 3 | 15% | 67,500 |
| 4 | 10% | 45,000 |
| 5 | 7% | 31,500 |
| 6 | 5% | 22,500 |
| 7 | 3% | 13,500 |
| **합** | **100%** | **450,000** |
> **무결성 규칙:** 분급액의 **합은 원수수료와 정확히 같아야 한다.**
> 금액이 딱 나눠떨어지지 않을 때(예: 435,150원) 회차별 반올림 잔차가 생기는데,
> 이때는 **마지막 회차가 잔차를 흡수**해서 합계를 원금에 맞춘다. (→ §4 보완)
### ④ 유지수수료 (2차년)
계약이 유지되면 2차년부터 유지수수료가 매년 발생한다.
```
보험사지급액 = 1,000,000 × 10%(2차년 요율) = 100,000
설계사수수료 = 100,000 × 50%(등급5 2차년) = 50,000
```
### ⑤ 계약 승계 (김설계 해촉 → 박승계 후임)
김설계가 해촉되면 그 계약의 **유지수수료만** 후임 박승계(등급3)에게 넘어간다.
```
유지수수료(후임) = 100,000 × 40%(등급3 2차년) = 40,000 → 원장 agent_id = 20(박승계)
```
- **모집수수료는 원 모집설계사 김설계(10)에게 그대로 남는다.** (고아계약 승계 표준)
### ⑥ 오버라이드 (상위 직책자 이지점)
하위 김설계의 실적에 대해 상위 이지점이 받는 수당.
```
오버라이드 = 하위실적 2,000,000 × 4% = 80,000 (cap 100,000 미만이므로 그대로)
```
### ⑦ 조기 해지 환수 — 13개월차 해지
계약이 13개월차(2024-01-15 → 2025-02-15)에 깨지면 13~24개월 구간 환수율 50%.
```
환수액 = 기지급 모집수수료 450,000 × 50% = 225,000 ← 다음 정산에서 (−) 차감
```
### ⑧ 집계 → 세금 → 실지급액(net)
김설계 한 달치를 settle_master로 집계하고 세금·공제를 반영한다.
과세사업자(TAXABLE) gross 450,000 기준:
```
소득세 = 450,000 × 3.3% = 14,850
지방세 = 14,850 × 10% = 1,485
부가세 = 450,000 × 10% = 45,000 (과세사업자만 +)
실지급 = 450,000 14,850 1,485 + 45,000 = 478,665
```
- 여기에 가지급금 공제 100,000이 있으면 net = 478,665 100,000 = **378,665**
- 비과세(NONE) 설계사는 부가세가 없어 net = 450,000 14,850 1,485 = **433,665**
---
## 3. 시나리오 ↔ 코드 ↔ 테스트 대응표
| 단계 | 코드 | 테스트 메서드 |
|---|---|---|
| ① 모집수수료 | `CommissionCalculator.calcRecruitLedger` | `s1_보험인입_모집수수료_원장계산` |
| ② 1200% 룰 | `BatchRegulatoryLimitChecker.checkContractTotal` | `s2_규제_1200퍼센트_한도_통과및위반` |
| ③ 분급 7년 | `BatchInstallmentPlanGenerator.generate` | `s3_분급_7년_분할_합계100퍼센트` |
| ③ 분급 잔차 | 〃 | `s3b_분급_라운딩_잔차_마지막회차_흡수` |
| ④ 유지수수료 | `CommissionCalculator.calcMaintainLedger` | `s4_유지수수료_2차년` |
| ⑤ 계약 승계 | `CommissionCalculator.calcMaintainLedger(effectiveAgentId)` | `s5_계약승계_유지수수료_후임귀속` |
| ⑦ 차등 환수 | `BatchChargebackCalculator.calculate` | `s6_조기해지_차등환수_13개월_50퍼센트` |
| ⑧ 세금/실지급 | `BatchTaxCalculator.calculate` | `s7_세금_실지급액_과세사업자` / `s8_세금_비과세` |
---
## 4. 이 시나리오 작성 중 발견·보완한 사항
**분급 계획 합계 불일치(반올림 잔차) — 수정함.**
- 현상: 분급액을 회차별로 `총액 × 비율 / 100` 으로 각각 반올림하면,
원금이 7로 나눠떨어지지 않을 때 분급 합계가 원금과 1~수원 어긋났다.
(예: 435,150원 → 회차 합 435,153원, +3원 누수)
- 영향: 분급 총합 ≠ 원 모집수수료 → 정산 무결성(합계 일치) 위반, 과/미지급.
- 수정: **비율 합이 정확히 100%인 정규 스케줄**일 때 마지막 회차가
`원금 직전 회차 합` 으로 잔차를 흡수하도록 보정.
(비율 합이 100% 미만인 부분지급 스케줄은 기존 동작 유지 → 설계 의도 보존)
- 대상: `InstallmentPlanGenerator`(ga-api), `BatchInstallmentPlanGenerator`(ga-batch) 양쪽.
---
*기준: schema v108, DOMAIN_KNOWLEDGE.md 7장 공식. 모든 금액 BigDecimal HALF_UP.*