diff --git a/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackCalculateReq.java b/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackCalculateReq.java new file mode 100644 index 0000000..3a4c92d --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackCalculateReq.java @@ -0,0 +1,24 @@ +package com.ga.api.controller.chargeback; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Data +public class ChargebackCalculateReq { + + @NotNull + private LocalDate contractDate; + + @NotNull + private LocalDate terminationDate; + + @NotNull + @Positive + private BigDecimal originalCommission; + + private String productCategory; +} diff --git a/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackGradeController.java b/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackGradeController.java new file mode 100644 index 0000000..66161f3 --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/controller/chargeback/ChargebackGradeController.java @@ -0,0 +1,75 @@ +package com.ga.api.controller.chargeback; + +import com.ga.api.service.chargeback.ChargebackCalculator; +import com.ga.api.service.chargeback.ChargebackGradeService; +import com.ga.api.service.chargeback.ChargebackResult; +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.chargeback.ChargebackGradeResp; +import com.ga.core.vo.chargeback.ChargebackGradeSaveReq; +import com.ga.core.vo.chargeback.ChargebackGradeSearchParam; +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/chargeback-grades") +@RequiredArgsConstructor +public class ChargebackGradeController { + + private final ChargebackGradeService gradeService; + private final ChargebackCalculator calculator; + + @GetMapping + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "READ") + public ApiResponse> list(ChargebackGradeSearchParam param) { + return ApiResponse.ok(gradeService.list(param)); + } + + @GetMapping("/{gradeId}") + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "READ") + public ApiResponse detail(@PathVariable Long gradeId) { + return ApiResponse.ok(gradeService.detail(gradeId)); + } + + @PostMapping + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "CREATE") + @DataChangeLog(menu = "CHARGEBACK_GRADE", table = "chargeback_grade") + public ApiResponse create(@Valid @RequestBody ChargebackGradeSaveReq req) { + gradeService.create(req); + return ApiResponse.ok(); + } + + @PutMapping("/{gradeId}") + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "UPDATE") + @DataChangeLog(menu = "CHARGEBACK_GRADE", table = "chargeback_grade") + public ApiResponse update(@PathVariable Long gradeId, + @Valid @RequestBody ChargebackGradeSaveReq req) { + gradeService.update(gradeId, req); + return ApiResponse.ok(); + } + + @PutMapping("/{gradeId}/deactivate") + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "UPDATE") + @DataChangeLog(menu = "CHARGEBACK_GRADE", table = "chargeback_grade") + public ApiResponse deactivate(@PathVariable Long gradeId) { + gradeService.deactivate(gradeId); + return ApiResponse.ok(); + } + + @PostMapping("/calculate") + @RequirePermission(menu = "CHARGEBACK_GRADE", perm = "READ") + public ApiResponse calculate(@Valid @RequestBody ChargebackCalculateReq req) { + ChargebackResult result = calculator.calculate( + req.getContractDate(), + req.getTerminationDate(), + req.getOriginalCommission(), + req.getProductCategory() + ); + return ApiResponse.ok(result); + } +} diff --git a/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackCalculator.java b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackCalculator.java new file mode 100644 index 0000000..10ebaa9 --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackCalculator.java @@ -0,0 +1,45 @@ +package com.ga.api.service.chargeback; + +import com.ga.core.mapper.chargeback.ChargebackGradeMapper; +import com.ga.core.vo.chargeback.ChargebackGradeVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +@Component +@RequiredArgsConstructor +public class ChargebackCalculator { + + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private final ChargebackGradeMapper gradeMapper; + + public ChargebackResult calculate(LocalDate contractDate, LocalDate terminationDate, + BigDecimal originalCommission, String productCategory) { + int monthsElapsed = (int) ChronoUnit.MONTHS.between(contractDate, terminationDate); + String today = LocalDate.now().format(DATE_FMT); + + ChargebackGradeVO grade = gradeMapper.selectByMonths(productCategory, monthsElapsed, today); + + if (grade == null) { + return new ChargebackResult(BigDecimal.ZERO, monthsElapsed, BigDecimal.ZERO, + "적용 환수 구간 없음"); + } + + BigDecimal chargebackAmount = originalCommission + .multiply(grade.getChargebackPercent()) + .divide(BigDecimal.valueOf(100), 0, RoundingMode.HALF_UP); + + return new ChargebackResult( + chargebackAmount, + monthsElapsed, + grade.getChargebackPercent(), + String.format("경과월 %d개월 → 환수율 %s%%", monthsElapsed, grade.getChargebackPercent()) + ); + } +} diff --git a/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackGradeService.java b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackGradeService.java new file mode 100644 index 0000000..0b82da2 --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackGradeService.java @@ -0,0 +1,86 @@ +package com.ga.api.service.chargeback; + +import com.ga.common.exception.BizException; +import com.ga.common.exception.ErrorCode; +import com.ga.common.model.PageResponse; +import com.ga.core.mapper.chargeback.ChargebackGradeMapper; +import com.ga.core.vo.chargeback.ChargebackGradeResp; +import com.ga.core.vo.chargeback.ChargebackGradeSaveReq; +import com.ga.core.vo.chargeback.ChargebackGradeSearchParam; +import com.ga.core.vo.chargeback.ChargebackGradeVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +@Service +@RequiredArgsConstructor +public class ChargebackGradeService { + + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private final ChargebackGradeMapper gradeMapper; + + @Transactional(readOnly = true) + public PageResponse list(ChargebackGradeSearchParam param) { + param.startPage(); + return PageResponse.of(gradeMapper.selectList(param)); + } + + @Transactional(readOnly = true) + public ChargebackGradeResp detail(Long gradeId) { + ChargebackGradeResp resp = gradeMapper.selectDetailById(gradeId); + if (resp == null) throw new BizException(ErrorCode.NOT_FOUND); + return resp; + } + + @Transactional + public void create(ChargebackGradeSaveReq req) { + String today = LocalDate.now().format(DATE_FMT); + ChargebackGradeVO existing = gradeMapper.selectByMonths( + req.getProductCategory(), req.getMonthsFrom(), today); + if (existing != null) { + throw new BizException(ErrorCode.DUPLICATE_DATA); + } + gradeMapper.insert(toVO(req)); + } + + @Transactional + public void update(Long gradeId, ChargebackGradeSaveReq req) { + ChargebackGradeVO existing = gradeMapper.selectById(gradeId); + if (existing == null) throw new BizException(ErrorCode.NOT_FOUND); + ChargebackGradeVO vo = toVO(req); + vo.setGradeId(gradeId); + vo.setIsActive(existing.getIsActive()); + gradeMapper.update(vo); + } + + @Transactional + public void deactivate(Long gradeId) { + ChargebackGradeVO existing = gradeMapper.selectById(gradeId); + if (existing == null) throw new BizException(ErrorCode.NOT_FOUND); + if (Boolean.FALSE.equals(existing.getIsActive())) { + throw new BizException(ErrorCode.INVALID_PARAMETER); + } + gradeMapper.updateStatus(gradeId, false); + } + + @Transactional(readOnly = true) + public ChargebackGradeVO selectByMonths(String productCategory, Integer monthsElapsed, String baseDate) { + return gradeMapper.selectByMonths(productCategory, monthsElapsed, baseDate); + } + + private ChargebackGradeVO toVO(ChargebackGradeSaveReq req) { + ChargebackGradeVO vo = new ChargebackGradeVO(); + vo.setProductCategory(req.getProductCategory()); + vo.setMonthsFrom(req.getMonthsFrom()); + vo.setMonthsTo(req.getMonthsTo()); + vo.setChargebackPercent(req.getChargebackPercent()); + vo.setEffectiveFrom(req.getEffectiveFrom()); + vo.setEffectiveTo(req.getEffectiveTo()); + vo.setIsActive(true); + return vo; + } +} diff --git a/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackResult.java b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackResult.java new file mode 100644 index 0000000..57a2065 --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/service/chargeback/ChargebackResult.java @@ -0,0 +1,10 @@ +package com.ga.api.service.chargeback; + +import java.math.BigDecimal; + +public record ChargebackResult( + BigDecimal chargebackAmount, + int monthsElapsed, + BigDecimal appliedPercent, + String reason +) {}