feat: P10(#3) 환수 보험사자료 업로드 — 보험사 통보 기반 chargeback 인입 (PL 자율진행)

incar '환수보험사자료 업로드' 대응. GA는 LAPSE 계약 기반 자동환수만 있고 보험사가
통보한 환수자료(증권번호+환수금액)를 직접 인입하는 경로가 없던 갭. 신규 테이블 없이
기존 chargeback(V6) 재사용.

- DB(V113): 메뉴 CHARGEBACK_DATA(환수자료관리, GRP_SETTLE, /settle/chargeback-data)
  + 권한 READ/CREATE/EXPORT + 역할부여.
- core: ChargebackUploadExcelVO(@ExcelColumn 증권번호/환수금액/실효월/환수율) +
  ChargebackResp/SearchParam + ChargebackMapper.selectList/countByCondition/
  existsByContractAndMonth + ContractMapper.selectByPolicyNo(contract_date 포함).
- api: ChargebackUploadService.upload(settleMonth,file) — ExcelService.importLargeExcel
  로 SAX 파싱, policy_no→contract/agent 해소(미존재=에러행), 중복(contract+월) skip,
  실효월 YYYYMM→경과월 변환(ChronoUnit), ChargebackVO status=NEW·remain=cb_amount
  insertBatch. ChargebackService.list/export. ChargebackController(/api/chargebacks
  /upload, GET 목록, export).
- frontend: ChargebackData 화면(정산월+파일 업로드, 결과 성공/스킵/에러행 표시, AG Grid
  목록 합계행, 엑셀) + api/chargeback.ts + App 라우트.

검증: build SUCCESSFUL, Flyway V113(schema v113). 라이브 업로드 e2e — 정상 2건 INSERT
(NEW, remain=cb_amount) + 미존재 증권번호 1건 에러행 리포트 + 중복 skip, GET 목록 200,
경과월 변환 동작 확인. 테스트 chargeback 전건 정리. 스펙 docs/DOMAIN_GAP_P10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
GA Pro
2026-06-05 00:51:08 +09:00
parent 90256a19a1
commit 284ecc4203
15 changed files with 1004 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
import api, { PageResponse, unwrap } from './request';
// ── Types ──────────────────────────────────────────
export type ChargebackStatus = 'NEW' | 'PROCESSING' | 'SETTLED' | 'CANCELLED';
export interface ChargebackRow extends Record<string, unknown> {
cbId: number;
policyNo: string;
agentId: number;
agentName: string;
contractorName: string;
lapseMonth: number | null;
cbRate: number | null;
cbAmount: number;
paidAmount: number;
remainAmount: number;
settleMonth: string;
status: ChargebackStatus;
createdAt: string;
}
export interface ChargebackSearchParam {
settleMonth?: string;
agentId?: number;
status?: string;
policyNo?: string;
pageNum?: number;
pageSize?: number;
}
export interface ChargebackUploadResult {
successCount: number;
errorCount: number;
skipCount: number;
errors: Array<{ rowNo: number; reason: string }>;
}
// ── API ───────────────────────────────────────────
export const chargebackApi = {
list: (p: ChargebackSearchParam) =>
unwrap<PageResponse<ChargebackRow>>(
api.get('/api/chargebacks', { params: p }),
),
upload: (settleMonth: string, file: File) => {
const form = new FormData();
form.append('file', file);
return unwrap<ChargebackUploadResult>(
api.post(`/api/chargebacks/upload?settleMonth=${settleMonth}`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
}),
);
},
export: (p: ChargebackSearchParam) =>
api.get('/api/chargebacks/export', {
params: p,
responseType: 'blob',
}),
};