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

4.3 KiB
Raw Permalink Blame History

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 패턴 미러

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 확인. 테스트 데이터 정리.