feat: P9 환수채권 이월/미수금 관리 — 수수료 갭 풀스택 (PL 자율진행)
incar CMS 메뉴(환수이월관리/환수계약리스트/위촉중환수) ↔ GA 부재 + 코드 갭 (AggregateStep net<0 시 경고만, 미회수 환수 채권화·차월상계·대손 생애주기 전무) 을 도출해 풀스택 구현. 미회수 환수가 소실되던 재무 누락을 폐쇄. - DB(V111): clawback_receivable(채권 마스터, UNIQUE agent+origin월) + clawback_recovery(상계 회수 이력) + settle_master 3컬럼(clawback_recovered/ clawback_carryover/payable_amount) + 메뉴 CLAWBACK_RECEIVABLE + 권한 + 공통코드. - core: VO/Resp/SaveReq/SearchParam/Enum/Mapper(.java/.xml) 2테이블 + MapStruct. outstandingBalance(origin<settleMonth 자기상계방지) / FIFO / upsertCarryover(멱등). - api: ClawbackReceivableService/Controller(/api/clawback-receivables) — list/detail/ summary/write-off(OUTSTANDING·RECOVERING만, CLEARED/WRITTEN_OFF 전이)/export(@Transactional). - batch: AggregateStep 통합 — net<0 → carryover 채권화, net>0 → FIFO 상계회수, reverse-먼저 방식으로 동일월 재실행 멱등. net_amount 의미 불변(무결성 보존). payable_amount = max(0,net) − recovered. - fix(잠복): SettleMasterMapper.xml upsert에 신규 3컬럼 누락 → INSERT/ON CONFLICT 추가 (배치가 VO에 set해도 DEFAULT 0으로 미영속되던 버그, PL 검증서 발견). - frontend: ClawbackReceivable 화면(AG Grid 합계행 + 회수이력 타임라인 Drawer + 대손/상환 모달) + api 모듈 + App 라우트. 검증(PL 직접): ./gradlew build(test포함) SUCCESSFUL. Flyway V109~V111 운영DB 적용 (schema v111). 라이브 API: GET 목록/summary 200, write-off WRITTEN_OFF 200(한글사유 영속), 종결 재처리·잘못된 전이상태 E411 차단. 배치 이월/상계/멱등/부분상계 4시나리오 실 스키마 롤백 트랜잭션 검증 PASS(CHECK balance>=0 포함). 잔여 데이터 0. 스펙: docs/DOMAIN_GAP_P9_환수채권이월.md. 후속 갭(시상/인정실적 환수 ledger, 지급대상건 구성, 환수 보험사자료 업로드) 문서화. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import api, { PageResponse, unwrap } from './request';
|
||||
|
||||
// ── Types ──────────────────────────────────────────
|
||||
|
||||
export type ClawbackStatus = 'OUTSTANDING' | 'RECOVERING' | 'CLEARED' | 'WRITTEN_OFF';
|
||||
|
||||
export interface ClawbackReceivableRow extends Record<string, unknown> {
|
||||
receivableId: number;
|
||||
agentId: number;
|
||||
agentName: string;
|
||||
originSettleMonth: string;
|
||||
originalAmount: number;
|
||||
recoveredAmount: number;
|
||||
balance: number;
|
||||
status: ClawbackStatus;
|
||||
statusName: string;
|
||||
lastRecoveryMonth?: string;
|
||||
writeoffReason?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ClawbackRecoveryItem {
|
||||
settleMonth: string;
|
||||
recoveredAmount: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ClawbackReceivableDetail extends ClawbackReceivableRow {
|
||||
recoveries: ClawbackRecoveryItem[];
|
||||
}
|
||||
|
||||
export interface ClawbackSummaryRow extends Record<string, unknown> {
|
||||
agentId: number;
|
||||
agentName: string;
|
||||
totalBalance: number;
|
||||
receivableCount: number;
|
||||
}
|
||||
|
||||
export interface ClawbackSearchParam {
|
||||
agentId?: number;
|
||||
agentName?: string;
|
||||
status?: string;
|
||||
originSettleMonth?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface WriteOffParam {
|
||||
status: 'CLEARED' | 'WRITTEN_OFF';
|
||||
writeoffReason: string;
|
||||
}
|
||||
|
||||
// ── API ───────────────────────────────────────────
|
||||
|
||||
export const clawbackReceivableApi = {
|
||||
list: (p: ClawbackSearchParam) =>
|
||||
unwrap<PageResponse<ClawbackReceivableRow>>(
|
||||
api.get('/api/clawback-receivables', { params: p }),
|
||||
),
|
||||
|
||||
summary: () =>
|
||||
unwrap<ClawbackSummaryRow[]>(
|
||||
api.get('/api/clawback-receivables/summary'),
|
||||
),
|
||||
|
||||
detail: (id: number) =>
|
||||
unwrap<ClawbackReceivableDetail>(
|
||||
api.get(`/api/clawback-receivables/${id}`),
|
||||
),
|
||||
|
||||
writeOff: (id: number, body: WriteOffParam) =>
|
||||
unwrap<ClawbackReceivableRow>(
|
||||
api.put(`/api/clawback-receivables/${id}/write-off`, body),
|
||||
),
|
||||
|
||||
export: (p: ClawbackSearchParam) =>
|
||||
api.get('/api/clawback-receivables/export', {
|
||||
params: p,
|
||||
responseType: 'blob',
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user