feat: 계약 승계(contract succession) — 유지수수료 귀속 후임 전환 (V86)
설계사 해촉/이관 시 보험계약의 유지수수료 귀속을 승계월 기준 후임 설계사로 전환. 모집수수료는 원 모집설계사 귀속 유지(고아계약 승계 표준). - DB V86: contract_succession 테이블 + 인덱스 + 메뉴/권한(GRP_PRD) + 공통코드 2그룹 + 테스트데이터 1건 - core: ContractSuccession VO/Resp/SaveReq/SearchParam + Mapper(.java/.xml) selectEffectiveSuccessions(settleMonth): 계약별 최근 ACTIVE 승계 1건 (DISTINCT ON) - api: ContractSuccessionService/Controller (/api/contract-successions, CRUD+cancel) - batch: CalcMaintainStep 이 effective 승계 사전로드(N+1 회피) 후 effectiveAgentId 결정, CommissionCalculator.calcMaintainLedger 오버로드로 후임 등급 payout + ledger agent_id 후임 기록. AggregateStep 은 ledger agent_id 집계라 무변경. - frontend: api/succession.ts + ContractSuccessions.tsx + App.tsx 라우트 검증: 5모듈 compileJava SUCCESS / ga-frontend tsc 0 / Flyway v86 적용 / API CRUD+cancel+from==to 가드(E411) 라이브 PASS / resolver 쿼리 라이브 검증(계약1→후임 agent2). 배치 풀잡 e2e 는 정산월 원장 재생성(파괴적)이라 미실행. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package com.ga.core.mapper.succession;
|
||||
|
||||
import com.ga.core.vo.succession.ContractSuccessionResp;
|
||||
import com.ga.core.vo.succession.ContractSuccessionSearchParam;
|
||||
import com.ga.core.vo.succession.ContractSuccessionVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ContractSuccessionMapper {
|
||||
ContractSuccessionResp selectById(@Param("successionId") Long successionId);
|
||||
List<ContractSuccessionResp> selectList(ContractSuccessionSearchParam param);
|
||||
|
||||
/**
|
||||
* 배치 유지수수료 계산용 — 정산월 기준 유효한 승계의 (계약, 후임설계사) 매핑.
|
||||
* 계약별로 succession_date 의 YYYYMM 이 정산월 이하인 가장 최근 ACTIVE 승계 1건만.
|
||||
*/
|
||||
List<ContractSuccessionVO> selectEffectiveSuccessions(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(ContractSuccessionVO vo);
|
||||
int cancel(@Param("successionId") Long successionId, @Param("updatedBy") Long updatedBy);
|
||||
int delete(@Param("successionId") Long successionId);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ga.core.vo.succession;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ContractSuccessionResp {
|
||||
private Long successionId;
|
||||
private Long contractId;
|
||||
private String policyNo;
|
||||
private Long fromAgentId;
|
||||
private String fromAgentName;
|
||||
private Long toAgentId;
|
||||
private String toAgentName;
|
||||
private LocalDate successionDate;
|
||||
private String reason;
|
||||
private String reasonName;
|
||||
private String memo;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ga.core.vo.succession;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class ContractSuccessionSaveReq {
|
||||
@NotNull
|
||||
private Long contractId;
|
||||
@NotNull
|
||||
private Long fromAgentId;
|
||||
@NotNull
|
||||
private Long toAgentId;
|
||||
@NotNull
|
||||
private LocalDate successionDate;
|
||||
/** TERMINATION/TRANSFER/REASSIGN (기본 TERMINATION) */
|
||||
private String reason;
|
||||
private String memo;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ga.core.vo.succession;
|
||||
|
||||
import com.ga.common.model.SearchParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ContractSuccessionSearchParam extends SearchParam {
|
||||
private Long contractId;
|
||||
private Long fromAgentId;
|
||||
private Long toAgentId;
|
||||
private String reason;
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ga.core.vo.succession;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* contract_succession — 계약 승계 (V86)
|
||||
* 유지수수료 귀속 설계사를 승계일 기준으로 전임→후임 전환.
|
||||
*/
|
||||
@Data
|
||||
public class ContractSuccessionVO {
|
||||
private Long successionId;
|
||||
private Long contractId;
|
||||
private Long fromAgentId;
|
||||
private Long toAgentId;
|
||||
private LocalDate successionDate;
|
||||
/** SUCCESSION_REASON: TERMINATION/TRANSFER/REASSIGN */
|
||||
private String reason;
|
||||
private String memo;
|
||||
/** SUCCESSION_STATUS: ACTIVE/CANCELED */
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
private Long createdBy;
|
||||
private LocalDateTime updatedAt;
|
||||
private Long updatedBy;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ga.core.mapper.succession.ContractSuccessionMapper">
|
||||
|
||||
<resultMap id="VOMap" type="com.ga.core.vo.succession.ContractSuccessionVO"/>
|
||||
<resultMap id="RespMap" type="com.ga.core.vo.succession.ContractSuccessionResp"/>
|
||||
|
||||
<sql id="joinCols">
|
||||
cs.succession_id, cs.contract_id, ct.policy_no,
|
||||
cs.from_agent_id, fa.agent_name AS from_agent_name,
|
||||
cs.to_agent_id, ta.agent_name AS to_agent_name,
|
||||
cs.succession_date,
|
||||
cs.reason,
|
||||
(SELECT cc.code_name FROM common_code cc
|
||||
WHERE cc.group_code = 'SUCCESSION_REASON' AND cc.code = cs.reason) AS reason_name,
|
||||
cs.memo,
|
||||
cs.status,
|
||||
(SELECT cc.code_name FROM common_code cc
|
||||
WHERE cc.group_code = 'SUCCESSION_STATUS' AND cc.code = cs.status) AS status_name,
|
||||
cs.created_at
|
||||
</sql>
|
||||
|
||||
<select id="selectById" resultMap="RespMap">
|
||||
SELECT <include refid="joinCols"/>
|
||||
FROM contract_succession cs
|
||||
LEFT JOIN contract ct ON ct.contract_id = cs.contract_id
|
||||
LEFT JOIN agent fa ON fa.agent_id = cs.from_agent_id
|
||||
LEFT JOIN agent ta ON ta.agent_id = cs.to_agent_id
|
||||
WHERE cs.succession_id = #{successionId}
|
||||
</select>
|
||||
|
||||
<select id="selectList" resultMap="RespMap">
|
||||
SELECT <include refid="joinCols"/>
|
||||
FROM contract_succession cs
|
||||
LEFT JOIN contract ct ON ct.contract_id = cs.contract_id
|
||||
LEFT JOIN agent fa ON fa.agent_id = cs.from_agent_id
|
||||
LEFT JOIN agent ta ON ta.agent_id = cs.to_agent_id
|
||||
<where>
|
||||
<if test="contractId != null">AND cs.contract_id = #{contractId}</if>
|
||||
<if test="fromAgentId != null">AND cs.from_agent_id = #{fromAgentId}</if>
|
||||
<if test="toAgentId != null">AND cs.to_agent_id = #{toAgentId}</if>
|
||||
<if test="reason != null and reason != ''">AND cs.reason = #{reason}</if>
|
||||
<if test="status != null and status != ''">AND cs.status = #{status}</if>
|
||||
</where>
|
||||
ORDER BY cs.succession_date DESC, cs.succession_id DESC
|
||||
</select>
|
||||
|
||||
<!-- 정산월 기준 계약별 유효 승계 (가장 최근 ACTIVE 1건) -->
|
||||
<select id="selectEffectiveSuccessions" resultMap="VOMap">
|
||||
SELECT DISTINCT ON (cs.contract_id)
|
||||
cs.contract_id, cs.to_agent_id, cs.from_agent_id, cs.succession_date, cs.status
|
||||
FROM contract_succession cs
|
||||
WHERE cs.status = 'ACTIVE'
|
||||
AND TO_CHAR(cs.succession_date, 'YYYYMM') <= #{settleMonth}
|
||||
ORDER BY cs.contract_id, cs.succession_date DESC, cs.succession_id DESC
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="successionId">
|
||||
INSERT INTO contract_succession
|
||||
(contract_id, from_agent_id, to_agent_id, succession_date, reason, memo, status, created_by)
|
||||
VALUES
|
||||
(#{contractId}, #{fromAgentId}, #{toAgentId}, #{successionDate},
|
||||
COALESCE(#{reason}, 'TERMINATION'), #{memo}, 'ACTIVE', #{createdBy})
|
||||
</insert>
|
||||
|
||||
<update id="cancel">
|
||||
UPDATE contract_succession
|
||||
SET status = 'CANCELED',
|
||||
updated_at = NOW(),
|
||||
updated_by = #{updatedBy}
|
||||
WHERE succession_id = #{successionId}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
DELETE FROM contract_succession WHERE succession_id = #{successionId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user