Files
ga-commission-system/docs/DOMAIN_GAP_P11_시상환수.md
GA Pro 934e45d295 feat: P11(#1) 시상/신인시상 환수 — award_ledger 환수 처리 (PL 자율진행)
incar 유지율시상환수/신인도입유치자시상환수 대응. award_ledger(V92)는 지급만 있고
환수가 없던 갭. 정착지원금/생보운영지원/지점장수당의 clawback_amount+net_amount 패턴으로
통일(신인시상=award_type NEW_RECRUIT로 기존재 → 별도 테이블 불필요).

- DB(V114): award_ledger에 clawback_amount/net_amount 컬럼 추가 + net=award 백필 +
  CHECK(clawback>=0) + status CHECK에 CLAWED_BACK 추가(pg_constraint 동적 탐색 DROP/재생성).
- core: AwardLedgerVO/Resp/SaveReq에 clawbackAmount/netAmount. AwardLedgerMapper
  aggregateByAgent SUM(award_amount)→SUM(net_amount), insert에 두 컬럼, applyClawback UPDATE.
- api: AwardService.createLedger가 net=award−clawback 계산(clawback≤award 검증). 신규
  applyClawback(ledgerId, {clawbackAmount,reason}) — 초과/취소상태 E411, status=CLAWED_BACK.
  PUT /api/awards/ledgers/{id}/clawback.
- frontend: Award 원장 그리드에 환수액/실지급 컬럼 + 합계 + 환수처리 모달(PermissionButton UPDATE).
- AggregateStep 무변경: aggregateByAgent가 net 반환 → 환수가 other_commission 자동 차감,
  net<0이면 P9 환수채권 이월에 자연 연계.

검증: build+test SUCCESSFUL, Flyway V114(schema v114). 라이브: award 100000 원장 →
clawback 30000 → net 70000·CLAWED_BACK, 초과환수 200000 → E411, aggregate net=70000 반영
확인. 테스트 데이터 정리. 스펙 docs/DOMAIN_GAP_P11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:59:48 +09:00

63 lines
4.3 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.
# P11 — 시상/신인시상 환수 (#1)
작성: PL, 2026-06-04. 후속 갭 3종 중 두 번째.
근거: incar `유지율시상환수`/`신인도입유치자시상환수`/`시상내역 조회`. GA award_ledger(V92)는
지급만 있고 환수(clawback) 처리가 없음. 정착지원금/생보운영지원/지점장수당은 이미
`clawback_amount`+`net_amount` 패턴 보유 → **award_ledger도 동일 패턴으로 통일**.
신인시상은 award_type='NEW_RECRUIT'로 이미 존재 → 별도 테이블 불필요. 모든 award_type에 환수 적용.
Flyway: **V113 최신 → V114**. 금액 NUMERIC(15,2), HALF_UP.
## 1. 현재 award_ledger (V92)
ledger_id / agent_id / settle_month / award_type(NEW_RECRUIT/CORPORATE/RETENTION/PERFORMANCE) /
base_amount / award_amount / status(CALCULATED/CONFIRMED/PAID/CANCELLED) / created_at / updated_at.
aggregateByAgent = SUM(award_amount) WHERE status!=CANCELLED.
**문제**: clawback_amount/net_amount 없음. settling/life/branch는 net=supportclawback, aggregate=SUM(net).
## 2. 스키마 변경 (V114) — settling_support_ledger 패턴 미러
```sql
ALTER TABLE award_ledger
ADD COLUMN clawback_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
ADD COLUMN net_amount NUMERIC(15,2) NOT NULL DEFAULT 0;
-- 기존 행 백필: 환수 없던 과거 = net = award
UPDATE award_ledger SET net_amount = award_amount WHERE net_amount = 0 AND clawback_amount = 0;
-- clawback 음수 방지 + 상태에 CLAWED_BACK 추가(settling 패턴과 통일)
ALTER TABLE award_ledger ADD CONSTRAINT chk_award_clawback CHECK (clawback_amount >= 0);
ALTER TABLE award_ledger DROP CONSTRAINT < status CHECK >; -- \d 로 확인 (예: award_ledger_status_check)
ALTER TABLE award_ledger ADD CONSTRAINT chk_award_status
CHECK (status IN ('CALCULATED','CONFIRMED','PAID','CANCELLED','CLAWED_BACK'));
```
> 기존 status CHECK 제약명은 `\d award_ledger` 또는 information_schema 로 정확히 확인 후 DROP. 추정명 사용 금지.
> net_amount는 음수 허용(환수>시상 시 음수 가능)이나, **§4 검증에서 clawback ≤ award_amount 강제**하므로 실제 net≥0. (지급성이므로 음수 차단 — settling 동일)
메뉴 변경 없음(award 화면 기존). 공통코드 AWARD_STATUS에 CLAWED_BACK 있으면 추가(없으면 생략).
## 3. Core
- AwardLedgerVO/Resp/SaveReq에 `clawbackAmount`, `netAmount` 추가.
- `AwardLedgerMapper.aggregateByAgent` XML: `SUM(award_amount)`**`SUM(net_amount)`** (net=awardclawback). status!=CANCELLED 유지.
- 신규 Mapper 메서드 `applyClawback(@Param ledgerId, @Param clawbackAmount, @Param netAmount, @Param status)` (+XML): clawback_amount/net_amount/status='CLAWED_BACK'/updated_at 갱신.
- createLedger 시 net_amount = award_amount clawback_amount 저장(insert XML에 두 컬럼 포함).
## 4. API (AwardService 확장)
- `createLedger`: clawback_amount(SaveReq, 기본0) 받아 **clawback ≤ award_amount 검증(초과 시 E411)**, net = award clawback 계산해 저장.
- **신규 액션** `applyClawback(ledgerId, AwardClawbackReq{clawbackAmount, reason})` @Transactional:
- 원장 조회(없으면 NOT_FOUND). 이미 CANCELLED면 E411.
- clawbackAmount > award_amount 면 E411("환수액이 시상액 초과").
- net = award_amount clawbackAmount. mapper.applyClawback(ledgerId, clawback, net, 'CLAWED_BACK').
- @DataChangeLog.
- Controller: `PUT /api/awards/ledgers/{id}/clawback` (@RequirePermission(menu=AWARD, action=UPDATE)). 기존 award 컨트롤러 경로 패턴 확인.
> **AggregateStep 무변경**: awardMapper.aggregateByAgent가 SUM(net_amount) 반환하므로 환수가 자동으로 other_commission 차감에 반영. net<0이면 P9 환수채권 이월에 자연 연계.
## 5. Frontend (기존 Award 화면 확장)
- award 원장 그리드에 환수액/실지급(net) 컬럼 추가.
- 행 액션 "환수처리"(PermissionButton UPDATE) → 모달(환수액 입력 + 사유) → PUT /clawback. 성공 시 invalidate.
- api 모듈에 applyClawback 추가. 기존 award api/페이지 파일명 확인 후 확장.
## 6. 검증(PL)
build+test, Flyway V114(award_ledger 컬럼/제약/백필), 라이브: award 원장 1건 생성(net=award) →
applyClawback(부분환수) → net 감소·status CLAWED_BACK 확인 → clawback>award E411 → aggregateByAgent
가 net 반영하는지 SQL 확인. 테스트 데이터 정리.
</content>