feat: incar 갭 반영 수수료/운영 14개 도메인 풀스택 + 배치통합 + 신입교육자료

incar CMS(116메뉴) 벤치마킹 갭분석으로 ga-pro에 없던 14개 도메인을 풀스택 추가.

P7 수수료 계산 7종 (V88~V96):
- 수입수수료+수지차, 소개·이관수수료, 정착지원금, 인정실적+환산율,
  시상, 생보운영지원수수료, 지점장수당
- 각 rule/ledger + 공식(보험료×요율, 수입−지급 등) Service 내장

P8 운영/정산 7종 (V97~V105):
- 단계마감, 본사정산, 등급평가, 공동계약, 보증보험, 적립금, 수수료시뮬레이터
- close/reopen/apply/simulate/upsert 등 액션 + 손익·잔액·등급 산정 로직

배치 통합 (V106):
- settle_master.other_commission_total 컬럼 추가(DEFAULT 0, 백필)
- 설계사 지급성 9종 원장 aggregateByAgent → AggregateStep gross 합산
- 빈 원장 0 → 기존 정산결과·무결성 불변(비파괴적)

테스트: P7/P8 Service 공식 단위테스트 21건(ga-api:test 57건 전체 통과)
프론트: 화면 14개 + React.lazy 코드 스플리팅(단일 4MB → 130+ 청크)
문서: DOMAIN_GAP_P7/P8.md, 신입교육_보험과수수료_완전기초.md, DOMAIN_KNOWLEDGE/HANDOFF 갱신

검증: 전체 ./gradlew build(test 포함) SUCCESSFUL, Flyway V88~V106 success,
GET+액션POST smoke 5xx 0건, 9개 집계쿼리 SQL 유효성 확인, 공식 런타임 실측.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
GA Pro
2026-05-31 15:45:10 +09:00
parent 70620463b4
commit bfc724669b
231 changed files with 13325 additions and 189 deletions
@@ -4,6 +4,15 @@ import com.ga.batch.settlement.SettlementContext;
import com.ga.batch.settlement.domain.BatchTaxCalculator;
import com.ga.batch.settlement.domain.TaxBreakdown;
import com.ga.common.util.MoneyUtil;
import com.ga.core.mapper.commission.AwardLedgerMapper;
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
import com.ga.core.mapper.commission.CollectionCommissionLedgerMapper;
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
import com.ga.core.mapper.commission.PersistencyBonusMapper;
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
import com.ga.core.mapper.commission.ReinstatementCommissionLedgerMapper;
import com.ga.core.mapper.commission.RenewalCommissionLedgerMapper;
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
import com.ga.core.mapper.ledger.ExceptionLedgerMapper;
import com.ga.core.mapper.ledger.MaintainLedgerMapper;
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
@@ -68,6 +77,17 @@ public class AggregateStep implements Tasklet {
private final CommissionStatementMapper statementMapper;
private final BatchTaxCalculator taxCalculator;
// 설계사 지급성 9종 원장 Mapper
private final CollectionCommissionLedgerMapper collectionMapper;
private final RenewalCommissionLedgerMapper renewalMapper;
private final ReinstatementCommissionLedgerMapper reinstatementMapper;
private final PersistencyBonusMapper persistencyBonusMapper;
private final ReferralCommissionLedgerMapper referralMapper;
private final SettlingSupportLedgerMapper settlingSupportMapper;
private final AwardLedgerMapper awardMapper;
private final LifeSupportLedgerMapper lifeSupportMapper;
private final BranchMgrAllowanceLedgerMapper branchMgrAllowanceMapper;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
log.info("[Step8 aggregate] settleMonth={}", ctx.getSettleMonth());
@@ -86,6 +106,17 @@ public class AggregateStep implements Tasklet {
excMinus.put(agentId, toBd(row.get("minus")));
}
// 설계사 지급성 9종 집계
Map<Long, BigDecimal> collectionMap = toMap(collectionMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> renewalMap = toMap(renewalMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> reinstatementMap = toMap(reinstatementMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> persistencyMap = toMap(persistencyBonusMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> referralMap = toMap(referralMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> settlingSupportMap = toMap(settlingSupportMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> awardMap = toMap(awardMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> lifeSupportMap = toMap(lifeSupportMapper.aggregateByAgent(ctx.getSettleMonth()));
Map<Long, BigDecimal> branchMgrMap = toMap(branchMgrAllowanceMapper.aggregateByAgent(ctx.getSettleMonth()));
// 합집합 agent_id
Set<Long> agentIds = new HashSet<>();
agentIds.addAll(recruitMap.keySet());
@@ -94,6 +125,15 @@ public class AggregateStep implements Tasklet {
agentIds.addAll(overrideMap.keySet());
agentIds.addAll(excPlus.keySet());
agentIds.addAll(excMinus.keySet());
agentIds.addAll(collectionMap.keySet());
agentIds.addAll(renewalMap.keySet());
agentIds.addAll(reinstatementMap.keySet());
agentIds.addAll(persistencyMap.keySet());
agentIds.addAll(referralMap.keySet());
agentIds.addAll(settlingSupportMap.keySet());
agentIds.addAll(awardMap.keySet());
agentIds.addAll(lifeSupportMap.keySet());
agentIds.addAll(branchMgrMap.keySet());
// 활성 설계사 누락 보충
AgentSearchParam ap = new AgentSearchParam();
@@ -122,8 +162,21 @@ public class AggregateStep implements Tasklet {
BigDecimal ep = excPlus.getOrDefault(agentId, BigDecimal.ZERO);
BigDecimal em = excMinus.getOrDefault(agentId, BigDecimal.ZERO);
// 설계사 지급성 9종 합산 (원장 없으면 ZERO → gross 불변 보장)
BigDecimal otherCommission = BigDecimal.ZERO;
otherCommission = MoneyUtil.add(otherCommission, collectionMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, renewalMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, reinstatementMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, persistencyMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, referralMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, settlingSupportMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, awardMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, lifeSupportMap.getOrDefault(agentId, BigDecimal.ZERO));
otherCommission = MoneyUtil.add(otherCommission, branchMgrMap.getOrDefault(agentId, BigDecimal.ZERO));
BigDecimal gross = MoneyUtil.add(MoneyUtil.add(MoneyUtil.add(recruit, maintain), ov), ep)
.subtract(cb).subtract(em);
gross = MoneyUtil.add(gross, otherCommission);
// 세금유형 조회 (없으면 기본: BUSINESS_INCOME / NONE)
AgentVO agentVO = agentMapper.selectById(agentId);
@@ -152,6 +205,7 @@ public class AggregateStep implements Tasklet {
vo.setExceptionMinus(em);
vo.setOverrideTotal(ov);
vo.setChargebackTotal(cb);
vo.setOtherCommissionTotal(otherCommission);
vo.setGrossAmount(gross);
vo.setTaxAmount(totalTax); // incomeTax + localTax 합산
vo.setNetAmount(netAmount); // gross - incomeTax - localTax + vat - deduction