# P10 — 환수 보험사자료 업로드 (#3)

작성: PL, 2026-06-04. 사용자 "후속 갭 3종 진행" 중 첫 번째.
근거: incar `환수보험사자료 업로드`. GA는 LAPSE 계약 기반 자동환수(ChargebackExceptionStep)만 있고,
보험사가 통보한 환수자료(증권번호+환수금액)를 직접 인입하는 경로가 없음.

범위: 풀스택(DB→Core→API→Frontend). 신규 테이블 없음 — 기존 `chargeback`(V6) 재사용.
Flyway: **V112 최신 → V113**(메뉴/권한만). 금액 NUMERIC(15,2).

## 1. chargeback 테이블 (기존, 재사용)
cb_id PK / contract_id FK(nullable) / agent_id FK NN / policy_no / lapse_month INT /
cb_rate / cb_amount NN / paid_amount(0) / remain_amount NN / settle_month CHAR(6) NN /
installment_no / max_installments / status('NEW') / created_at.

## 2. 업로드 설계
- **1 업로드 = 1 settleMonth**(요청 파라미터). 파일 각 행 = 계약별 환수 통보.
- 엑셀 헤더(ChargebackUploadExcelVO, @ExcelColumn): `증권번호`(policyNo, order1) / `환수금액`(cbAmount, order2) / `실효월`(lapseMonth YYYYMM 문자열, order3, 선택) / `환수율`(cbRate, order4, 선택).
- **agent/contract 해소**: policy_no → contract 조회 → contract_id + agent_id. 미발견 행은 에러행으로 수집(스킵, 결과에 행번호+사유 반환). ContractMapper에 `selectByPolicyNo(policyNo)` 없으면 core가 추가(contract 테이블 policy_no 컬럼 확인).
- 생성 ChargebackVO: contractId/agentId(해소), policyNo, lapseMonth(YYYYMM→INT 변환, '202401'→202401 또는 경과월? **chargeback.lapse_month는 INT**: 기존 의미가 경과월(month elapsed)인지 실효연월인지 ChargebackExceptionStep 확인 후 동일 의미로 — 자동환수와 일관성. 불명확하면 실효연월 YYYYMM의 끝2자리(월)이 아니라 **경과월**로: contract_date~lapse_date 개월수 계산. 단순화를 위해 파일에 없으면 null 허용), cbRate, cbAmount, paidAmount=0, remainAmount=cbAmount, settleMonth(param), status='NEW'.
  - **주의**: 자동환수(ChargebackExceptionStep)와 동일 (contract, settleMonth) 중복 생성 방지 — 동일 contract_id+settle_month NEW 존재 시 스킵 또는 갱신(중복 인입 방지). core에 `existsByContractAndMonth` 또는 service에서 selectByAgent로 확인. 최소: 같은 정산월 재업로드 시 중복 INSERT 경고만(운영자 책임). **권장: contract_id+settle_month UNIQUE 부분 보장 위해 service가 사전 조회 후 skip + 결과 리포트.**
- ExcelService.importLargeExcel(file, ChargebackUploadExcelVO.class, 500, consumer) 사용. consumer에서 해소+검증+chargebackMapper.insertBatch. 트랜잭션: @Transactional.

## 3. 조회(List) — 신규
chargeback 목록 화면이 현재 없음. 추가:
- core: ChargebackResp(+agentName/contractNo 조인) / ChargebackSearchParam(settleMonth/agentId/status/policyNo) / ChargebackMapper.selectList + countByCondition. (기존 ChargebackVO 유지)
- api: ChargebackService.list(PageHelper) / detail. 업로드는 ChargebackUploadService.upload(settleMonth, file)→ExcelImportResult-유사(성공/에러행).

## 4. API
- `POST /api/chargebacks/upload?settleMonth=YYYYMM` (multipart file) — @RequirePermission(CHARGEBACK_DATA, CREATE), @DataChangeLog. 반환: {successCount, errorCount, errors[]}.
- `GET /api/chargebacks` — 목록(@RequirePermission READ, PageResponse).
- `GET /api/chargebacks/export` — 엑셀(@Transactional).
컨트롤러 위치 `controller/settle/ChargebackController` 또는 기존 chargeback 패키지 확인.

## 5. DB (V113)
- 메뉴 `CHARGEBACK_DATA`(환수자료관리), 상위 GRP_SETTLE, path `/settle/chargeback-data`, component `settle/ChargebackData`. (V97 단계마감 등 GRP_SETTLE 패턴)
- 권한 READ/CREATE/EXPORT + 역할부여 SUPER_ADMIN·ADMIN 전체, MANAGER READ.
- 공통코드 불필요(chargeback status는 기존 코드 재사용 여부 확인 — 없으면 생략).

## 6. Frontend
- `src/api/chargeback.ts` — list/upload(FormData)/export.
- `src/pages/settle/ChargebackData.tsx` — 상단 정산월 선택 + 파일 업로드 버튼(업로드 결과 성공/에러행 표시) + AG Grid 목록(증권번호/설계사/환수금액/잔여/상태/정산월) + 엑셀. 기존 업로드 화면(UploadPage 등) 결과표시 패턴 참고.
- App.tsx 라우트 `settle/chargeback-data`.

## 7. 검증(PL)
build+test, Flyway V113, GET 목록 200, **샘플 엑셀 업로드 실호출**(정상행 INSERT + 미존재 증권번호 에러행 리포트) 라이브 확인 후 테스트 chargeback 롤백/삭제.
</content>
