feat(api): ChargebackCalculator + ChargebackGradeService + Controller (도메인 P1-3-c)
도메인 P1 마지막 항목 — 차등 환수 마무리. ChargebackCalculator (pure @Component): - calculate(contractDate, terminationDate, originalCommission, productCategory) - monthsElapsed = ChronoUnit.MONTHS.between - selectByMonths로 적용 비율 조회 - chargebackAmount = originalCommission × percent / 100 (HALF_UP, scale 0) - grade 없으면 ZERO + "적용 환수 구간 없음" ChargebackResult record: - chargebackAmount, monthsElapsed, appliedPercent, reason ChargebackGradeService: - list/detail (readOnly) - create/update (@Transactional, 중복 검증) - deactivate (@Transactional) ChargebackGradeController: - GET/POST/PUT 표준 + /api/chargeback-grades/calculate (외부 호출용) - 모든 보호 엔드포인트 @RequirePermission, CUD에 @DataChangeLog ga-batch 정산 step 자동 통합은 마무리 통합 단계에서.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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<PageResponse<ChargebackGradeResp>> list(ChargebackGradeSearchParam param) {
|
||||
return ApiResponse.ok(gradeService.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{gradeId}")
|
||||
@RequirePermission(menu = "CHARGEBACK_GRADE", perm = "READ")
|
||||
public ApiResponse<ChargebackGradeResp> 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<Void> 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<Void> 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<Void> deactivate(@PathVariable Long gradeId) {
|
||||
gradeService.deactivate(gradeId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/calculate")
|
||||
@RequirePermission(menu = "CHARGEBACK_GRADE", perm = "READ")
|
||||
public ApiResponse<ChargebackResult> calculate(@Valid @RequestBody ChargebackCalculateReq req) {
|
||||
ChargebackResult result = calculator.calculate(
|
||||
req.getContractDate(),
|
||||
req.getTerminationDate(),
|
||||
req.getOriginalCommission(),
|
||||
req.getProductCategory()
|
||||
);
|
||||
return ApiResponse.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<ChargebackGradeResp> 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
) {}
|
||||
Reference in New Issue
Block a user