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:
@@ -14,6 +14,12 @@ public interface ContractMapper {
|
||||
ContractResp selectDetailById(@Param("contractId") Long contractId);
|
||||
ContractVO selectById(@Param("contractId") Long contractId);
|
||||
int existsByPolicyNo(@Param("policyNo") String policyNo);
|
||||
|
||||
/**
|
||||
* 증권번호로 계약 조회 (업로드 시 agent/contract 해소용).
|
||||
* 반환: ContractVO (contractId + agentId 포함). 미존재 시 null.
|
||||
*/
|
||||
ContractVO selectByPolicyNo(@Param("policyNo") String policyNo);
|
||||
int insert(ContractVO vo);
|
||||
int update(ContractVO vo);
|
||||
int updateStatus(@Param("contractId") Long contractId, @Param("status") String status);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ga.core.mapper.settle;
|
||||
|
||||
import com.ga.core.vo.settle.ChargebackResp;
|
||||
import com.ga.core.vo.settle.ChargebackSearchParam;
|
||||
import com.ga.core.vo.settle.ChargebackVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -20,4 +22,18 @@ public interface ChargebackMapper {
|
||||
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
BigDecimal sumByMonth(@Param("settleMonth") String settleMonth);
|
||||
|
||||
// ── P10 환수자료 업로드 / 조회 ──────────────────────────────────────────
|
||||
/** 환수 목록 조회 (agentName / contractorName 조인 포함). PageHelper 페이징 사용. */
|
||||
List<ChargebackResp> selectList(ChargebackSearchParam param);
|
||||
|
||||
/** 환수 목록 건수 (페이징 없이 count만). */
|
||||
int countByCondition(ChargebackSearchParam param);
|
||||
|
||||
/**
|
||||
* 동일 contract + settleMonth 환수 레코드 존재 여부.
|
||||
* 업로드 중복 방지용. 반환값 > 0 이면 이미 존재.
|
||||
*/
|
||||
int existsByContractAndMonth(@Param("contractId") Long contractId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ga.core.vo.settle;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 환수 목록 조회 응답 VO.
|
||||
*
|
||||
* chargeback 테이블 컬럼 + 조인 필드:
|
||||
* - agentName : agent.agent_name
|
||||
* - contractNo: contract 식별용 (policy_no 와 동일 역할, 조인 시 포함)
|
||||
*/
|
||||
@Data
|
||||
public class ChargebackResp {
|
||||
private Long cbId;
|
||||
private Long contractId;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
/** contract.policy_no (chargeback.policy_no와 동일 값, 조인으로 확인) */
|
||||
private String policyNo;
|
||||
/** contract.contractor_name */
|
||||
private String contractorName;
|
||||
/** 경과월 (months elapsed from contract_date to lapse_date) */
|
||||
private Integer lapseMonth;
|
||||
private BigDecimal cbRate;
|
||||
private BigDecimal cbAmount;
|
||||
private BigDecimal paidAmount;
|
||||
private BigDecimal remainAmount;
|
||||
private String settleMonth;
|
||||
private Integer installmentNo;
|
||||
private Integer maxInstallments;
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ga.core.vo.settle;
|
||||
|
||||
import com.ga.common.model.SearchParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 환수 목록 검색 파라미터.
|
||||
*
|
||||
* 상속 필드(SearchParam): pageNum / pageSize / status / searchKeyword / startDate / endDate
|
||||
* 추가 필드: settleMonth / agentId / policyNo
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChargebackSearchParam extends SearchParam {
|
||||
/** 정산월 YYYYMM */
|
||||
private String settleMonth;
|
||||
private Long agentId;
|
||||
/** 증권번호 검색 */
|
||||
private String policyNo;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ga.core.vo.settle;
|
||||
|
||||
import com.ga.common.annotation.ExcelColumn;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 보험사 환수자료 업로드용 엑셀 VO.
|
||||
*
|
||||
* 파일 헤더 순서: 증권번호 / 환수금액 / 실효월 / 환수율
|
||||
* - 실효월(lapseMonth): YYYYMM 문자열, 선택(없으면 null).
|
||||
* 파일에 입력된 실효연월(예: 202401)을 서비스 계층에서
|
||||
* contract_date 기준 경과월(INT)로 변환 후 ChargebackVO.lapseMonth에 저장.
|
||||
* - 환수율(cbRate): DECIMAL(7,4), 선택.
|
||||
*/
|
||||
@Data
|
||||
public class ChargebackUploadExcelVO {
|
||||
|
||||
@ExcelColumn(header = "증권번호", order = 1)
|
||||
private String policyNo;
|
||||
|
||||
@ExcelColumn(header = "환수금액", order = 2)
|
||||
private BigDecimal cbAmount;
|
||||
|
||||
/** 실효월 YYYYMM (선택). 서비스에서 경과월로 변환. */
|
||||
@ExcelColumn(header = "실효월", order = 3)
|
||||
private String lapseMonth;
|
||||
|
||||
/** 환수율 (선택). */
|
||||
@ExcelColumn(header = "환수율", order = 4)
|
||||
private BigDecimal cbRate;
|
||||
}
|
||||
@@ -68,6 +68,19 @@
|
||||
SELECT COUNT(*) FROM contract WHERE policy_no = #{policyNo}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
증권번호로 계약 조회 (P10 환수자료 업로드 시 agent/contract 해소용).
|
||||
contract.policy_no 는 UNIQUE NOT NULL(V2).
|
||||
contractId + agentId 만 필요하므로 ContractVOMap 재사용.
|
||||
미존재 시 null 반환.
|
||||
-->
|
||||
<select id="selectByPolicyNo" resultMap="ContractVOMap">
|
||||
SELECT contract_id, agent_id, policy_no, contract_date, status
|
||||
FROM contract
|
||||
WHERE policy_no = #{policyNo}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.ga.core.vo.product.ContractVO" useGeneratedKeys="true" keyProperty="contractId">
|
||||
INSERT INTO contract (agent_id, product_id, policy_no, contractor_name, insured_name,
|
||||
premium, pay_cycle, pay_period, coverage_period, status,
|
||||
|
||||
@@ -5,6 +5,56 @@
|
||||
|
||||
<resultMap id="CBMap" type="com.ga.core.vo.settle.ChargebackVO"/>
|
||||
|
||||
<resultMap id="CBRespMap" type="com.ga.core.vo.settle.ChargebackResp"/>
|
||||
|
||||
<!-- 조회 공통 컬럼: chargeback 전체 + 조인 필드 -->
|
||||
<sql id="cbRespCols">
|
||||
cb.cb_id,
|
||||
cb.contract_id,
|
||||
cb.agent_id,
|
||||
a.agent_name,
|
||||
cb.policy_no,
|
||||
ct.contractor_name,
|
||||
cb.lapse_month,
|
||||
cb.cb_rate,
|
||||
cb.cb_amount,
|
||||
cb.paid_amount,
|
||||
cb.remain_amount,
|
||||
cb.settle_month,
|
||||
cb.installment_no,
|
||||
cb.max_installments,
|
||||
cb.status,
|
||||
cb.created_at
|
||||
</sql>
|
||||
|
||||
<!-- 동적 WHERE 조건 (selectList / countByCondition 공용) -->
|
||||
<sql id="cbWhere">
|
||||
<where>
|
||||
<if test="settleMonth != null and settleMonth != ''">
|
||||
AND cb.settle_month = #{settleMonth}
|
||||
</if>
|
||||
<if test="agentId != null">
|
||||
AND cb.agent_id = #{agentId}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND cb.status = #{status}
|
||||
</if>
|
||||
<if test="policyNo != null and policyNo != ''">
|
||||
AND cb.policy_no = #{policyNo}
|
||||
</if>
|
||||
<if test="searchKeyword != null and searchKeyword != ''">
|
||||
AND (cb.policy_no LIKE CONCAT('%', #{searchKeyword}, '%')
|
||||
OR a.agent_name LIKE CONCAT('%', #{searchKeyword}, '%'))
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND cb.created_at >= #{startDate}::date
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND cb.created_at <= (#{endDate}::date + INTERVAL '1 day')
|
||||
</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<insert id="insert" parameterType="com.ga.core.vo.settle.ChargebackVO" useGeneratedKeys="true" keyProperty="cbId">
|
||||
INSERT INTO chargeback (contract_id, agent_id, policy_no, lapse_month, cb_rate,
|
||||
cb_amount, paid_amount, remain_amount, settle_month,
|
||||
@@ -48,4 +98,43 @@
|
||||
SELECT COALESCE(SUM(cb_amount), 0) FROM chargeback WHERE settle_month = #{settleMonth}
|
||||
</select>
|
||||
|
||||
<!-- ── P10 환수자료 업로드 / 조회 ────────────────────────────────────── -->
|
||||
|
||||
<!--
|
||||
환수 목록 조회.
|
||||
PageHelper.startPage() 호출 후 이 쿼리 실행 시 자동 페이징.
|
||||
agent / contract 좌조인: contract 없는 행(보험사 직접 통보, contract_id NULL)도 포함.
|
||||
-->
|
||||
<select id="selectList" resultMap="CBRespMap">
|
||||
SELECT <include refid="cbRespCols"/>
|
||||
FROM chargeback cb
|
||||
JOIN agent a ON a.agent_id = cb.agent_id
|
||||
LEFT JOIN contract ct ON ct.contract_id = cb.contract_id
|
||||
<include refid="cbWhere"/>
|
||||
ORDER BY cb.created_at DESC, cb.cb_id DESC
|
||||
</select>
|
||||
|
||||
<!--
|
||||
페이징 없이 총 건수만 반환.
|
||||
PageHelper 없이 count가 필요한 경우(별도 카운트 쿼리) 사용.
|
||||
-->
|
||||
<select id="countByCondition" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM chargeback cb
|
||||
JOIN agent a ON a.agent_id = cb.agent_id
|
||||
LEFT JOIN contract ct ON ct.contract_id = cb.contract_id
|
||||
<include refid="cbWhere"/>
|
||||
</select>
|
||||
|
||||
<!--
|
||||
동일 contract_id + settle_month 환수 레코드 존재 여부.
|
||||
반환값 > 0 이면 중복 → 업로드 시 해당 행 스킵 + 에러행 리포트.
|
||||
-->
|
||||
<select id="existsByContractAndMonth" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM chargeback
|
||||
WHERE contract_id = #{contractId}
|
||||
AND settle_month = #{settleMonth}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user