From 60c4cbae783a5772a2a696611a29eb0c6039dd03 Mon Sep 17 00:00:00 2001 From: GA Pro Date: Wed, 3 Jun 2026 10:38:09 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=98=A4=EB=B2=84=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20insurance=5Ftype=5Fcond=20=EB=AF=B8=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EA=B3=BC=EC=A7=80=EA=B8=89=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(PL=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EC=A7=84=ED=96=89?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(HIGH): CalcOverrideStep base 가 override_rule.insurance_type_cond 를 무시하고 하위 설계사의 전 보험종류 실적 합계에 율을 곱해 과지급. 룰에 보험종류 조건이 지정되면(LIFE 등) 해당 종류 실적만 base 로 집계하도록 수정. (현 시드는 insurance_type_cond NULL 이라 잠복상태였으나 종류한정 룰 도입 시 과지급) - add: RecruitLedgerMapper/MaintainLedgerMapper.sumByAgentMonthAndType + XML - test: CalcOverrideStepTest — LIFE 한정 룰 base 필터 검증 - 전체 build+test GREEN: 153건 0실패 잔여 백로그(정책결정 필요 — 자동수정 보류): 1200%룰 초과분 clip/이연(지급정책), 계약승계 from검증(다단계 체인+contract.agent_id 갱신정책), 정착지원금 환수공식(입력 스키마), 회계 원천세 예수금 분개(회계정책), net음수 클램프(이월저장), 세금 3중경로 통일. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../settlement/step/CalcOverrideStep.java | 17 ++- .../settlement/step/CalcOverrideStepTest.java | 101 ++++++++++++++++++ .../mapper/ledger/MaintainLedgerMapper.java | 5 + .../mapper/ledger/RecruitLedgerMapper.java | 5 + .../mapper/ledger/MaintainLedgerMapper.xml | 6 ++ .../mapper/ledger/RecruitLedgerMapper.xml | 6 ++ 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 ga-batch/src/test/java/com/ga/batch/settlement/step/CalcOverrideStepTest.java diff --git a/ga-batch/src/main/java/com/ga/batch/settlement/step/CalcOverrideStep.java b/ga-batch/src/main/java/com/ga/batch/settlement/step/CalcOverrideStep.java index d3745fe..22705fe 100644 --- a/ga-batch/src/main/java/com/ga/batch/settlement/step/CalcOverrideStep.java +++ b/ga-batch/src/main/java/com/ga/batch/settlement/step/CalcOverrideStep.java @@ -80,7 +80,8 @@ public class CalcOverrideStep implements Tasklet { lower.getOrgId(), rule.getToGrade(), lower.getAgentId(), upperCache); if (upperAgentId == null) continue; - BigDecimal base = sumLowerCommission(lower.getAgentId(), ctx.getSettleMonth()); + BigDecimal base = sumLowerCommission( + lower.getAgentId(), ctx.getSettleMonth(), rule.getInsuranceTypeCond()); if (MoneyUtil.isZero(base)) continue; BigDecimal amount = MoneyUtil.pct(base, rule.getOverridePct()); @@ -105,8 +106,18 @@ public class CalcOverrideStep implements Tasklet { return RepeatStatus.FINISHED; } - /** 하위 설계사의 정산월 모집 + 유지 합계 */ - private BigDecimal sumLowerCommission(Long agentId, String settleMonth) { + /** + * 하위 설계사의 정산월 모집 + 유지 합계. + * 룰에 insurance_type_cond 가 지정되면 해당 보험종류 실적만 base 로 집계. + * (지정 없음/ALL 이면 전 보험종류 합계 — 보험종류 한정 룰이 전체 실적에 곱해져 과지급되는 것을 방지) + */ + private BigDecimal sumLowerCommission(Long agentId, String settleMonth, String insuranceTypeCond) { + if (insuranceTypeCond != null && !insuranceTypeCond.isBlank() + && !"ALL".equalsIgnoreCase(insuranceTypeCond)) { + BigDecimal r = recruitMapper.sumByAgentMonthAndType(agentId, settleMonth, insuranceTypeCond); + BigDecimal m = maintainMapper.sumByAgentMonthAndType(agentId, settleMonth, insuranceTypeCond); + return MoneyUtil.add(r, m); + } BigDecimal r = recruitMapper.sumByAgentMonth(agentId, settleMonth); BigDecimal m = maintainMapper.sumByAgentMonth(agentId, settleMonth); return MoneyUtil.add(r, m); diff --git a/ga-batch/src/test/java/com/ga/batch/settlement/step/CalcOverrideStepTest.java b/ga-batch/src/test/java/com/ga/batch/settlement/step/CalcOverrideStepTest.java new file mode 100644 index 0000000..046f807 --- /dev/null +++ b/ga-batch/src/test/java/com/ga/batch/settlement/step/CalcOverrideStepTest.java @@ -0,0 +1,101 @@ +package com.ga.batch.settlement.step; + +import com.ga.batch.settlement.SettlementContext; +import com.ga.core.mapper.ledger.MaintainLedgerMapper; +import com.ga.core.mapper.ledger.RecruitLedgerMapper; +import com.ga.core.mapper.org.AgentMapper; +import com.ga.core.mapper.org.OrganizationMapper; +import com.ga.core.mapper.rule.RuleMapper; +import com.ga.core.mapper.settle.OverrideSettleMapper; +import com.ga.core.vo.org.AgentResp; +import com.ga.core.vo.org.AgentSearchParam; +import com.ga.core.vo.rule.OverrideRuleVO; +import com.ga.core.vo.settle.OverrideSettleVO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.math.BigDecimal; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * 오버라이드 보험종류 조건(insurance_type_cond) 적용 검증. + * 룰에 insurance_type_cond='LIFE' 가 있으면 base 가 LIFE 실적만으로 집계되어야 한다 + * (전 보험종류 합계에 율을 곱해 과지급되던 결함 수정). + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class CalcOverrideStepTest { + + @Mock SettlementContext ctx; + @Mock RuleMapper ruleMapper; + @Mock AgentMapper agentMapper; + @Mock OrganizationMapper organizationMapper; + @Mock OverrideSettleMapper overrideMapper; + @Mock RecruitLedgerMapper recruitMapper; + @Mock MaintainLedgerMapper maintainMapper; + + CalcOverrideStep step; + + static final String MONTH = "202506"; + static final Long LOWER = 10L, UPPER = 99L, ORG = 100L; + + @BeforeEach + void setUp() { + step = new CalcOverrideStep(ctx, ruleMapper, agentMapper, + organizationMapper, overrideMapper, recruitMapper, maintainMapper); + when(ctx.getSettleMonth()).thenReturn(MONTH); + + // 하위 설계사(등급5) 1명 / 상위(등급1) 1명 + AgentResp lower = new AgentResp(); + lower.setAgentId(LOWER); lower.setOrgId(ORG); lower.setGradeId(5L); + AgentResp upper = new AgentResp(); + upper.setAgentId(UPPER); upper.setOrgId(ORG); upper.setGradeId(1L); + when(agentMapper.selectList(any(AgentSearchParam.class))).thenAnswer(inv -> { + AgentSearchParam p = inv.getArgument(0); + return p.getOrgId() != null ? List.of(upper) : List.of(lower); // 상위탐색 vs 전체ACTIVE + }); + when(organizationMapper.selectAncestorIds(ORG)).thenReturn(List.of(ORG)); + } + + @Test + @DisplayName("insurance_type_cond='LIFE' 룰: base 는 LIFE 실적만 집계, 전체합 미사용") + void overrideBase_filteredByInsuranceType() { + OverrideRuleVO rule = new OverrideRuleVO(); + rule.setFromGrade(5L); + rule.setToGrade(1L); + rule.setOverridePct(new BigDecimal("4")); // 4% + rule.setInsuranceTypeCond("LIFE"); + when(ruleMapper.selectOverrideRules()).thenReturn(new java.util.ArrayList<>(List.of(rule))); + + // LIFE 실적만 1,000,000 (전체합은 호출되면 안 됨) + when(recruitMapper.sumByAgentMonthAndType(LOWER, MONTH, "LIFE")).thenReturn(new BigDecimal("1000000")); + when(maintainMapper.sumByAgentMonthAndType(LOWER, MONTH, "LIFE")).thenReturn(BigDecimal.ZERO); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + when(overrideMapper.insertBatch(captor.capture())).thenReturn(1); + + step.execute(null, null); + + List saved = captor.getValue(); + assertThat(saved).hasSize(1); + assertThat(saved.get(0).getBaseAmount()).isEqualByComparingTo("1000000"); // LIFE만 + assertThat(saved.get(0).getOverrideAmount()).isEqualByComparingTo("40000.00"); // ×4% + // 보험종류 한정 룰이면 전체합 집계는 호출되지 않아야 함 + verify(recruitMapper, never()).sumByAgentMonth(eq(LOWER), eq(MONTH)); + verify(maintainMapper, never()).sumByAgentMonth(eq(LOWER), eq(MONTH)); + } +} diff --git a/ga-core/src/main/java/com/ga/core/mapper/ledger/MaintainLedgerMapper.java b/ga-core/src/main/java/com/ga/core/mapper/ledger/MaintainLedgerMapper.java index f58d7ff..7967d3b 100644 --- a/ga-core/src/main/java/com/ga/core/mapper/ledger/MaintainLedgerMapper.java +++ b/ga-core/src/main/java/com/ga/core/mapper/ledger/MaintainLedgerMapper.java @@ -27,6 +27,11 @@ public interface MaintainLedgerMapper { BigDecimal sumByAgentMonth(@Param("agentId") Long agentId, @Param("settleMonth") String settleMonth); + /** 정산월 + 설계사 + 보험종류 합계 (오버라이드 insurance_type_cond 적용용) */ + BigDecimal sumByAgentMonthAndType(@Param("agentId") Long agentId, + @Param("settleMonth") String settleMonth, + @Param("insuranceType") String insuranceType); + /** 계약별 유지 원장 수수료 합산 (1200%룰 누적 검증용) */ BigDecimal sumByContract(@Param("contractId") Long contractId); } diff --git a/ga-core/src/main/java/com/ga/core/mapper/ledger/RecruitLedgerMapper.java b/ga-core/src/main/java/com/ga/core/mapper/ledger/RecruitLedgerMapper.java index 8fc6107..d67a87d 100644 --- a/ga-core/src/main/java/com/ga/core/mapper/ledger/RecruitLedgerMapper.java +++ b/ga-core/src/main/java/com/ga/core/mapper/ledger/RecruitLedgerMapper.java @@ -31,6 +31,11 @@ public interface RecruitLedgerMapper { BigDecimal sumByAgentMonth(@Param("agentId") Long agentId, @Param("settleMonth") String settleMonth); + /** 정산월 + 설계사 + 보험종류 합계 (오버라이드 insurance_type_cond 적용용) */ + BigDecimal sumByAgentMonthAndType(@Param("agentId") Long agentId, + @Param("settleMonth") String settleMonth, + @Param("insuranceType") String insuranceType); + /** 계약별 신계약 원장 수수료 합산 (1200%룰 누적 검증용) */ BigDecimal sumByContract(@Param("contractId") Long contractId); diff --git a/ga-core/src/main/resources/mapper/ledger/MaintainLedgerMapper.xml b/ga-core/src/main/resources/mapper/ledger/MaintainLedgerMapper.xml index e003e00..28c150c 100644 --- a/ga-core/src/main/resources/mapper/ledger/MaintainLedgerMapper.xml +++ b/ga-core/src/main/resources/mapper/ledger/MaintainLedgerMapper.xml @@ -112,6 +112,12 @@ WHERE agent_id = #{agentId} AND settle_month = #{settleMonth} + + + +