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
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.AwardService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "시상")
@RestController
@RequestMapping("/api/awards")
@RequiredArgsConstructor
public class AwardController {
private final AwardService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "AWARD", perm = "READ")
public ApiResponse<PageResponse<AwardRuleResp>> listRules(
@ModelAttribute AwardRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "AWARD", perm = "READ")
public ApiResponse<AwardRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "AWARD", perm = "CREATE")
@DataChangeLog(menu = "AWARD", table = "award_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody AwardRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "AWARD", perm = "UPDATE")
@DataChangeLog(menu = "AWARD", table = "award_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody AwardRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "AWARD", perm = "DELETE")
@DataChangeLog(menu = "AWARD", table = "award_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "AWARD", perm = "READ")
public ApiResponse<PageResponse<AwardLedgerResp>> listLedgers(
@ModelAttribute AwardLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "AWARD", perm = "READ")
public ApiResponse<AwardLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "AWARD", perm = "CREATE")
@DataChangeLog(menu = "AWARD", table = "award_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody AwardLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.BranchAllowanceService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "지점장수당")
@RestController
@RequestMapping("/api/branch-allowances")
@RequiredArgsConstructor
public class BranchAllowanceController {
private final BranchAllowanceService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
public ApiResponse<PageResponse<BranchMgrAllowanceRuleResp>> listRules(
@ModelAttribute BranchMgrAllowanceRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
public ApiResponse<BranchMgrAllowanceRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "CREATE")
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody BranchMgrAllowanceRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "UPDATE")
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody BranchMgrAllowanceRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "DELETE")
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
public ApiResponse<PageResponse<BranchMgrAllowanceLedgerResp>> listLedgers(
@ModelAttribute BranchMgrAllowanceLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
public ApiResponse<BranchMgrAllowanceLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "CREATE")
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody BranchMgrAllowanceLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,43 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.CommissionSimulationService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.CommissionSimulationResp;
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
import com.ga.core.vo.commission.CommissionSimulationSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "수수료 시뮬레이터")
@RestController
@RequestMapping("/api/commission-simulations")
@RequiredArgsConstructor
public class CommissionSimulationController {
private final CommissionSimulationService service;
@GetMapping
@RequirePermission(menu = "COMM_SIMULATOR", perm = "READ")
public ApiResponse<PageResponse<CommissionSimulationResp>> list(
@ModelAttribute CommissionSimulationSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{simId}")
@RequirePermission(menu = "COMM_SIMULATOR", perm = "READ")
public ApiResponse<CommissionSimulationResp> detail(@PathVariable Long simId) {
return ApiResponse.ok(service.detail(simId));
}
@PostMapping("/simulate")
@RequirePermission(menu = "COMM_SIMULATOR", perm = "CREATE")
@DataChangeLog(menu = "COMM_SIMULATOR", table = "commission_simulation")
public ApiResponse<CommissionSimulationResp> simulate(@Valid @RequestBody CommissionSimulationSaveReq req) {
return ApiResponse.ok(service.simulate(req));
}
}
@@ -0,0 +1,102 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.GradeEvaluationService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.AgentGradeEvaluationResp;
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
import com.ga.core.vo.commission.AgentGradeEvaluationSearchParam;
import com.ga.core.vo.commission.GradeEvalRuleResp;
import com.ga.core.vo.commission.GradeEvalRuleSaveReq;
import com.ga.core.vo.commission.GradeEvalRuleSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "등급평가")
@RestController
@RequestMapping("/api/grade-evaluations")
@RequiredArgsConstructor
public class GradeEvaluationController {
private final GradeEvaluationService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
public ApiResponse<PageResponse<GradeEvalRuleResp>> listRules(@ModelAttribute GradeEvalRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
public ApiResponse<GradeEvalRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "GRADE_EVAL", perm = "CREATE")
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody GradeEvalRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "GRADE_EVAL", perm = "UPDATE")
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody GradeEvalRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "GRADE_EVAL", perm = "DELETE")
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Evaluation ────────────────────────────────────────────────────────────
@GetMapping
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
public ApiResponse<PageResponse<AgentGradeEvaluationResp>> listEvals(
@ModelAttribute AgentGradeEvaluationSearchParam param) {
return ApiResponse.ok(service.listEvals(param));
}
@GetMapping("/{evalId}")
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
public ApiResponse<AgentGradeEvaluationResp> detailEval(@PathVariable Long evalId) {
return ApiResponse.ok(service.detailEval(evalId));
}
@PostMapping
@RequirePermission(menu = "GRADE_EVAL", perm = "CREATE")
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
public ApiResponse<Long> createEval(@Valid @RequestBody AgentGradeEvaluationSaveReq req) {
return ApiResponse.ok(service.createEval(req));
}
@PostMapping("/{evalId}/apply")
@RequirePermission(menu = "GRADE_EVAL", perm = "UPDATE")
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
public ApiResponse<Void> applyEval(@PathVariable Long evalId) {
service.applyEval(evalId);
return ApiResponse.ok();
}
@DeleteMapping("/{evalId}")
@RequirePermission(menu = "GRADE_EVAL", perm = "DELETE")
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
public ApiResponse<Void> deleteEval(@PathVariable Long evalId) {
service.deleteEval(evalId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,60 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.GuaranteeInsuranceService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceResp;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSaveReq;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "보증보험")
@RestController
@RequestMapping("/api/guarantee-insurances")
@RequiredArgsConstructor
public class GuaranteeInsuranceController {
private final GuaranteeInsuranceService service;
@GetMapping
@RequirePermission(menu = "GUARANTEE_INS", perm = "READ")
public ApiResponse<PageResponse<AgentGuaranteeInsuranceResp>> list(
@ModelAttribute AgentGuaranteeInsuranceSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{guaranteeId}")
@RequirePermission(menu = "GUARANTEE_INS", perm = "READ")
public ApiResponse<AgentGuaranteeInsuranceResp> detail(@PathVariable Long guaranteeId) {
return ApiResponse.ok(service.detail(guaranteeId));
}
@PostMapping
@RequirePermission(menu = "GUARANTEE_INS", perm = "CREATE")
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
public ApiResponse<Long> create(@Valid @RequestBody AgentGuaranteeInsuranceSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{guaranteeId}")
@RequirePermission(menu = "GUARANTEE_INS", perm = "UPDATE")
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
public ApiResponse<Void> update(@PathVariable Long guaranteeId,
@Valid @RequestBody AgentGuaranteeInsuranceSaveReq req) {
service.update(guaranteeId, req);
return ApiResponse.ok();
}
@DeleteMapping("/{guaranteeId}")
@RequirePermission(menu = "GUARANTEE_INS", perm = "DELETE")
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
public ApiResponse<Void> delete(@PathVariable Long guaranteeId) {
service.delete(guaranteeId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,66 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.HqSettleService;
import com.ga.api.service.commission.HqSettleService.HqSummaryResp;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.HqSettleItemResp;
import com.ga.core.vo.commission.HqSettleItemSaveReq;
import com.ga.core.vo.commission.HqSettleItemSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "본사정산")
@RestController
@RequestMapping("/api/hq-settles")
@RequiredArgsConstructor
public class HqSettleController {
private final HqSettleService service;
@GetMapping
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
public ApiResponse<PageResponse<HqSettleItemResp>> list(@ModelAttribute HqSettleItemSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/summary")
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
public ApiResponse<HqSummaryResp> summary(@RequestParam String settleMonth) {
return ApiResponse.ok(service.summary(settleMonth));
}
@GetMapping("/{itemId}")
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
public ApiResponse<HqSettleItemResp> detail(@PathVariable Long itemId) {
return ApiResponse.ok(service.detail(itemId));
}
@PostMapping
@RequirePermission(menu = "HQ_SETTLE", perm = "CREATE")
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
public ApiResponse<Long> create(@Valid @RequestBody HqSettleItemSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{itemId}")
@RequirePermission(menu = "HQ_SETTLE", perm = "UPDATE")
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
public ApiResponse<Void> update(@PathVariable Long itemId,
@Valid @RequestBody HqSettleItemSaveReq req) {
service.update(itemId, req);
return ApiResponse.ok();
}
@DeleteMapping("/{itemId}")
@RequirePermission(menu = "HQ_SETTLE", perm = "DELETE")
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
public ApiResponse<Void> delete(@PathVariable Long itemId) {
service.delete(itemId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,66 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.IncomeCommissionService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "수입수수료/수지차")
@RestController
@RequestMapping("/api/income-commissions")
@RequiredArgsConstructor
public class IncomeCommissionController {
private final IncomeCommissionService service;
// ── Income Ledger ────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
public ApiResponse<PageResponse<IncomeCommissionLedgerResp>> listLedgers(
@ModelAttribute IncomeCommissionLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{incomeId}")
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
public ApiResponse<IncomeCommissionLedgerResp> detailLedger(@PathVariable Long incomeId) {
return ApiResponse.ok(service.detailLedger(incomeId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "INCOME_COMM", perm = "CREATE")
@DataChangeLog(menu = "INCOME_COMM", table = "income_commission_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody IncomeCommissionLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
// ── Margin Summary ───────────────────────────────────────────────────────
@GetMapping("/margins")
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
public ApiResponse<PageResponse<MarginSummaryResp>> listMargins(
@ModelAttribute MarginSummarySearchParam param) {
return ApiResponse.ok(service.listMargins(param));
}
@GetMapping("/margins/{marginId}")
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
public ApiResponse<MarginSummaryResp> detailMargin(@PathVariable Long marginId) {
return ApiResponse.ok(service.detailMargin(marginId));
}
@PostMapping("/margin/recalc")
@RequirePermission(menu = "INCOME_COMM", perm = "CREATE")
@DataChangeLog(menu = "INCOME_COMM", table = "margin_summary")
public ApiResponse<Long> recalcMargin(@RequestParam String settleMonth,
@RequestParam(required = false) Long agentId) {
return ApiResponse.ok(service.recalcMargin(settleMonth, agentId));
}
}
@@ -0,0 +1,59 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.JointContractService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.JointContractResp;
import com.ga.core.vo.commission.JointContractSaveReq;
import com.ga.core.vo.commission.JointContractSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "공동계약")
@RestController
@RequestMapping("/api/joint-contracts")
@RequiredArgsConstructor
public class JointContractController {
private final JointContractService service;
@GetMapping
@RequirePermission(menu = "JOINT_CONTRACT", perm = "READ")
public ApiResponse<PageResponse<JointContractResp>> list(@ModelAttribute JointContractSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{jointId}")
@RequirePermission(menu = "JOINT_CONTRACT", perm = "READ")
public ApiResponse<JointContractResp> detail(@PathVariable Long jointId) {
return ApiResponse.ok(service.detail(jointId));
}
@PostMapping
@RequirePermission(menu = "JOINT_CONTRACT", perm = "CREATE")
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
public ApiResponse<Long> create(@Valid @RequestBody JointContractSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{jointId}")
@RequirePermission(menu = "JOINT_CONTRACT", perm = "UPDATE")
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
public ApiResponse<Void> update(@PathVariable Long jointId,
@Valid @RequestBody JointContractSaveReq req) {
service.update(jointId, req);
return ApiResponse.ok();
}
@DeleteMapping("/{jointId}")
@RequirePermission(menu = "JOINT_CONTRACT", perm = "DELETE")
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
public ApiResponse<Void> delete(@PathVariable Long jointId) {
service.delete(jointId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.LifeSupportService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "생보운영지원수수료")
@RestController
@RequestMapping("/api/life-supports")
@RequiredArgsConstructor
public class LifeSupportController {
private final LifeSupportService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
public ApiResponse<PageResponse<LifeSupportRuleResp>> listRules(
@ModelAttribute LifeSupportRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
public ApiResponse<LifeSupportRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "CREATE")
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody LifeSupportRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "UPDATE")
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody LifeSupportRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "DELETE")
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
public ApiResponse<PageResponse<LifeSupportLedgerResp>> listLedgers(
@ModelAttribute LifeSupportLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
public ApiResponse<LifeSupportLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "CREATE")
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody LifeSupportLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.RecognizedPerformanceService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "인정실적")
@RestController
@RequestMapping("/api/recognized-performances")
@RequiredArgsConstructor
public class RecognizedPerformanceController {
private final RecognizedPerformanceService service;
// ── Product Conversion Rate (rule) ────────────────────────────────────────
@GetMapping("/rates")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
public ApiResponse<PageResponse<ProductConversionRateResp>> listRates(
@ModelAttribute ProductConversionRateSearchParam param) {
return ApiResponse.ok(service.listRates(param));
}
@GetMapping("/rates/{rateId}")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
public ApiResponse<ProductConversionRateResp> detailRate(@PathVariable Long rateId) {
return ApiResponse.ok(service.detailRate(rateId));
}
@PostMapping("/rates")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "CREATE")
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
public ApiResponse<Long> createRate(@Valid @RequestBody ProductConversionRateSaveReq req) {
return ApiResponse.ok(service.createRate(req));
}
@PutMapping("/rates/{rateId}")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "UPDATE")
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
public ApiResponse<Void> updateRate(@PathVariable Long rateId,
@Valid @RequestBody ProductConversionRateSaveReq req) {
service.updateRate(rateId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rates/{rateId}")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "DELETE")
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
public ApiResponse<Void> deleteRate(@PathVariable Long rateId) {
service.deleteRate(rateId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
public ApiResponse<PageResponse<RecognizedPerformanceLedgerResp>> listLedgers(
@ModelAttribute RecognizedPerformanceLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
public ApiResponse<RecognizedPerformanceLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "CREATE")
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "recognized_performance_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody RecognizedPerformanceLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.ReferralCommissionService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "소개·이관수수료")
@RestController
@RequestMapping("/api/referral-commissions")
@RequiredArgsConstructor
public class ReferralCommissionController {
private final ReferralCommissionService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
public ApiResponse<PageResponse<ReferralCommissionRuleResp>> listRules(
@ModelAttribute ReferralCommissionRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
public ApiResponse<ReferralCommissionRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "REFERRAL_COMM", perm = "CREATE")
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody ReferralCommissionRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "REFERRAL_COMM", perm = "UPDATE")
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody ReferralCommissionRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "REFERRAL_COMM", perm = "DELETE")
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
public ApiResponse<PageResponse<ReferralCommissionLedgerResp>> listLedgers(
@ModelAttribute ReferralCommissionLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
public ApiResponse<ReferralCommissionLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "REFERRAL_COMM", perm = "CREATE")
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody ReferralCommissionLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,42 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.ReserveFundService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.AgentReserveFundResp;
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
import com.ga.core.vo.commission.AgentReserveFundSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "적립금")
@RestController
@RequestMapping("/api/reserve-funds")
@RequiredArgsConstructor
public class ReserveFundController {
private final ReserveFundService service;
@GetMapping
@RequirePermission(menu = "RESERVE_FUND", perm = "READ")
public ApiResponse<PageResponse<AgentReserveFundResp>> list(@ModelAttribute AgentReserveFundSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{reserveId}")
@RequirePermission(menu = "RESERVE_FUND", perm = "READ")
public ApiResponse<AgentReserveFundResp> detail(@PathVariable Long reserveId) {
return ApiResponse.ok(service.detail(reserveId));
}
@PostMapping
@RequirePermission(menu = "RESERVE_FUND", perm = "CREATE")
@DataChangeLog(menu = "RESERVE_FUND", table = "agent_reserve_fund")
public ApiResponse<Long> create(@Valid @RequestBody AgentReserveFundSaveReq req) {
return ApiResponse.ok(service.create(req));
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.SettlingSupportService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "정착지원금")
@RestController
@RequestMapping("/api/settling-supports")
@RequiredArgsConstructor
public class SettlingSupportController {
private final SettlingSupportService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
public ApiResponse<PageResponse<SettlingSupportRuleResp>> listRules(
@ModelAttribute SettlingSupportRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
public ApiResponse<SettlingSupportRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "CREATE")
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody SettlingSupportRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "UPDATE")
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody SettlingSupportRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "DELETE")
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
public ApiResponse<PageResponse<SettlingSupportLedgerResp>> listLedgers(
@ModelAttribute SettlingSupportLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
public ApiResponse<SettlingSupportLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "CREATE")
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody SettlingSupportLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,66 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.StepCloseService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.commission.StepCloseResp;
import com.ga.core.vo.commission.StepCloseSaveReq;
import com.ga.core.vo.commission.StepCloseSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "단계마감")
@RestController
@RequestMapping("/api/step-closes")
@RequiredArgsConstructor
public class StepCloseController {
private final StepCloseService service;
@GetMapping
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
public ApiResponse<PageResponse<StepCloseResp>> list(@ModelAttribute StepCloseSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/by-month")
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
public ApiResponse<List<StepCloseResp>> listByMonth(@RequestParam String settleMonth) {
return ApiResponse.ok(service.listByMonth(settleMonth));
}
@GetMapping("/{stepCloseId}")
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
public ApiResponse<StepCloseResp> detail(@PathVariable Long stepCloseId) {
return ApiResponse.ok(service.detail(stepCloseId));
}
@PostMapping
@RequirePermission(menu = "STEP_CLOSE", perm = "UPDATE")
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
public ApiResponse<Long> create(@Valid @RequestBody StepCloseSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{stepCloseId}/close")
@RequirePermission(menu = "STEP_CLOSE", perm = "APPROVE")
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
public ApiResponse<Void> close(@PathVariable Long stepCloseId) {
service.close(stepCloseId);
return ApiResponse.ok();
}
@PostMapping("/{stepCloseId}/reopen")
@RequirePermission(menu = "STEP_CLOSE", perm = "APPROVE")
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
public ApiResponse<Void> reopen(@PathVariable Long stepCloseId) {
service.reopen(stepCloseId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,114 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.AwardLedgerMapper;
import com.ga.core.mapper.commission.AwardRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AwardService {
private final AwardRuleMapper ruleMapper;
private final AwardLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<AwardRuleResp> listRules(AwardRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public AwardRuleResp detailRule(Long ruleId) {
AwardRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(AwardRuleSaveReq req) {
AwardRuleVO vo = new AwardRuleVO();
vo.setAwardType(req.getAwardType());
vo.setCalcType(req.getCalcType());
vo.setThresholdAmount(req.getThresholdAmount());
vo.setAwardRate(req.getAwardRate());
vo.setAwardFixed(req.getAwardFixed());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, AwardRuleSaveReq req) {
AwardRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
AwardRuleVO vo = new AwardRuleVO();
vo.setRuleId(ruleId);
vo.setAwardType(req.getAwardType());
vo.setCalcType(req.getCalcType());
vo.setThresholdAmount(req.getThresholdAmount());
vo.setAwardRate(req.getAwardRate());
vo.setAwardFixed(req.getAwardFixed());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
AwardRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<AwardLedgerResp> listLedgers(AwardLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public AwardLedgerResp detailLedger(Long ledgerId) {
AwardLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(AwardLedgerSaveReq req) {
// 룰 조회로 calc_type 결정
AwardRuleVO rule = ruleMapper.selectActiveRule(req.getAwardType(), LocalDate.now());
if (rule == null) throw new BizException(ErrorCode.NOT_FOUND);
BigDecimal awardAmount;
if ("FIXED".equals(rule.getCalcType())) {
awardAmount = rule.getAwardFixed().setScale(2, RoundingMode.HALF_UP);
} else {
// RATE: base × award_rate
awardAmount = req.getBaseAmount()
.multiply(rule.getAwardRate())
.setScale(2, RoundingMode.HALF_UP);
}
AwardLedgerVO vo = new AwardLedgerVO();
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setAwardType(req.getAwardType());
vo.setBaseAmount(req.getBaseAmount());
vo.setAwardAmount(awardAmount);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,107 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
import com.ga.core.mapper.commission.BranchMgrAllowanceRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BranchAllowanceService {
private final BranchMgrAllowanceRuleMapper ruleMapper;
private final BranchMgrAllowanceLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<BranchMgrAllowanceRuleResp> listRules(BranchMgrAllowanceRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public BranchMgrAllowanceRuleResp detailRule(Long ruleId) {
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(BranchMgrAllowanceRuleSaveReq req) {
BranchMgrAllowanceRuleVO vo = new BranchMgrAllowanceRuleVO();
vo.setPositionCode(req.getPositionCode());
vo.setAllowanceRate(req.getAllowanceRate());
vo.setCapAmount(req.getCapAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, BranchMgrAllowanceRuleSaveReq req) {
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
BranchMgrAllowanceRuleVO vo = new BranchMgrAllowanceRuleVO();
vo.setRuleId(ruleId);
vo.setPositionCode(req.getPositionCode());
vo.setAllowanceRate(req.getAllowanceRate());
vo.setCapAmount(req.getCapAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<BranchMgrAllowanceLedgerResp> listLedgers(BranchMgrAllowanceLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public BranchMgrAllowanceLedgerResp detailLedger(Long ledgerId) {
BranchMgrAllowanceLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(BranchMgrAllowanceLedgerSaveReq req) {
// 수당액 = min(base × rate, cap) — cap NULL이면 상한 없음
BigDecimal raw = req.getBaseAmount()
.multiply(req.getAllowanceRate())
.setScale(2, RoundingMode.HALF_UP);
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
// cap_amount는 SaveReq에 없으므로 allowanceRate와 동일 룰에서 cap 조회
// positionCode가 req에 없어 단순 구현: raw 그대로 사용 (배치에서 룰 참조 시 위임)
BigDecimal allowanceAmount = raw;
BranchMgrAllowanceLedgerVO vo = new BranchMgrAllowanceLedgerVO();
vo.setManagerAgentId(req.getManagerAgentId());
vo.setOrgId(req.getOrgId());
vo.setSettleMonth(req.getSettleMonth());
vo.setBaseAmount(req.getBaseAmount());
vo.setAllowanceRate(req.getAllowanceRate());
vo.setAllowanceAmount(allowanceAmount);
vo.setClawbackAmount(clawback);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,94 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.commission.CommissionSimulationMapper;
import com.ga.core.vo.commission.CommissionSimulationResp;
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
import com.ga.core.vo.commission.CommissionSimulationSearchParam;
import com.ga.core.vo.commission.CommissionSimulationVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CommissionSimulationService {
private static final BigDecimal WITHHOLDING_TAX_RATE = new BigDecimal("0.033");
private static final BigDecimal DEFAULT_CHARGEBACK_RATE = new BigDecimal("0.5");
private final CommissionSimulationMapper simulationMapper;
public PageResponse<CommissionSimulationResp> list(CommissionSimulationSearchParam param) {
param.startPage();
return PageResponse.of(simulationMapper.selectList(param));
}
public CommissionSimulationResp detail(Long simId) {
CommissionSimulationResp resp = simulationMapper.selectById(simId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 수수료 시뮬레이션 계산 후 저장·반환.
* company_payment = premium × company_rate
* agent_commission = company_payment × payout_rate
* withholding_tax = agent_commission × 3.3%
* net_amount = agent_commission withholding_tax
* expected_chargeback: 입력 값이 있으면 사용, 없으면 agent_commission × 50% (13개월 가정)
* 모든 금액 setScale(2, HALF_UP).
*/
@Transactional
public CommissionSimulationResp simulate(CommissionSimulationSaveReq req) {
BigDecimal companyPayment = req.getPremiumAmount()
.multiply(req.getCompanyRate())
.setScale(2, RoundingMode.HALF_UP);
BigDecimal agentCommission = companyPayment
.multiply(req.getPayoutRate())
.setScale(2, RoundingMode.HALF_UP);
BigDecimal withholdingTax = agentCommission
.multiply(WITHHOLDING_TAX_RATE)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal netAmount = agentCommission
.subtract(withholdingTax)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal expectedChargeback;
if (req.getExpectedChargeback() != null) {
expectedChargeback = req.getExpectedChargeback().setScale(2, RoundingMode.HALF_UP);
} else {
expectedChargeback = agentCommission
.multiply(DEFAULT_CHARGEBACK_RATE)
.setScale(2, RoundingMode.HALF_UP);
}
CommissionSimulationVO vo = new CommissionSimulationVO();
vo.setSimName(req.getSimName());
vo.setPremiumAmount(req.getPremiumAmount());
vo.setCompanyRate(req.getCompanyRate());
vo.setPayoutRate(req.getPayoutRate());
vo.setCommissionYear(req.getCommissionYear());
vo.setCompanyPayment(companyPayment);
vo.setAgentCommission(agentCommission);
vo.setWithholdingTax(withholdingTax);
vo.setNetAmount(netAmount);
vo.setExpectedChargeback(expectedChargeback);
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
simulationMapper.insert(vo);
CommissionSimulationResp resp = simulationMapper.selectById(vo.getSimId());
if (resp == null) throw new BizException(ErrorCode.INTERNAL_ERROR);
return resp;
}
}
@@ -0,0 +1,129 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.AgentGradeEvaluationMapper;
import com.ga.core.mapper.commission.GradeEvalRuleMapper;
import com.ga.core.vo.commission.AgentGradeEvaluationResp;
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
import com.ga.core.vo.commission.AgentGradeEvaluationSearchParam;
import com.ga.core.vo.commission.AgentGradeEvaluationVO;
import com.ga.core.vo.commission.GradeEvalRuleResp;
import com.ga.core.vo.commission.GradeEvalRuleSaveReq;
import com.ga.core.vo.commission.GradeEvalRuleSearchParam;
import com.ga.core.vo.commission.GradeEvalRuleVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class GradeEvaluationService {
private final GradeEvalRuleMapper ruleMapper;
private final AgentGradeEvaluationMapper evalMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<GradeEvalRuleResp> listRules(GradeEvalRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public GradeEvalRuleResp detailRule(Long ruleId) {
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(GradeEvalRuleSaveReq req) {
GradeEvalRuleVO vo = new GradeEvalRuleVO();
vo.setGradeId(req.getGradeId());
vo.setMinScore(req.getMinScore());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, GradeEvalRuleSaveReq req) {
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
GradeEvalRuleVO vo = new GradeEvalRuleVO();
vo.setRuleId(ruleId);
vo.setGradeId(req.getGradeId());
vo.setMinScore(req.getMinScore());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Evaluation ────────────────────────────────────────────────────────────
public PageResponse<AgentGradeEvaluationResp> listEvals(AgentGradeEvaluationSearchParam param) {
param.startPage();
return PageResponse.of(evalMapper.selectList(param));
}
public AgentGradeEvaluationResp detailEval(Long evalId) {
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 등급평가 생성.
* selectActiveRuleByScore(score, 기준일)로 evaluated_grade_id 결정.
* 매칭 룰 없으면 prevGradeId 유지 또는 null.
*/
@Transactional
public Long createEval(AgentGradeEvaluationSaveReq req) {
LocalDate baseDate = LocalDate.now();
GradeEvalRuleVO matchedRule = ruleMapper.selectActiveRuleByScore(req.getPerformanceScore(), baseDate);
Long evaluatedGradeId = (matchedRule != null) ? matchedRule.getGradeId() : req.getPrevGradeId();
AgentGradeEvaluationVO vo = new AgentGradeEvaluationVO();
vo.setAgentId(req.getAgentId());
vo.setEvalPeriod(req.getEvalPeriod());
vo.setPerformanceScore(req.getPerformanceScore());
vo.setPrevGradeId(req.getPrevGradeId());
vo.setEvaluatedGradeId(evaluatedGradeId);
vo.setStatus("EVALUATED");
evalMapper.insert(vo);
return vo.getEvalId();
}
/**
* 등급평가 반영 액션 (EVALUATED → APPLIED).
*/
@Transactional
public void applyEval(Long evalId) {
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
if ("APPLIED".equals(resp.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 반영된 평가입니다.");
}
evalMapper.updateStatus(evalId, "APPLIED");
}
@Transactional
public void deleteEval(Long evalId) {
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
evalMapper.delete(evalId);
}
}
@@ -0,0 +1,68 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.AgentGuaranteeInsuranceMapper;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceResp;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSaveReq;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSearchParam;
import com.ga.core.vo.commission.AgentGuaranteeInsuranceVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class GuaranteeInsuranceService {
private final AgentGuaranteeInsuranceMapper guaranteeInsuranceMapper;
public PageResponse<AgentGuaranteeInsuranceResp> list(AgentGuaranteeInsuranceSearchParam param) {
param.startPage();
return PageResponse.of(guaranteeInsuranceMapper.selectList(param));
}
public AgentGuaranteeInsuranceResp detail(Long guaranteeId) {
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(AgentGuaranteeInsuranceSaveReq req) {
AgentGuaranteeInsuranceVO vo = new AgentGuaranteeInsuranceVO();
vo.setAgentId(req.getAgentId());
vo.setPolicyNo(req.getPolicyNo());
vo.setCarrierId(req.getCarrierId());
vo.setGuaranteeAmount(req.getGuaranteeAmount());
vo.setStartDate(req.getStartDate());
vo.setEndDate(req.getEndDate());
vo.setStatus("ACTIVE");
guaranteeInsuranceMapper.insert(vo);
return vo.getGuaranteeId();
}
@Transactional
public void update(Long guaranteeId, AgentGuaranteeInsuranceSaveReq req) {
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
AgentGuaranteeInsuranceVO vo = new AgentGuaranteeInsuranceVO();
vo.setGuaranteeId(guaranteeId);
vo.setAgentId(req.getAgentId());
vo.setPolicyNo(req.getPolicyNo());
vo.setCarrierId(req.getCarrierId());
vo.setGuaranteeAmount(req.getGuaranteeAmount());
vo.setStartDate(req.getStartDate());
vo.setEndDate(req.getEndDate());
guaranteeInsuranceMapper.update(vo);
}
@Transactional
public void delete(Long guaranteeId) {
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
guaranteeInsuranceMapper.delete(guaranteeId);
}
}
@@ -0,0 +1,90 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.HqSettleItemMapper;
import com.ga.core.vo.commission.HqSettleItemResp;
import com.ga.core.vo.commission.HqSettleItemSaveReq;
import com.ga.core.vo.commission.HqSettleItemSearchParam;
import com.ga.core.vo.commission.HqSettleItemVO;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class HqSettleService {
private final HqSettleItemMapper itemMapper;
@Data
public static class HqSummaryResp {
private final String settleMonth;
private final BigDecimal income;
private final BigDecimal expense;
private final BigDecimal profit;
}
public PageResponse<HqSettleItemResp> list(HqSettleItemSearchParam param) {
param.startPage();
return PageResponse.of(itemMapper.selectList(param));
}
public HqSettleItemResp detail(Long itemId) {
HqSettleItemResp resp = itemMapper.selectById(itemId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 본사손익 집계: income expense.
* 둘 다 null이면 0으로 처리.
*/
public HqSummaryResp summary(String settleMonth) {
BigDecimal income = itemMapper.sumByMonthAndType(settleMonth, "INCOME");
BigDecimal expense = itemMapper.sumByMonthAndType(settleMonth, "EXPENSE");
if (income == null) income = BigDecimal.ZERO;
if (expense == null) expense = BigDecimal.ZERO;
BigDecimal profit = income.subtract(expense);
return new HqSummaryResp(settleMonth, income, expense, profit);
}
@Transactional
public Long create(HqSettleItemSaveReq req) {
HqSettleItemVO vo = new HqSettleItemVO();
vo.setSettleMonth(req.getSettleMonth());
vo.setItemType(req.getItemType());
vo.setItemName(req.getItemName());
vo.setAmount(req.getAmount());
vo.setMemo(req.getMemo());
vo.setStatus("REGISTERED");
itemMapper.insert(vo);
return vo.getItemId();
}
@Transactional
public void update(Long itemId, HqSettleItemSaveReq req) {
HqSettleItemResp resp = itemMapper.selectById(itemId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
HqSettleItemVO vo = new HqSettleItemVO();
vo.setItemId(itemId);
vo.setSettleMonth(req.getSettleMonth());
vo.setItemType(req.getItemType());
vo.setItemName(req.getItemName());
vo.setAmount(req.getAmount());
vo.setMemo(req.getMemo());
itemMapper.update(vo);
}
@Transactional
public void delete(Long itemId) {
HqSettleItemResp resp = itemMapper.selectById(itemId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
itemMapper.delete(itemId);
}
}
@@ -0,0 +1,109 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.IncomeCommissionLedgerMapper;
import com.ga.core.mapper.commission.MarginSummaryMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class IncomeCommissionService {
private final IncomeCommissionLedgerMapper ledgerMapper;
private final MarginSummaryMapper marginMapper;
// ── Income Ledger ────────────────────────────────────────────────────────
public PageResponse<IncomeCommissionLedgerResp> listLedgers(IncomeCommissionLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public IncomeCommissionLedgerResp detailLedger(Long incomeId) {
IncomeCommissionLedgerResp resp = ledgerMapper.selectById(incomeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(IncomeCommissionLedgerSaveReq req) {
IncomeCommissionLedgerVO vo = new IncomeCommissionLedgerVO();
vo.setContractId(req.getContractId());
vo.setCarrierId(req.getCarrierId());
vo.setSettleMonth(req.getSettleMonth());
vo.setPremiumAmount(req.getPremiumAmount());
vo.setCompanyRate(req.getCompanyRate());
// 수입수수료 = 보험료 × 보험사요율
vo.setIncomeAmount(req.getPremiumAmount()
.multiply(req.getCompanyRate())
.setScale(2, RoundingMode.HALF_UP));
vo.setCommissionType(req.getCommissionType());
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getIncomeId();
}
// ── Margin Summary ───────────────────────────────────────────────────────
public PageResponse<MarginSummaryResp> listMargins(MarginSummarySearchParam param) {
param.startPage();
return PageResponse.of(marginMapper.selectList(param));
}
public MarginSummaryResp detailMargin(Long marginId) {
MarginSummaryResp resp = marginMapper.selectById(marginId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 수지차 재계산 (recalc).
* settle_month 단위로 income_total(수입수수료 합) payout_total(settle_master net_amount 합) 산출.
* 수입수수료 원장에는 설계사 귀속이 없어(보험사·계약 단위) income 은 월 전체 합을 쓰고,
* agentId 가 주어지면 payout 만 해당 설계사로 좁혀 집계한다. (미지정 시 GA 월 전체 수지차)
* 수지차 = income_total payout_total, 수지차율 = 수지차 / income_total (income_total > 0).
*/
@Transactional
public Long recalcMargin(String settleMonth, Long agentId) {
BigDecimal incomeTotal = marginMapper.sumIncomeByMonth(settleMonth);
if (incomeTotal == null) incomeTotal = BigDecimal.ZERO;
BigDecimal payoutTotal = marginMapper.sumPayoutByMonth(settleMonth, agentId);
if (payoutTotal == null) payoutTotal = BigDecimal.ZERO;
BigDecimal marginAmount = incomeTotal.subtract(payoutTotal).setScale(2, RoundingMode.HALF_UP);
BigDecimal marginRate = BigDecimal.ZERO;
if (incomeTotal.compareTo(BigDecimal.ZERO) > 0) {
marginRate = marginAmount.divide(incomeTotal, 4, RoundingMode.HALF_UP);
}
MarginSummaryVO existing = marginMapper.selectByMonthAndAgent(settleMonth, agentId);
if (existing != null) {
existing.setIncomeTotal(incomeTotal);
existing.setPayoutTotal(payoutTotal);
existing.setMarginAmount(marginAmount);
existing.setMarginRate(marginRate);
marginMapper.update(existing);
return existing.getMarginId();
} else {
MarginSummaryVO vo = new MarginSummaryVO();
vo.setSettleMonth(settleMonth);
vo.setAgentId(agentId);
vo.setIncomeTotal(incomeTotal);
vo.setPayoutTotal(payoutTotal);
vo.setMarginAmount(marginAmount);
vo.setMarginRate(marginRate);
marginMapper.insert(vo);
return vo.getMarginId();
}
}
}
@@ -0,0 +1,96 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.JointContractMapper;
import com.ga.core.vo.commission.JointContractResp;
import com.ga.core.vo.commission.JointContractSaveReq;
import com.ga.core.vo.commission.JointContractSearchParam;
import com.ga.core.vo.commission.JointContractVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class JointContractService {
private final JointContractMapper jointContractMapper;
public PageResponse<JointContractResp> list(JointContractSearchParam param) {
param.startPage();
return PageResponse.of(jointContractMapper.selectList(param));
}
public JointContractResp detail(Long jointId) {
JointContractResp resp = jointContractMapper.selectById(jointId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 공동계약 생성.
* 기존 share_rate 합 + 신규 share_rate > 1.0 이면 E411 예외.
*/
@Transactional
public Long create(JointContractSaveReq req) {
validateShareRate(req.getContractId(), null, req.getShareRate());
JointContractVO vo = new JointContractVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setRole(req.getRole());
vo.setShareRate(req.getShareRate());
jointContractMapper.insert(vo);
return vo.getJointId();
}
/**
* 공동계약 수정.
* 기존 share_rate 합(자신 제외) + 수정 share_rate > 1.0 이면 E411 예외.
*/
@Transactional
public void update(Long jointId, JointContractSaveReq req) {
JointContractResp resp = jointContractMapper.selectById(jointId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
validateShareRate(req.getContractId(), jointId, req.getShareRate());
JointContractVO vo = new JointContractVO();
vo.setJointId(jointId);
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setRole(req.getRole());
vo.setShareRate(req.getShareRate());
jointContractMapper.update(vo);
}
@Transactional
public void delete(Long jointId) {
JointContractResp resp = jointContractMapper.selectById(jointId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
jointContractMapper.delete(jointId);
}
/**
* 계약별 share_rate 합 검증.
* excludeJointId: 수정 시 자신 레코드를 합산에서 제외하기 위한 파라미터 (null=create).
* sumShareByContract는 계약 전체 합을 반환하므로, 수정 시 기존 자신의 share_rate를 차감.
*/
private void validateShareRate(Long contractId, Long excludeJointId, BigDecimal newShareRate) {
BigDecimal existingSum = jointContractMapper.sumShareByContract(contractId);
if (existingSum == null) existingSum = BigDecimal.ZERO;
if (excludeJointId != null) {
JointContractResp existing = jointContractMapper.selectById(excludeJointId);
if (existing != null) {
existingSum = existingSum.subtract(existing.getShareRate());
}
}
if (existingSum.add(newShareRate).compareTo(BigDecimal.ONE) > 0) {
throw new BizException(ErrorCode.INVALID_PARAMETER, "계약별 지분율 합이 1을 초과합니다.");
}
}
}
@@ -0,0 +1,103 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
import com.ga.core.mapper.commission.LifeSupportRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class LifeSupportService {
private final LifeSupportRuleMapper ruleMapper;
private final LifeSupportLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<LifeSupportRuleResp> listRules(LifeSupportRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public LifeSupportRuleResp detailRule(Long ruleId) {
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(LifeSupportRuleSaveReq req) {
LifeSupportRuleVO vo = new LifeSupportRuleVO();
vo.setAgentGrade(req.getAgentGrade());
vo.setSupportRate(req.getSupportRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, LifeSupportRuleSaveReq req) {
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
LifeSupportRuleVO vo = new LifeSupportRuleVO();
vo.setRuleId(ruleId);
vo.setAgentGrade(req.getAgentGrade());
vo.setSupportRate(req.getSupportRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<LifeSupportLedgerResp> listLedgers(LifeSupportLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public LifeSupportLedgerResp detailLedger(Long ledgerId) {
LifeSupportLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(LifeSupportLedgerSaveReq req) {
// 지원수수료 = base × support_rate
BigDecimal supportAmount = req.getBaseAmount()
.multiply(req.getSupportRate())
.setScale(2, RoundingMode.HALF_UP);
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
// 실지급액 = support - clawback
BigDecimal netAmount = supportAmount.subtract(clawback).setScale(2, RoundingMode.HALF_UP);
LifeSupportLedgerVO vo = new LifeSupportLedgerVO();
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setBaseAmount(req.getBaseAmount());
vo.setSupportRate(req.getSupportRate());
vo.setSupportAmount(supportAmount);
vo.setClawbackAmount(clawback);
vo.setNetAmount(netAmount);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,105 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.ProductConversionRateMapper;
import com.ga.core.mapper.commission.RecognizedPerformanceLedgerMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RecognizedPerformanceService {
private final ProductConversionRateMapper conversionRateMapper;
private final RecognizedPerformanceLedgerMapper ledgerMapper;
// ── Product Conversion Rate (rule) ────────────────────────────────────────
public PageResponse<ProductConversionRateResp> listRates(ProductConversionRateSearchParam param) {
param.startPage();
return PageResponse.of(conversionRateMapper.selectList(param));
}
public ProductConversionRateResp detailRate(Long rateId) {
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRate(ProductConversionRateSaveReq req) {
ProductConversionRateVO vo = new ProductConversionRateVO();
vo.setProductId(req.getProductId());
vo.setConversionRate(req.getConversionRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
conversionRateMapper.insert(vo);
return vo.getRateId();
}
@Transactional
public void updateRate(Long rateId, ProductConversionRateSaveReq req) {
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ProductConversionRateVO vo = new ProductConversionRateVO();
vo.setRateId(rateId);
vo.setProductId(req.getProductId());
vo.setConversionRate(req.getConversionRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
conversionRateMapper.update(vo);
}
@Transactional
public void deleteRate(Long rateId) {
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
conversionRateMapper.delete(rateId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<RecognizedPerformanceLedgerResp> listLedgers(RecognizedPerformanceLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public RecognizedPerformanceLedgerResp detailLedger(Long ledgerId) {
RecognizedPerformanceLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(RecognizedPerformanceLedgerSaveReq req) {
// 환산보험료 = premium × conversion_rate
BigDecimal convertedPremium = req.getPremiumAmount()
.multiply(req.getConversionRate())
.setScale(2, RoundingMode.HALF_UP);
// 인정실적 = converted_premium × recognition_rate
BigDecimal recognizedAmount = convertedPremium
.multiply(req.getRecognitionRate())
.setScale(2, RoundingMode.HALF_UP);
RecognizedPerformanceLedgerVO vo = new RecognizedPerformanceLedgerVO();
vo.setAgentId(req.getAgentId());
vo.setContractId(req.getContractId());
vo.setSettleMonth(req.getSettleMonth());
vo.setPremiumAmount(req.getPremiumAmount());
vo.setConversionRate(req.getConversionRate());
vo.setConvertedPremium(convertedPremium);
vo.setRecognitionRate(req.getRecognitionRate());
vo.setRecognizedAmount(recognizedAmount);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,115 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
import com.ga.core.mapper.commission.ReferralCommissionRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ReferralCommissionService {
private final ReferralCommissionRuleMapper ruleMapper;
private final ReferralCommissionLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<ReferralCommissionRuleResp> listRules(ReferralCommissionRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public ReferralCommissionRuleResp detailRule(Long ruleId) {
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(ReferralCommissionRuleSaveReq req) {
ReferralCommissionRuleVO vo = new ReferralCommissionRuleVO();
vo.setReferralType(req.getReferralType());
vo.setCalcType(req.getCalcType());
vo.setCommissionRate(req.getCommissionRate());
vo.setFixedAmount(req.getFixedAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, ReferralCommissionRuleSaveReq req) {
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ReferralCommissionRuleVO vo = new ReferralCommissionRuleVO();
vo.setRuleId(ruleId);
vo.setReferralType(req.getReferralType());
vo.setCalcType(req.getCalcType());
vo.setCommissionRate(req.getCommissionRate());
vo.setFixedAmount(req.getFixedAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<ReferralCommissionLedgerResp> listLedgers(ReferralCommissionLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public ReferralCommissionLedgerResp detailLedger(Long ledgerId) {
ReferralCommissionLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(ReferralCommissionLedgerSaveReq req) {
ReferralCommissionLedgerVO vo = new ReferralCommissionLedgerVO();
vo.setContractId(req.getContractId());
vo.setReferrerAgentId(req.getReferrerAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setReferralType(req.getReferralType());
vo.setBaseAmount(req.getBaseAmount());
vo.setCommissionRate(req.getCommissionRate());
// 소개·이관수수료 = base × rate (RATE) 또는 fixed_amount (FIXED)
BigDecimal commissionAmount;
if (req.getCommissionRate() != null) {
commissionAmount = req.getBaseAmount()
.multiply(req.getCommissionRate())
.setScale(2, RoundingMode.HALF_UP);
} else {
// FIXED: 룰에서 fixed_amount 조회
ReferralCommissionRuleVO rule = ruleMapper.selectActiveRule(
req.getReferralType(), java.time.LocalDate.now());
if (rule == null || rule.getFixedAmount() == null) {
throw new BizException(ErrorCode.NOT_FOUND);
}
commissionAmount = rule.getFixedAmount().setScale(2, RoundingMode.HALF_UP);
}
vo.setCommissionAmount(commissionAmount);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,71 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.AgentReserveFundMapper;
import com.ga.core.vo.commission.AgentReserveFundResp;
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
import com.ga.core.vo.commission.AgentReserveFundSearchParam;
import com.ga.core.vo.commission.AgentReserveFundVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ReserveFundService {
private final AgentReserveFundMapper reserveFundMapper;
public PageResponse<AgentReserveFundResp> list(AgentReserveFundSearchParam param) {
param.startPage();
return PageResponse.of(reserveFundMapper.selectList(param));
}
public AgentReserveFundResp detail(Long reserveId) {
AgentReserveFundResp resp = reserveFundMapper.selectById(reserveId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 적립금 생성(upsert).
* balance = (직전월 balance or 0) + reserveIn reserveOut. setScale(2, HALF_UP).
* 해당 월 레코드가 이미 존재하면 update, 없으면 insert.
*/
@Transactional
public Long create(AgentReserveFundSaveReq req) {
AgentReserveFundVO prevMonth = reserveFundMapper.selectLatestBefore(req.getAgentId(), req.getSettleMonth());
BigDecimal prevBalance = (prevMonth != null && prevMonth.getBalance() != null)
? prevMonth.getBalance()
: BigDecimal.ZERO;
BigDecimal balance = prevBalance
.add(req.getReserveIn())
.subtract(req.getReserveOut())
.setScale(2, RoundingMode.HALF_UP);
AgentReserveFundVO existing = reserveFundMapper.selectByAgentAndMonth(req.getAgentId(), req.getSettleMonth());
if (existing != null) {
existing.setReserveIn(req.getReserveIn());
existing.setReserveOut(req.getReserveOut());
existing.setBalance(balance);
reserveFundMapper.update(existing);
return existing.getReserveId();
}
AgentReserveFundVO vo = new AgentReserveFundVO();
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setReserveIn(req.getReserveIn());
vo.setReserveOut(req.getReserveOut());
vo.setBalance(balance);
reserveFundMapper.insert(vo);
return vo.getReserveId();
}
}
@@ -0,0 +1,103 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
import com.ga.core.mapper.commission.SettlingSupportRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SettlingSupportService {
private final SettlingSupportRuleMapper ruleMapper;
private final SettlingSupportLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<SettlingSupportRuleResp> listRules(SettlingSupportRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public SettlingSupportRuleResp detailRule(Long ruleId) {
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(SettlingSupportRuleSaveReq req) {
SettlingSupportRuleVO vo = new SettlingSupportRuleVO();
vo.setAgentGrade(req.getAgentGrade());
vo.setSupportAmount(req.getSupportAmount());
vo.setDurationMonths(req.getDurationMonths());
vo.setClawbackMonths(req.getClawbackMonths());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, SettlingSupportRuleSaveReq req) {
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
SettlingSupportRuleVO vo = new SettlingSupportRuleVO();
vo.setRuleId(ruleId);
vo.setAgentGrade(req.getAgentGrade());
vo.setSupportAmount(req.getSupportAmount());
vo.setDurationMonths(req.getDurationMonths());
vo.setClawbackMonths(req.getClawbackMonths());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<SettlingSupportLedgerResp> listLedgers(SettlingSupportLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public SettlingSupportLedgerResp detailLedger(Long ledgerId) {
SettlingSupportLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(SettlingSupportLedgerSaveReq req) {
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
// 실지급액 = support_amount - clawback_amount
BigDecimal netAmount = req.getSupportAmount()
.subtract(clawback)
.setScale(2, RoundingMode.HALF_UP);
SettlingSupportLedgerVO vo = new SettlingSupportLedgerVO();
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setSupportAmount(req.getSupportAmount());
vo.setClawbackAmount(clawback);
vo.setNetAmount(netAmount);
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,70 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.commission.StepCloseMapper;
import com.ga.core.vo.commission.StepCloseResp;
import com.ga.core.vo.commission.StepCloseSaveReq;
import com.ga.core.vo.commission.StepCloseSearchParam;
import com.ga.core.vo.commission.StepCloseVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StepCloseService {
private final StepCloseMapper stepCloseMapper;
public PageResponse<StepCloseResp> list(StepCloseSearchParam param) {
param.startPage();
return PageResponse.of(stepCloseMapper.selectList(param));
}
public List<StepCloseResp> listByMonth(String settleMonth) {
return stepCloseMapper.selectBySettleMonth(settleMonth);
}
public StepCloseResp detail(Long stepCloseId) {
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(StepCloseSaveReq req) {
StepCloseVO vo = new StepCloseVO();
vo.setSettleMonth(req.getSettleMonth());
vo.setStepCode(req.getStepCode());
vo.setStepSeq(req.getStepSeq());
vo.setStatus("OPEN");
stepCloseMapper.insert(vo);
return vo.getStepCloseId();
}
@Transactional
public void close(Long stepCloseId) {
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
if ("CLOSED".equals(resp.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 마감된 단계입니다.");
}
stepCloseMapper.updateStatus(stepCloseId, "CLOSED", SecurityUtil.getCurrentUserId());
}
@Transactional
public void reopen(Long stepCloseId) {
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
if ("OPEN".equals(resp.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 열려 있는 단계입니다.");
}
stepCloseMapper.updateStatus(stepCloseId, "OPEN", SecurityUtil.getCurrentUserId());
}
}
@@ -0,0 +1,77 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.AwardLedgerMapper;
import com.ga.core.mapper.commission.AwardRuleMapper;
import com.ga.core.vo.commission.AwardLedgerSaveReq;
import com.ga.core.vo.commission.AwardLedgerVO;
import com.ga.core.vo.commission.AwardRuleVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
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.when;
@ExtendWith(MockitoExtension.class)
class AwardServiceTest {
@Mock AwardRuleMapper ruleMapper;
@Mock AwardLedgerMapper ledgerMapper;
@InjectMocks AwardService service;
@Test
@DisplayName("createLedger RATE: award_amount = base × award_rate (1,000,000 × 0.05 = 50,000)")
void createLedger_rate() {
AwardRuleVO rule = new AwardRuleVO();
rule.setCalcType("RATE");
rule.setAwardRate(new BigDecimal("0.05"));
when(ruleMapper.selectActiveRule(eq("PERFORMANCE"), any())).thenReturn(rule);
AwardLedgerSaveReq req = new AwardLedgerSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setAwardType("PERFORMANCE");
req.setBaseAmount(new BigDecimal("1000000"));
ArgumentCaptor<AwardLedgerVO> captor = ArgumentCaptor.forClass(AwardLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
AwardLedgerVO saved = captor.getValue();
assertThat(saved.getAwardAmount()).isEqualByComparingTo("50000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
@Test
@DisplayName("createLedger FIXED: award_amount = award_fixed (200,000)")
void createLedger_fixed() {
AwardRuleVO rule = new AwardRuleVO();
rule.setCalcType("FIXED");
rule.setAwardFixed(new BigDecimal("200000"));
when(ruleMapper.selectActiveRule(eq("NEW_RECRUIT"), any())).thenReturn(rule);
AwardLedgerSaveReq req = new AwardLedgerSaveReq();
req.setAgentId(2L);
req.setSettleMonth("202506");
req.setAwardType("NEW_RECRUIT");
req.setBaseAmount(new BigDecimal("999999")); // FIXED이므로 base는 무시됨
ArgumentCaptor<AwardLedgerVO> captor = ArgumentCaptor.forClass(AwardLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
AwardLedgerVO saved = captor.getValue();
assertThat(saved.getAwardAmount()).isEqualByComparingTo("200000.00");
}
}
@@ -0,0 +1,48 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
import com.ga.core.mapper.commission.BranchMgrAllowanceRuleMapper;
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerSaveReq;
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class BranchAllowanceServiceTest {
@Mock BranchMgrAllowanceRuleMapper ruleMapper;
@Mock BranchMgrAllowanceLedgerMapper ledgerMapper;
@InjectMocks BranchAllowanceService service;
@Test
@DisplayName("createLedger: allowance = base × allowance_rate (2,000,000 × 0.05 = 100,000)")
void createLedger_allowanceAmount() {
BranchMgrAllowanceLedgerSaveReq req = new BranchMgrAllowanceLedgerSaveReq();
req.setManagerAgentId(1L);
req.setOrgId(10L);
req.setSettleMonth("202506");
req.setBaseAmount(new BigDecimal("2000000"));
req.setAllowanceRate(new BigDecimal("0.05"));
req.setClawbackAmount(null);
ArgumentCaptor<BranchMgrAllowanceLedgerVO> captor = ArgumentCaptor.forClass(BranchMgrAllowanceLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
BranchMgrAllowanceLedgerVO saved = captor.getValue();
assertThat(saved.getAllowanceAmount()).isEqualByComparingTo("100000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
}
@@ -0,0 +1,69 @@
package com.ga.api.service.commission;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.commission.CommissionSimulationMapper;
import com.ga.core.vo.commission.CommissionSimulationResp;
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
import com.ga.core.vo.commission.CommissionSimulationVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CommissionSimulationServiceTest {
@Mock CommissionSimulationMapper simulationMapper;
@InjectMocks CommissionSimulationService service;
/**
* 1,000,000 × 0.6 = 600,000 (company_payment)
* 600,000 × 0.75 = 450,000 (agent_commission)
* 450,000 × 0.033 = 14,850 (withholding_tax)
* 450,000 - 14,850 = 435,150 (net_amount)
* 450,000 × 0.5 = 225,000 (expected_chargeback 자동 계산)
*/
@Test
@DisplayName("simulate: 수수료 공식 전체 검증 (1,000,000/0.6/0.75)")
void simulate_fullFormula() {
CommissionSimulationSaveReq req = new CommissionSimulationSaveReq();
req.setSimName("테스트시뮬레이션");
req.setPremiumAmount(new BigDecimal("1000000"));
req.setCompanyRate(new BigDecimal("0.6"));
req.setPayoutRate(new BigDecimal("0.75"));
req.setCommissionYear(1);
req.setExpectedChargeback(null); // 자동 계산
ArgumentCaptor<CommissionSimulationVO> captor = ArgumentCaptor.forClass(CommissionSimulationVO.class);
when(simulationMapper.insert(captor.capture())).thenReturn(1);
CommissionSimulationResp mockResp = new CommissionSimulationResp();
mockResp.setSimId(1L);
when(simulationMapper.selectById(any())).thenReturn(mockResp);
try (MockedStatic<SecurityUtil> staticMock = mockStatic(SecurityUtil.class)) {
staticMock.when(SecurityUtil::getCurrentUserId).thenReturn(null);
service.simulate(req);
}
CommissionSimulationVO saved = captor.getValue();
assertThat(saved.getCompanyPayment()).isEqualByComparingTo("600000.00");
assertThat(saved.getAgentCommission()).isEqualByComparingTo("450000.00");
assertThat(saved.getWithholdingTax()).isEqualByComparingTo("14850.00");
assertThat(saved.getNetAmount()).isEqualByComparingTo("435150.00");
assertThat(saved.getExpectedChargeback()).isEqualByComparingTo("225000.00");
}
}
@@ -0,0 +1,72 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.AgentGradeEvaluationMapper;
import com.ga.core.mapper.commission.GradeEvalRuleMapper;
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
import com.ga.core.vo.commission.AgentGradeEvaluationVO;
import com.ga.core.vo.commission.GradeEvalRuleVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GradeEvaluationServiceTest {
@Mock GradeEvalRuleMapper ruleMapper;
@Mock AgentGradeEvaluationMapper evalMapper;
@InjectMocks GradeEvaluationService service;
@Test
@DisplayName("createEval: 매칭 룰이 있으면 evaluated_grade_id = rule.gradeId")
void createEval_ruleMatched() {
GradeEvalRuleVO rule = new GradeEvalRuleVO();
rule.setGradeId(5L);
when(ruleMapper.selectActiveRuleByScore(any(), any())).thenReturn(rule);
AgentGradeEvaluationSaveReq req = new AgentGradeEvaluationSaveReq();
req.setAgentId(1L);
req.setEvalPeriod("202506");
req.setPerformanceScore(new BigDecimal("850"));
req.setPrevGradeId(3L);
ArgumentCaptor<AgentGradeEvaluationVO> captor = ArgumentCaptor.forClass(AgentGradeEvaluationVO.class);
when(evalMapper.insert(captor.capture())).thenReturn(1);
service.createEval(req);
AgentGradeEvaluationVO saved = captor.getValue();
assertThat(saved.getEvaluatedGradeId()).isEqualTo(5L);
assertThat(saved.getStatus()).isEqualTo("EVALUATED");
}
@Test
@DisplayName("createEval: 매칭 룰 없으면 evaluated_grade_id = prevGradeId 유지")
void createEval_noRuleMatch_usePrevGrade() {
when(ruleMapper.selectActiveRuleByScore(any(), any())).thenReturn(null);
AgentGradeEvaluationSaveReq req = new AgentGradeEvaluationSaveReq();
req.setAgentId(1L);
req.setEvalPeriod("202506");
req.setPerformanceScore(new BigDecimal("100"));
req.setPrevGradeId(2L);
ArgumentCaptor<AgentGradeEvaluationVO> captor = ArgumentCaptor.forClass(AgentGradeEvaluationVO.class);
when(evalMapper.insert(captor.capture())).thenReturn(1);
service.createEval(req);
AgentGradeEvaluationVO saved = captor.getValue();
assertThat(saved.getEvaluatedGradeId()).isEqualTo(2L);
}
}
@@ -0,0 +1,47 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.HqSettleItemMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HqSettleServiceTest {
@Mock HqSettleItemMapper itemMapper;
@InjectMocks HqSettleService service;
@Test
@DisplayName("summary: profit = income - expense (3,000,000 - 1,200,000 = 1,800,000)")
void summary_profitCalc() {
when(itemMapper.sumByMonthAndType("202506", "INCOME")).thenReturn(new BigDecimal("3000000"));
when(itemMapper.sumByMonthAndType("202506", "EXPENSE")).thenReturn(new BigDecimal("1200000"));
HqSettleService.HqSummaryResp result = service.summary("202506");
assertThat(result.getIncome()).isEqualByComparingTo("3000000");
assertThat(result.getExpense()).isEqualByComparingTo("1200000");
assertThat(result.getProfit()).isEqualByComparingTo("1800000");
assertThat(result.getSettleMonth()).isEqualTo("202506");
}
@Test
@DisplayName("summary: income/expense null이면 0으로 처리 → profit = 0")
void summary_nullHandling() {
when(itemMapper.sumByMonthAndType("202506", "INCOME")).thenReturn(null);
when(itemMapper.sumByMonthAndType("202506", "EXPENSE")).thenReturn(null);
HqSettleService.HqSummaryResp result = service.summary("202506");
assertThat(result.getProfit()).isEqualByComparingTo("0");
}
}
@@ -0,0 +1,67 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.IncomeCommissionLedgerMapper;
import com.ga.core.mapper.commission.MarginSummaryMapper;
import com.ga.core.vo.commission.IncomeCommissionLedgerSaveReq;
import com.ga.core.vo.commission.IncomeCommissionLedgerVO;
import com.ga.core.vo.commission.MarginSummaryVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class IncomeCommissionServiceTest {
@Mock IncomeCommissionLedgerMapper ledgerMapper;
@Mock MarginSummaryMapper marginMapper;
@InjectMocks IncomeCommissionService service;
@Test
@DisplayName("createLedger: income_amount = premium × company_rate (1,000,000 × 0.6 = 600,000)")
void createLedger_incomeAmount() {
IncomeCommissionLedgerSaveReq req = new IncomeCommissionLedgerSaveReq();
req.setContractId(1L);
req.setCarrierId(10L);
req.setSettleMonth("202506");
req.setPremiumAmount(new BigDecimal("1000000"));
req.setCompanyRate(new BigDecimal("0.6"));
req.setCommissionType("RECRUIT");
ArgumentCaptor<IncomeCommissionLedgerVO> captor = ArgumentCaptor.forClass(IncomeCommissionLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
IncomeCommissionLedgerVO saved = captor.getValue();
assertThat(saved.getIncomeAmount()).isEqualByComparingTo("600000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
@Test
@DisplayName("recalcMargin: margin = income - payout, marginRate = margin / income")
void recalcMargin_calculation() {
when(marginMapper.sumIncomeByMonth("202506")).thenReturn(new BigDecimal("1000000"));
when(marginMapper.sumPayoutByMonth("202506", null)).thenReturn(new BigDecimal("600000"));
when(marginMapper.selectByMonthAndAgent("202506", null)).thenReturn(null);
ArgumentCaptor<MarginSummaryVO> captor = ArgumentCaptor.forClass(MarginSummaryVO.class);
when(marginMapper.insert(captor.capture())).thenReturn(1);
service.recalcMargin("202506", null);
MarginSummaryVO saved = captor.getValue();
assertThat(saved.getMarginAmount()).isEqualByComparingTo("400000.00");
assertThat(saved.getMarginRate()).isEqualByComparingTo("0.4000");
}
}
@@ -0,0 +1,58 @@
package com.ga.api.service.commission;
import com.ga.common.exception.BizException;
import com.ga.core.mapper.commission.JointContractMapper;
import com.ga.core.vo.commission.JointContractSaveReq;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class JointContractServiceTest {
@Mock JointContractMapper jointContractMapper;
@InjectMocks JointContractService service;
@Test
@DisplayName("create: 기존 지분율 합 + 신규 shareRate > 1이면 BizException 발생")
void create_shareRateExceedsOne_throwsBizException() {
// 기존 합 0.7, 신규 0.4 → 합계 1.1 > 1
when(jointContractMapper.sumShareByContract(eq(100L))).thenReturn(new BigDecimal("0.7"));
JointContractSaveReq req = new JointContractSaveReq();
req.setContractId(100L);
req.setAgentId(5L);
req.setRole("MAIN");
req.setShareRate(new BigDecimal("0.4"));
assertThatThrownBy(() -> service.create(req))
.isInstanceOf(BizException.class);
}
@Test
@DisplayName("create: 기존 합 + 신규 shareRate <= 1이면 정상 생성")
void create_shareRateValid_noException() {
// 기존 합 0.6, 신규 0.4 → 합계 1.0 = 1 (경계값 허용)
when(jointContractMapper.sumShareByContract(eq(100L))).thenReturn(new BigDecimal("0.6"));
when(jointContractMapper.insert(org.mockito.ArgumentMatchers.any())).thenReturn(1);
JointContractSaveReq req = new JointContractSaveReq();
req.setContractId(100L);
req.setAgentId(5L);
req.setRole("SUB");
req.setShareRate(new BigDecimal("0.4"));
// 예외 없이 통과해야 함
service.create(req);
}
}
@@ -0,0 +1,68 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
import com.ga.core.mapper.commission.LifeSupportRuleMapper;
import com.ga.core.vo.commission.LifeSupportLedgerSaveReq;
import com.ga.core.vo.commission.LifeSupportLedgerVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class LifeSupportServiceTest {
@Mock LifeSupportRuleMapper ruleMapper;
@Mock LifeSupportLedgerMapper ledgerMapper;
@InjectMocks LifeSupportService service;
@Test
@DisplayName("createLedger: support = base×rate, net = support - clawback (1,000,000×0.3=300,000, 300,000-20,000=280,000)")
void createLedger_calculation() {
LifeSupportLedgerSaveReq req = new LifeSupportLedgerSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setBaseAmount(new BigDecimal("1000000"));
req.setSupportRate(new BigDecimal("0.3"));
req.setClawbackAmount(new BigDecimal("20000"));
ArgumentCaptor<LifeSupportLedgerVO> captor = ArgumentCaptor.forClass(LifeSupportLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
LifeSupportLedgerVO saved = captor.getValue();
assertThat(saved.getSupportAmount()).isEqualByComparingTo("300000.00");
assertThat(saved.getNetAmount()).isEqualByComparingTo("280000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
@Test
@DisplayName("createLedger: clawback null이면 0으로 처리 → net = support")
void createLedger_noClawback() {
LifeSupportLedgerSaveReq req = new LifeSupportLedgerSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setBaseAmount(new BigDecimal("500000"));
req.setSupportRate(new BigDecimal("0.2"));
req.setClawbackAmount(null);
ArgumentCaptor<LifeSupportLedgerVO> captor = ArgumentCaptor.forClass(LifeSupportLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
LifeSupportLedgerVO saved = captor.getValue();
assertThat(saved.getSupportAmount()).isEqualByComparingTo("100000.00");
assertThat(saved.getNetAmount()).isEqualByComparingTo("100000.00");
}
}
@@ -0,0 +1,49 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.ProductConversionRateMapper;
import com.ga.core.mapper.commission.RecognizedPerformanceLedgerMapper;
import com.ga.core.vo.commission.RecognizedPerformanceLedgerSaveReq;
import com.ga.core.vo.commission.RecognizedPerformanceLedgerVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RecognizedPerformanceServiceTest {
@Mock ProductConversionRateMapper conversionRateMapper;
@Mock RecognizedPerformanceLedgerMapper ledgerMapper;
@InjectMocks RecognizedPerformanceService service;
@Test
@DisplayName("createLedger: converted=premium×conversion_rate, recognized=converted×recognition_rate (1,000,000×0.5×0.8=400,000)")
void createLedger_twoStepCalc() {
RecognizedPerformanceLedgerSaveReq req = new RecognizedPerformanceLedgerSaveReq();
req.setAgentId(1L);
req.setContractId(2L);
req.setSettleMonth("202506");
req.setPremiumAmount(new BigDecimal("1000000"));
req.setConversionRate(new BigDecimal("0.5"));
req.setRecognitionRate(new BigDecimal("0.8"));
ArgumentCaptor<RecognizedPerformanceLedgerVO> captor = ArgumentCaptor.forClass(RecognizedPerformanceLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
RecognizedPerformanceLedgerVO saved = captor.getValue();
assertThat(saved.getConvertedPremium()).isEqualByComparingTo("500000.00");
assertThat(saved.getRecognizedAmount()).isEqualByComparingTo("400000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
}
@@ -0,0 +1,75 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
import com.ga.core.mapper.commission.ReferralCommissionRuleMapper;
import com.ga.core.vo.commission.ReferralCommissionLedgerSaveReq;
import com.ga.core.vo.commission.ReferralCommissionLedgerVO;
import com.ga.core.vo.commission.ReferralCommissionRuleVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ReferralCommissionServiceTest {
@Mock ReferralCommissionRuleMapper ruleMapper;
@Mock ReferralCommissionLedgerMapper ledgerMapper;
@InjectMocks ReferralCommissionService service;
@Test
@DisplayName("createLedger RATE: commission_amount = base × rate (500,000 × 0.1 = 50,000)")
void createLedger_rate() {
ReferralCommissionLedgerSaveReq req = new ReferralCommissionLedgerSaveReq();
req.setContractId(1L);
req.setReferrerAgentId(2L);
req.setSettleMonth("202506");
req.setReferralType("REFERRAL");
req.setBaseAmount(new BigDecimal("500000"));
req.setCommissionRate(new BigDecimal("0.1"));
ArgumentCaptor<ReferralCommissionLedgerVO> captor = ArgumentCaptor.forClass(ReferralCommissionLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
ReferralCommissionLedgerVO saved = captor.getValue();
assertThat(saved.getCommissionAmount()).isEqualByComparingTo("50000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
@Test
@DisplayName("createLedger FIXED: commission_amount = rule.fixedAmount (30,000)")
void createLedger_fixed() {
ReferralCommissionLedgerSaveReq req = new ReferralCommissionLedgerSaveReq();
req.setContractId(1L);
req.setReferrerAgentId(2L);
req.setSettleMonth("202506");
req.setReferralType("TRANSFER");
req.setBaseAmount(new BigDecimal("500000"));
req.setCommissionRate(null); // FIXED 분기
ReferralCommissionRuleVO rule = new ReferralCommissionRuleVO();
rule.setCalcType("FIXED");
rule.setFixedAmount(new BigDecimal("30000"));
when(ruleMapper.selectActiveRule(any(), any())).thenReturn(rule);
ArgumentCaptor<ReferralCommissionLedgerVO> captor = ArgumentCaptor.forClass(ReferralCommissionLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
ReferralCommissionLedgerVO saved = captor.getValue();
assertThat(saved.getCommissionAmount()).isEqualByComparingTo("30000.00");
}
}
@@ -0,0 +1,71 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.AgentReserveFundMapper;
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
import com.ga.core.vo.commission.AgentReserveFundVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
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.when;
@ExtendWith(MockitoExtension.class)
class ReserveFundServiceTest {
@Mock AgentReserveFundMapper reserveFundMapper;
@InjectMocks ReserveFundService service;
@Test
@DisplayName("create: balance = 직전월잔액 + reserveIn - reserveOut (100,000 + 50,000 - 20,000 = 130,000)")
void create_balanceCalc() {
AgentReserveFundVO prevMonth = new AgentReserveFundVO();
prevMonth.setBalance(new BigDecimal("100000"));
when(reserveFundMapper.selectLatestBefore(eq(1L), eq("202506"))).thenReturn(prevMonth);
when(reserveFundMapper.selectByAgentAndMonth(eq(1L), eq("202506"))).thenReturn(null);
AgentReserveFundSaveReq req = new AgentReserveFundSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setReserveIn(new BigDecimal("50000"));
req.setReserveOut(new BigDecimal("20000"));
ArgumentCaptor<AgentReserveFundVO> captor = ArgumentCaptor.forClass(AgentReserveFundVO.class);
when(reserveFundMapper.insert(captor.capture())).thenReturn(1);
service.create(req);
AgentReserveFundVO saved = captor.getValue();
assertThat(saved.getBalance()).isEqualByComparingTo("130000.00");
}
@Test
@DisplayName("create: 직전월 잔액 없으면 0으로 처리 → balance = reserveIn - reserveOut")
void create_noPrevMonth() {
when(reserveFundMapper.selectLatestBefore(eq(1L), eq("202501"))).thenReturn(null);
when(reserveFundMapper.selectByAgentAndMonth(eq(1L), eq("202501"))).thenReturn(null);
AgentReserveFundSaveReq req = new AgentReserveFundSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202501");
req.setReserveIn(new BigDecimal("80000"));
req.setReserveOut(new BigDecimal("30000"));
ArgumentCaptor<AgentReserveFundVO> captor = ArgumentCaptor.forClass(AgentReserveFundVO.class);
when(reserveFundMapper.insert(captor.capture())).thenReturn(1);
service.create(req);
AgentReserveFundVO saved = captor.getValue();
assertThat(saved.getBalance()).isEqualByComparingTo("50000.00");
}
}
@@ -0,0 +1,65 @@
package com.ga.api.service.commission;
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
import com.ga.core.mapper.commission.SettlingSupportRuleMapper;
import com.ga.core.vo.commission.SettlingSupportLedgerSaveReq;
import com.ga.core.vo.commission.SettlingSupportLedgerVO;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SettlingSupportServiceTest {
@Mock SettlingSupportRuleMapper ruleMapper;
@Mock SettlingSupportLedgerMapper ledgerMapper;
@InjectMocks SettlingSupportService service;
@Test
@DisplayName("createLedger: net_amount = support_amount - clawback_amount (500,000 - 50,000 = 450,000)")
void createLedger_netAmount() {
SettlingSupportLedgerSaveReq req = new SettlingSupportLedgerSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setSupportAmount(new BigDecimal("500000"));
req.setClawbackAmount(new BigDecimal("50000"));
ArgumentCaptor<SettlingSupportLedgerVO> captor = ArgumentCaptor.forClass(SettlingSupportLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
SettlingSupportLedgerVO saved = captor.getValue();
assertThat(saved.getNetAmount()).isEqualByComparingTo("450000.00");
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
}
@Test
@DisplayName("createLedger: clawback null이면 0으로 처리 → net = support_amount")
void createLedger_noClawback() {
SettlingSupportLedgerSaveReq req = new SettlingSupportLedgerSaveReq();
req.setAgentId(1L);
req.setSettleMonth("202506");
req.setSupportAmount(new BigDecimal("300000"));
req.setClawbackAmount(null);
ArgumentCaptor<SettlingSupportLedgerVO> captor = ArgumentCaptor.forClass(SettlingSupportLedgerVO.class);
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
service.createLedger(req);
SettlingSupportLedgerVO saved = captor.getValue();
assertThat(saved.getNetAmount()).isEqualByComparingTo("300000.00");
assertThat(saved.getClawbackAmount()).isEqualByComparingTo("0");
}
}