fix: 모집정산 재실행 시 1200% clip 분급 UNIQUE 충돌 — 멱등성 보강

CalcRecruitStep(Step4)이 1200%룰 clip 초과분을 installment_plan(7년)으로 생성할 때,
재계산을 위해 recruit_ledger는 deleteBySettleMonth로 지우면서 분급은 정리하지 않아
같은 정산월을 재실행하면 동일 계약이 또 clip되어 동일 분급키(contract_id,plan_year,
plan_month)로 INSERT → UNIQUE 충돌로 Job 실패하던 버그.

수정: insertBatch 직전에 clip된 계약들의 SCHEDULED(미지급) 분급을 먼저 삭제
(InstallmentPlanMapper.deleteScheduledByContractIds). PAID 분급 이력은 보존하여
PayInstallmentStep 지급분과 충돌하지 않음.

검증: CalcRecruitStepTest 멱등 테스트(clip 시 delete→insert 순서/대상계약 검증) 추가,
전체 build SUCCESS. 라이브 E2E: 202605 정산을 연속 2회 실행 → 둘 다 COMPLETED,
분급 196건 불변(중복 0), settle_master gross 동일, 무결성 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
GA Pro
2026-06-13 00:09:35 +09:00
parent 17ab1098ec
commit 2a27aa31a0
4 changed files with 52 additions and 0 deletions
@@ -31,6 +31,13 @@ public interface InstallmentPlanMapper {
/** 7년치 분급 계획 일괄 생성 */
int insertBatch(@Param("list") List<InstallmentPlanVO> list);
/**
* 지정 계약들의 SCHEDULED(미지급) 분급 계획을 삭제. 모집정산(Step4) 재실행 시
* clip으로 재생성할 분급 계획이 기존 분급과 UNIQUE(contract_id,plan_year,plan_month)
* 충돌하는 것을 막는 멱등 장치. 이미 지급(PAID)된 분급은 이력 보존을 위해 건드리지 않는다.
*/
int deleteScheduledByContractIds(@Param("contractIds") List<Long> contractIds);
int update(InstallmentPlanVO vo);
int updateStatus(@Param("planId") Long planId, @Param("status") String status);
@@ -197,4 +197,14 @@
DELETE FROM installment_plan WHERE contract_id = #{contractId}
</delete>
<!-- 모집정산 재실행 멱등: 지정 계약들의 미지급(SCHEDULED) 분급만 삭제 (PAID 이력은 보존) -->
<delete id="deleteScheduledByContractIds">
DELETE FROM installment_plan
WHERE status = 'SCHEDULED'
AND contract_id IN
<foreach collection="contractIds" item="cid" open="(" separator="," close=")">
#{cid}
</foreach>
</delete>
</mapper>