동기화

This commit is contained in:
GA Pro
2026-05-14 01:21:27 +09:00
parent acea2afce5
commit 958fe6b980
379 changed files with 21786 additions and 423 deletions
@@ -0,0 +1,15 @@
package com.ga.api.controller.approval;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class AdvanceReq {
@NotNull
private Long approverId;
/** APPROVE / REJECT */
@NotBlank
private String action;
private String comment;
}
@@ -0,0 +1,115 @@
package com.ga.api.controller.approval;
import com.ga.api.service.approval.ApprovalService;
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.approval.*;
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/approvals")
@RequiredArgsConstructor
public class ApprovalController {
private final ApprovalService service;
// ── 결재선 ─────────────────────────────────────────────────────────────────
@GetMapping("/lines")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<PageResponse<ApprovalLineResp>> listLines(
@ModelAttribute ApprovalLineSearchParam param) {
return ApiResponse.ok(service.listLines(param));
}
@GetMapping("/lines/{lineId}")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<ApprovalLineResp> detailLine(@PathVariable Long lineId) {
return ApiResponse.ok(service.detailLine(lineId));
}
@GetMapping("/lines/{lineId}/steps")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<List<ApprovalLineStepResp>> stepsOfLine(@PathVariable Long lineId) {
return ApiResponse.ok(service.stepsOfLine(lineId));
}
@PostMapping("/lines")
@RequirePermission(menu = "APPROVAL", perm = "CREATE")
@DataChangeLog(menu = "APPROVAL", table = "approval_line")
public ApiResponse<Long> createLine(@Valid @RequestBody ApprovalLineSaveReq req) {
return ApiResponse.ok(service.createLine(req));
}
@PutMapping("/lines/{lineId}")
@RequirePermission(menu = "APPROVAL", perm = "UPDATE")
@DataChangeLog(menu = "APPROVAL", table = "approval_line")
public ApiResponse<Void> updateLine(@PathVariable Long lineId,
@Valid @RequestBody ApprovalLineSaveReq req) {
service.updateLine(lineId, req);
return ApiResponse.ok();
}
@PostMapping("/lines/{lineId}/steps")
@RequirePermission(menu = "APPROVAL", perm = "UPDATE")
@DataChangeLog(menu = "APPROVAL", table = "approval_line_step")
public ApiResponse<Void> addStep(@PathVariable Long lineId,
@Valid @RequestBody ApprovalLineStepSaveReq req) {
req.setLineId(lineId);
service.addStep(req);
return ApiResponse.ok();
}
// ── 결재 요청 ──────────────────────────────────────────────────────────────
@GetMapping("/requests")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<PageResponse<ApprovalRequestResp>> listRequests(
@ModelAttribute ApprovalRequestSearchParam param) {
return ApiResponse.ok(service.listRequests(param));
}
@GetMapping("/requests/{requestId}")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<ApprovalRequestResp> detailRequest(@PathVariable Long requestId) {
return ApiResponse.ok(service.detailRequest(requestId));
}
@GetMapping("/requests/{requestId}/history")
@RequirePermission(menu = "APPROVAL", perm = "READ")
public ApiResponse<List<ApprovalHistoryResp>> historyOfRequest(@PathVariable Long requestId) {
return ApiResponse.ok(service.historyOfRequest(requestId));
}
@PostMapping("/requests")
@RequirePermission(menu = "APPROVAL", perm = "CREATE")
@DataChangeLog(menu = "APPROVAL", table = "approval_request")
public ApiResponse<Long> createRequest(@Valid @RequestBody ApprovalRequestSaveReq req) {
return ApiResponse.ok(service.createRequest(req));
}
@PostMapping("/requests/{requestId}/advance")
@RequirePermission(menu = "APPROVAL", perm = "APPROVE")
@DataChangeLog(menu = "APPROVAL", table = "approval_request")
public ApiResponse<Void> advance(@PathVariable Long requestId,
@Valid @RequestBody AdvanceReq req) {
service.advance(requestId, req.getApproverId(), req.getAction(), req.getComment());
return ApiResponse.ok();
}
@PostMapping("/requests/{requestId}/cancel")
@RequirePermission(menu = "APPROVAL", perm = "UPDATE")
@DataChangeLog(menu = "APPROVAL", table = "approval_request")
public ApiResponse<Void> cancel(@PathVariable Long requestId) {
service.cancel(requestId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,85 @@
package com.ga.api.controller.attachment;
import com.ga.api.service.attachment.AttachmentService;
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.attachment.AttachmentResp;
import com.ga.core.vo.attachment.AttachmentSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@Tag(name = "첨부파일")
@RestController
@RequestMapping("/api/attachments")
@RequiredArgsConstructor
public class AttachmentController {
private final AttachmentService service;
@GetMapping
@RequirePermission(menu = "ATTACHMENT", perm = "READ")
public ApiResponse<PageResponse<AttachmentResp>> list(@ModelAttribute AttachmentSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/by-ref")
@RequirePermission(menu = "ATTACHMENT", perm = "READ")
public ApiResponse<List<AttachmentResp>> listByRef(@RequestParam String refType,
@RequestParam Long refId) {
return ApiResponse.ok(service.listByRef(refType, refId));
}
@GetMapping("/{attachmentId}/meta")
@RequirePermission(menu = "ATTACHMENT", perm = "READ")
public ApiResponse<AttachmentResp> detail(@PathVariable Long attachmentId) {
return ApiResponse.ok(service.detail(attachmentId));
}
@PostMapping(consumes = "multipart/form-data")
@RequirePermission(menu = "ATTACHMENT", perm = "CREATE")
@DataChangeLog(menu = "ATTACHMENT", table = "attachment")
public ApiResponse<Long> upload(@RequestParam("file") MultipartFile file,
@RequestParam String refType,
@RequestParam Long refId) {
return ApiResponse.ok(service.upload(file, refType, refId));
}
@GetMapping("/{attachmentId}/download")
@RequirePermission(menu = "ATTACHMENT", perm = "READ")
public void download(@PathVariable Long attachmentId, HttpServletResponse response) throws IOException {
AttachmentResp meta = service.detail(attachmentId);
Path filePath = service.resolveFilePath(attachmentId);
response.setContentType(meta.getMimeType() != null ? meta.getMimeType() : MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
String encodedName = URLEncoder.encode(meta.getFileName(), StandardCharsets.UTF_8).replace("+", "%20");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedName);
response.setContentLengthLong(meta.getFileSize() != null ? meta.getFileSize() : 0L);
try (OutputStream out = response.getOutputStream()) {
Files.copy(filePath, out);
}
}
@DeleteMapping("/{attachmentId}")
@RequirePermission(menu = "ATTACHMENT", perm = "DELETE")
@DataChangeLog(menu = "ATTACHMENT", table = "attachment")
public ApiResponse<Void> delete(@PathVariable Long attachmentId) {
service.delete(attachmentId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,82 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.CollectionCommissionService;
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/collection-commissions")
@RequiredArgsConstructor
public class CollectionCommissionController {
private final CollectionCommissionService service;
// ── Rule ──────────────────────────────────────────────────────────────────
@GetMapping("/rules")
@RequirePermission(menu = "COLLECT_COMM", perm = "READ")
public ApiResponse<PageResponse<CollectionCommissionRuleResp>> listRules(
@ModelAttribute CollectionCommissionRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "COLLECT_COMM", perm = "READ")
public ApiResponse<CollectionCommissionRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "COLLECT_COMM", perm = "CREATE")
@DataChangeLog(menu = "COLLECT_COMM", table = "collection_commission_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody CollectionCommissionRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "COLLECT_COMM", perm = "UPDATE")
@DataChangeLog(menu = "COLLECT_COMM", table = "collection_commission_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody CollectionCommissionRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "COLLECT_COMM", perm = "DELETE")
@DataChangeLog(menu = "COLLECT_COMM", table = "collection_commission_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
// ── Ledger ────────────────────────────────────────────────────────────────
@GetMapping("/ledgers")
@RequirePermission(menu = "COLLECT_COMM", perm = "READ")
public ApiResponse<PageResponse<CollectionCommissionLedgerResp>> listLedgers(
@ModelAttribute CollectionCommissionLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "COLLECT_COMM", perm = "READ")
public ApiResponse<CollectionCommissionLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "COLLECT_COMM", perm = "CREATE")
@DataChangeLog(menu = "COLLECT_COMM", table = "collection_commission_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody CollectionCommissionLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,66 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.CommissionMasterService;
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.CommissionMasterResp;
import com.ga.core.vo.commission.CommissionMasterSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "수수료 마스터")
@RestController
@RequestMapping("/api/commissions")
@RequiredArgsConstructor
public class CommissionMasterController {
private final CommissionMasterService service;
@GetMapping
@RequirePermission(menu = "COMM_MASTER", perm = "READ")
public ApiResponse<PageResponse<CommissionMasterResp>> list(@ModelAttribute CommissionMasterSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{masterId}")
@RequirePermission(menu = "COMM_MASTER", perm = "READ")
public ApiResponse<CommissionMasterResp> detail(@PathVariable Long masterId) {
return ApiResponse.ok(service.detail(masterId));
}
@GetMapping("/by-agent")
@RequirePermission(menu = "COMM_MASTER", perm = "READ")
public ApiResponse<List<CommissionMasterResp>> byAgent(@RequestParam Long agentId,
@RequestParam String settleMonth) {
return ApiResponse.ok(service.listByAgentAndMonth(agentId, settleMonth));
}
@GetMapping("/aggregated")
@RequirePermission(menu = "COMM_MASTER", perm = "READ")
public ApiResponse<List<CommissionMasterResp>> aggregated(@RequestParam String settleMonth) {
return ApiResponse.ok(service.aggregatedByMonth(settleMonth));
}
@PutMapping("/{masterId}/status")
@RequirePermission(menu = "COMM_MASTER", perm = "UPDATE")
@DataChangeLog(menu = "COMM_MASTER", table = "commission_master")
public ApiResponse<Void> updateStatus(@PathVariable Long masterId,
@RequestBody Map<String, String> body) {
service.updateStatus(masterId, body.get("status"));
return ApiResponse.ok();
}
@DeleteMapping("/{masterId}")
@RequirePermission(menu = "COMM_MASTER", perm = "DELETE")
@DataChangeLog(menu = "COMM_MASTER", table = "commission_master")
public ApiResponse<Void> delete(@PathVariable Long masterId) {
service.delete(masterId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,46 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.OverrideLedgerService;
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 lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "오버라이드 원장")
@RestController
@RequestMapping("/api/override-ledgers")
@RequiredArgsConstructor
public class OverrideLedgerController {
private final OverrideLedgerService service;
@GetMapping
@RequirePermission(menu = "OVERRIDE_LEDGER", perm = "READ")
public ApiResponse<PageResponse<OverrideLedgerResp>> list(@ModelAttribute OverrideLedgerSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{ledgerId}")
@RequirePermission(menu = "OVERRIDE_LEDGER", perm = "READ")
public ApiResponse<OverrideLedgerResp> detail(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detail(ledgerId));
}
@GetMapping("/summary")
@RequirePermission(menu = "OVERRIDE_LEDGER", perm = "READ")
public ApiResponse<List<OverrideSummaryResp>> summaryList(@ModelAttribute OverrideSummarySearchParam param) {
return ApiResponse.ok(service.summaryList(param));
}
@GetMapping("/summary/by-agent")
@RequirePermission(menu = "OVERRIDE_LEDGER", perm = "READ")
public ApiResponse<OverrideSummaryResp> summaryByAgent(@RequestParam Long toAgentId,
@RequestParam String settleMonth) {
return ApiResponse.ok(service.summaryByAgentAndMonth(toAgentId, settleMonth));
}
}
@@ -0,0 +1,79 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.PersistencyBonusService;
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/persistency-bonus")
@RequiredArgsConstructor
public class PersistencyBonusController {
private final PersistencyBonusService service;
@GetMapping("/rules")
@RequirePermission(menu = "PERSIST_BONUS", perm = "READ")
public ApiResponse<PageResponse<PersistencyBonusRuleResp>> listRules(
@ModelAttribute PersistencyBonusRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "PERSIST_BONUS", perm = "READ")
public ApiResponse<PersistencyBonusRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "PERSIST_BONUS", perm = "CREATE")
@DataChangeLog(menu = "PERSIST_BONUS", table = "persistency_bonus_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody PersistencyBonusRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "PERSIST_BONUS", perm = "UPDATE")
@DataChangeLog(menu = "PERSIST_BONUS", table = "persistency_bonus_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody PersistencyBonusRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "PERSIST_BONUS", perm = "DELETE")
@DataChangeLog(menu = "PERSIST_BONUS", table = "persistency_bonus_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
@GetMapping("/bonuses")
@RequirePermission(menu = "PERSIST_BONUS", perm = "READ")
public ApiResponse<PageResponse<PersistencyBonusResp>> listBonuses(
@ModelAttribute PersistencyBonusSearchParam param) {
return ApiResponse.ok(service.listBonuses(param));
}
@GetMapping("/bonuses/{bonusId}")
@RequirePermission(menu = "PERSIST_BONUS", perm = "READ")
public ApiResponse<PersistencyBonusResp> detailBonus(@PathVariable Long bonusId) {
return ApiResponse.ok(service.detailBonus(bonusId));
}
@PostMapping("/bonuses")
@RequirePermission(menu = "PERSIST_BONUS", perm = "CREATE")
@DataChangeLog(menu = "PERSIST_BONUS", table = "persistency_bonus")
public ApiResponse<Void> upsertBonus(@Valid @RequestBody PersistencyBonusSaveReq req) {
service.upsertBonus(req);
return ApiResponse.ok();
}
}
@@ -0,0 +1,78 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.ReinstatementCommissionService;
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/reinstatement-commissions")
@RequiredArgsConstructor
public class ReinstatementCommissionController {
private final ReinstatementCommissionService service;
@GetMapping("/rules")
@RequirePermission(menu = "REINSTATE_COMM", perm = "READ")
public ApiResponse<PageResponse<ReinstatementCommissionRuleResp>> listRules(
@ModelAttribute ReinstatementCommissionRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "REINSTATE_COMM", perm = "READ")
public ApiResponse<ReinstatementCommissionRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "REINSTATE_COMM", perm = "CREATE")
@DataChangeLog(menu = "REINSTATE_COMM", table = "reinstatement_commission_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody ReinstatementCommissionRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "REINSTATE_COMM", perm = "UPDATE")
@DataChangeLog(menu = "REINSTATE_COMM", table = "reinstatement_commission_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody ReinstatementCommissionRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "REINSTATE_COMM", perm = "DELETE")
@DataChangeLog(menu = "REINSTATE_COMM", table = "reinstatement_commission_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
@GetMapping("/ledgers")
@RequirePermission(menu = "REINSTATE_COMM", perm = "READ")
public ApiResponse<PageResponse<ReinstatementCommissionLedgerResp>> listLedgers(
@ModelAttribute ReinstatementCommissionLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "REINSTATE_COMM", perm = "READ")
public ApiResponse<ReinstatementCommissionLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "REINSTATE_COMM", perm = "CREATE")
@DataChangeLog(menu = "REINSTATE_COMM", table = "reinstatement_commission_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody ReinstatementCommissionLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,78 @@
package com.ga.api.controller.commission;
import com.ga.api.service.commission.RenewalCommissionService;
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/renewal-commissions")
@RequiredArgsConstructor
public class RenewalCommissionController {
private final RenewalCommissionService service;
@GetMapping("/rules")
@RequirePermission(menu = "RENEWAL_COMM", perm = "READ")
public ApiResponse<PageResponse<RenewalCommissionRuleResp>> listRules(
@ModelAttribute RenewalCommissionRuleSearchParam param) {
return ApiResponse.ok(service.listRules(param));
}
@GetMapping("/rules/{ruleId}")
@RequirePermission(menu = "RENEWAL_COMM", perm = "READ")
public ApiResponse<RenewalCommissionRuleResp> detailRule(@PathVariable Long ruleId) {
return ApiResponse.ok(service.detailRule(ruleId));
}
@PostMapping("/rules")
@RequirePermission(menu = "RENEWAL_COMM", perm = "CREATE")
@DataChangeLog(menu = "RENEWAL_COMM", table = "renewal_commission_rule")
public ApiResponse<Long> createRule(@Valid @RequestBody RenewalCommissionRuleSaveReq req) {
return ApiResponse.ok(service.createRule(req));
}
@PutMapping("/rules/{ruleId}")
@RequirePermission(menu = "RENEWAL_COMM", perm = "UPDATE")
@DataChangeLog(menu = "RENEWAL_COMM", table = "renewal_commission_rule")
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
@Valid @RequestBody RenewalCommissionRuleSaveReq req) {
service.updateRule(ruleId, req);
return ApiResponse.ok();
}
@DeleteMapping("/rules/{ruleId}")
@RequirePermission(menu = "RENEWAL_COMM", perm = "DELETE")
@DataChangeLog(menu = "RENEWAL_COMM", table = "renewal_commission_rule")
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
service.deleteRule(ruleId);
return ApiResponse.ok();
}
@GetMapping("/ledgers")
@RequirePermission(menu = "RENEWAL_COMM", perm = "READ")
public ApiResponse<PageResponse<RenewalCommissionLedgerResp>> listLedgers(
@ModelAttribute RenewalCommissionLedgerSearchParam param) {
return ApiResponse.ok(service.listLedgers(param));
}
@GetMapping("/ledgers/{ledgerId}")
@RequirePermission(menu = "RENEWAL_COMM", perm = "READ")
public ApiResponse<RenewalCommissionLedgerResp> detailLedger(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailLedger(ledgerId));
}
@PostMapping("/ledgers")
@RequirePermission(menu = "RENEWAL_COMM", perm = "CREATE")
@DataChangeLog(menu = "RENEWAL_COMM", table = "renewal_commission_ledger")
public ApiResponse<Long> createLedger(@Valid @RequestBody RenewalCommissionLedgerSaveReq req) {
return ApiResponse.ok(service.createLedger(req));
}
}
@@ -0,0 +1,70 @@
package com.ga.api.controller.complaint;
import com.ga.api.service.complaint.ComplaintService;
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.complaint.ComplaintResp;
import com.ga.core.vo.complaint.ComplaintSaveReq;
import com.ga.core.vo.complaint.ComplaintSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "민원 관리")
@RestController
@RequestMapping("/api/complaints")
@RequiredArgsConstructor
public class ComplaintController {
private final ComplaintService service;
@GetMapping
@RequirePermission(menu = "COMPLAINT", perm = "READ")
public ApiResponse<PageResponse<ComplaintResp>> list(@ModelAttribute ComplaintSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{complaintId}")
@RequirePermission(menu = "COMPLAINT", perm = "READ")
public ApiResponse<ComplaintResp> detail(@PathVariable Long complaintId) {
return ApiResponse.ok(service.detail(complaintId));
}
@PostMapping
@RequirePermission(menu = "COMPLAINT", perm = "CREATE")
@DataChangeLog(menu = "COMPLAINT", table = "complaint")
public ApiResponse<Long> create(@Valid @RequestBody ComplaintSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{complaintId}")
@RequirePermission(menu = "COMPLAINT", perm = "UPDATE")
@DataChangeLog(menu = "COMPLAINT", table = "complaint")
public ApiResponse<Void> update(@PathVariable Long complaintId,
@Valid @RequestBody ComplaintSaveReq req) {
service.update(complaintId, req);
return ApiResponse.ok();
}
@PostMapping("/{complaintId}/close")
@RequirePermission(menu = "COMPLAINT", perm = "UPDATE")
@DataChangeLog(menu = "COMPLAINT", table = "complaint")
public ApiResponse<Void> close(@PathVariable Long complaintId,
@RequestBody Map<String, String> body) {
service.close(complaintId, body.get("resolution"));
return ApiResponse.ok();
}
@DeleteMapping("/{complaintId}")
@RequirePermission(menu = "COMPLAINT", perm = "DELETE")
@DataChangeLog(menu = "COMPLAINT", table = "complaint")
public ApiResponse<Void> delete(@PathVariable Long complaintId) {
service.delete(complaintId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,70 @@
package com.ga.api.controller.contract;
import com.ga.api.service.contract.AgentContractService;
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.contract.AgentContractResp;
import com.ga.core.vo.contract.AgentContractSaveReq;
import com.ga.core.vo.contract.AgentContractSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "위촉계약 관리")
@RestController
@RequestMapping("/api/agent-contracts")
@RequiredArgsConstructor
public class AgentContractController {
private final AgentContractService service;
@GetMapping
@RequirePermission(menu = "AGENT_CONTRACT", perm = "READ")
public ApiResponse<PageResponse<AgentContractResp>> list(@ModelAttribute AgentContractSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{contractId}")
@RequirePermission(menu = "AGENT_CONTRACT", perm = "READ")
public ApiResponse<AgentContractResp> detail(@PathVariable Long contractId) {
return ApiResponse.ok(service.detail(contractId));
}
@PostMapping
@RequirePermission(menu = "AGENT_CONTRACT", perm = "CREATE")
@DataChangeLog(menu = "AGENT_CONTRACT", table = "agent_contract")
public ApiResponse<Long> create(@Valid @RequestBody AgentContractSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{contractId}")
@RequirePermission(menu = "AGENT_CONTRACT", perm = "UPDATE")
@DataChangeLog(menu = "AGENT_CONTRACT", table = "agent_contract")
public ApiResponse<Void> update(@PathVariable Long contractId,
@Valid @RequestBody AgentContractSaveReq req) {
service.update(contractId, req);
return ApiResponse.ok();
}
@PostMapping("/{contractId}/terminate")
@RequirePermission(menu = "AGENT_CONTRACT", perm = "UPDATE")
@DataChangeLog(menu = "AGENT_CONTRACT", table = "agent_contract")
public ApiResponse<Void> terminate(@PathVariable Long contractId,
@RequestBody Map<String, String> body) {
service.terminate(contractId, body.get("reason"));
return ApiResponse.ok();
}
@DeleteMapping("/{contractId}")
@RequirePermission(menu = "AGENT_CONTRACT", perm = "DELETE")
@DataChangeLog(menu = "AGENT_CONTRACT", table = "agent_contract")
public ApiResponse<Void> delete(@PathVariable Long contractId) {
service.delete(contractId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,59 @@
package com.ga.api.controller.endorse;
import com.ga.api.service.endorse.ContractEndorsementService;
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.endorse.ContractEndorsementResp;
import com.ga.core.vo.endorse.ContractEndorsementSaveReq;
import com.ga.core.vo.endorse.ContractEndorsementSearchParam;
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/contract-endorsements")
@RequiredArgsConstructor
public class ContractEndorsementController {
private final ContractEndorsementService service;
@GetMapping
@RequirePermission(menu = "ENDORSE", perm = "READ")
public ApiResponse<PageResponse<ContractEndorsementResp>> list(
@ModelAttribute ContractEndorsementSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{endorsementId}")
@RequirePermission(menu = "ENDORSE", perm = "READ")
public ApiResponse<ContractEndorsementResp> detail(@PathVariable Long endorsementId) {
return ApiResponse.ok(service.detail(endorsementId));
}
@PostMapping
@RequirePermission(menu = "ENDORSE", perm = "CREATE")
@DataChangeLog(menu = "ENDORSE", table = "contract_endorsement")
public ApiResponse<Long> create(@Valid @RequestBody ContractEndorsementSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{endorsementId}/process")
@RequirePermission(menu = "ENDORSE", perm = "UPDATE")
@DataChangeLog(menu = "ENDORSE", table = "contract_endorsement")
public ApiResponse<Void> process(@PathVariable Long endorsementId) {
service.process(endorsementId);
return ApiResponse.ok();
}
@DeleteMapping("/{endorsementId}")
@RequirePermission(menu = "ENDORSE", perm = "DELETE")
@DataChangeLog(menu = "ENDORSE", table = "contract_endorsement")
public ApiResponse<Void> delete(@PathVariable Long endorsementId) {
service.delete(endorsementId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,50 @@
package com.ga.api.controller.kpi;
import com.ga.api.service.kpi.KpiService;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.kpi.KpiCumulativeResp;
import com.ga.core.vo.kpi.KpiMonthlySummaryResp;
import com.ga.core.vo.kpi.KpiOrgSummaryResp;
import com.ga.core.vo.kpi.KpiRetentionResp;
import com.ga.core.vo.kpi.KpiSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "KPI 대시보드")
@RestController
@RequestMapping("/api/kpi")
@RequiredArgsConstructor
public class KpiController {
private final KpiService service;
@GetMapping("/monthly-summary")
@RequirePermission(menu = "KPI_MONTHLY", perm = "READ")
public ApiResponse<PageResponse<KpiMonthlySummaryResp>> monthlySummary(@ModelAttribute KpiSearchParam param) {
return ApiResponse.ok(service.monthlySummary(param));
}
@GetMapping("/org-summary")
@RequirePermission(menu = "KPI_ORG", perm = "READ")
public ApiResponse<PageResponse<KpiOrgSummaryResp>> orgSummary(@ModelAttribute KpiSearchParam param) {
return ApiResponse.ok(service.orgSummary(param));
}
@GetMapping("/retention")
@RequirePermission(menu = "KPI_RETENTION", perm = "READ")
public ApiResponse<PageResponse<KpiRetentionResp>> retention(@ModelAttribute KpiSearchParam param) {
return ApiResponse.ok(service.retention(param));
}
@GetMapping("/cumulative")
@RequirePermission(menu = "KPI_CUMULATIVE", perm = "READ")
public ApiResponse<PageResponse<KpiCumulativeResp>> cumulative(@ModelAttribute KpiSearchParam param) {
return ApiResponse.ok(service.cumulative(param));
}
}
@@ -0,0 +1,68 @@
package com.ga.api.controller.license;
import com.ga.api.service.license.AgentLicenseService;
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.license.AgentLicenseResp;
import com.ga.core.vo.license.AgentLicenseSaveReq;
import com.ga.core.vo.license.AgentLicenseSearchParam;
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/agent-licenses")
@RequiredArgsConstructor
public class AgentLicenseController {
private final AgentLicenseService service;
@GetMapping
@RequirePermission(menu = "AGENT_LICENSE", perm = "READ")
public ApiResponse<PageResponse<AgentLicenseResp>> list(@ModelAttribute AgentLicenseSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{licenseId}")
@RequirePermission(menu = "AGENT_LICENSE", perm = "READ")
public ApiResponse<AgentLicenseResp> detail(@PathVariable Long licenseId) {
return ApiResponse.ok(service.detail(licenseId));
}
@GetMapping("/expiring")
@RequirePermission(menu = "AGENT_LICENSE", perm = "READ")
public ApiResponse<List<AgentLicenseResp>> expiring(
@RequestParam(defaultValue = "30") int days) {
return ApiResponse.ok(service.expiring(days));
}
@PostMapping
@RequirePermission(menu = "AGENT_LICENSE", perm = "CREATE")
@DataChangeLog(menu = "AGENT_LICENSE", table = "agent_license")
public ApiResponse<Long> create(@Valid @RequestBody AgentLicenseSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{licenseId}")
@RequirePermission(menu = "AGENT_LICENSE", perm = "UPDATE")
@DataChangeLog(menu = "AGENT_LICENSE", table = "agent_license")
public ApiResponse<Void> update(@PathVariable Long licenseId,
@Valid @RequestBody AgentLicenseSaveReq req) {
service.update(licenseId, req);
return ApiResponse.ok();
}
@DeleteMapping("/{licenseId}")
@RequirePermission(menu = "AGENT_LICENSE", perm = "DELETE")
@DataChangeLog(menu = "AGENT_LICENSE", table = "agent_license")
public ApiResponse<Void> delete(@PathVariable Long licenseId) {
service.delete(licenseId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,97 @@
package com.ga.api.controller.notice;
import com.ga.api.service.notice.NoticeService;
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.common.util.SecurityUtil;
import com.ga.core.vo.notice.*;
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
@RequiredArgsConstructor
public class NoticeController {
private final NoticeService service;
// ── Notice ────────────────────────────────────────────────────────────────
@GetMapping("/api/notices")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<PageResponse<NoticeResp>> list(@ModelAttribute NoticeSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/api/notices/active")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<List<NoticeResp>> activeList() {
return ApiResponse.ok(service.activeList());
}
@GetMapping("/api/notices/{noticeId}")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<NoticeResp> detail(@PathVariable Long noticeId) {
return ApiResponse.ok(service.detail(noticeId));
}
@PostMapping("/api/notices")
@RequirePermission(menu = "NOTICE", perm = "CREATE")
@DataChangeLog(menu = "NOTICE", table = "notice")
public ApiResponse<Long> create(@Valid @RequestBody NoticeSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/api/notices/{noticeId}")
@RequirePermission(menu = "NOTICE", perm = "UPDATE")
@DataChangeLog(menu = "NOTICE", table = "notice")
public ApiResponse<Void> update(@PathVariable Long noticeId,
@Valid @RequestBody NoticeSaveReq req) {
service.update(noticeId, req);
return ApiResponse.ok();
}
@DeleteMapping("/api/notices/{noticeId}")
@RequirePermission(menu = "NOTICE", perm = "DELETE")
@DataChangeLog(menu = "NOTICE", table = "notice")
public ApiResponse<Void> delete(@PathVariable Long noticeId) {
service.delete(noticeId);
return ApiResponse.ok();
}
// ── UserNotification ──────────────────────────────────────────────────────
@GetMapping("/api/user-notifications")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<PageResponse<UserNotificationResp>> listNotifications(
@ModelAttribute UserNotificationSearchParam param) {
return ApiResponse.ok(service.listNotifications(param));
}
@GetMapping("/api/user-notifications/unread-count")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<Integer> unreadCount(@RequestParam Long userId) {
return ApiResponse.ok(service.unreadCount(userId));
}
@PostMapping("/api/user-notifications/{notificationId}/read")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<Void> markRead(@PathVariable Long notificationId,
@RequestParam Long userId) {
service.markRead(notificationId, userId);
return ApiResponse.ok();
}
@PostMapping("/api/user-notifications/read-all")
@RequirePermission(menu = "NOTICE", perm = "READ")
public ApiResponse<Void> markAllRead(@RequestParam Long userId) {
service.markAllRead(userId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,50 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.CarrierReconciliationOutService;
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.settle.CarrierReconciliationOutResp;
import com.ga.core.vo.settle.CarrierReconciliationOutSaveReq;
import com.ga.core.vo.settle.CarrierReconciliationOutSearchParam;
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/carrier-reconciliation")
@RequiredArgsConstructor
public class CarrierReconciliationOutController {
private final CarrierReconciliationOutService service;
@GetMapping
@RequirePermission(menu = "RECON_OUT", perm = "READ")
public ApiResponse<PageResponse<CarrierReconciliationOutResp>> list(@ModelAttribute CarrierReconciliationOutSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{reconOutId}")
@RequirePermission(menu = "RECON_OUT", perm = "READ")
public ApiResponse<CarrierReconciliationOutResp> detail(@PathVariable Long reconOutId) {
return ApiResponse.ok(service.detail(reconOutId));
}
@PostMapping
@RequirePermission(menu = "RECON_OUT", perm = "CREATE")
@DataChangeLog(menu = "RECON_OUT", table = "carrier_reconciliation_out")
public ApiResponse<Long> create(@Valid @RequestBody CarrierReconciliationOutSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{reconOutId}/retry")
@RequirePermission(menu = "RECON_OUT", perm = "UPDATE")
@DataChangeLog(menu = "RECON_OUT", table = "carrier_reconciliation_out")
public ApiResponse<Void> retry(@PathVariable Long reconOutId) {
service.retry(reconOutId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,74 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.CommissionDisputeService;
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.settle.CommissionDisputeResp;
import com.ga.core.vo.settle.CommissionDisputeSaveReq;
import com.ga.core.vo.settle.CommissionDisputeSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.Map;
@Tag(name = "수수료 이의신청 관리")
@RestController
@RequestMapping("/api/disputes")
@RequiredArgsConstructor
public class CommissionDisputeController {
private final CommissionDisputeService service;
@GetMapping
@RequirePermission(menu = "DISPUTE", perm = "READ")
public ApiResponse<PageResponse<CommissionDisputeResp>> list(@ModelAttribute CommissionDisputeSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{disputeId}")
@RequirePermission(menu = "DISPUTE", perm = "READ")
public ApiResponse<CommissionDisputeResp> detail(@PathVariable Long disputeId) {
return ApiResponse.ok(service.detail(disputeId));
}
@PostMapping
@RequirePermission(menu = "DISPUTE", perm = "CREATE")
@DataChangeLog(menu = "DISPUTE", table = "commission_dispute")
public ApiResponse<Long> create(@Valid @RequestBody CommissionDisputeSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{disputeId}/review")
@RequirePermission(menu = "DISPUTE", perm = "UPDATE")
@DataChangeLog(menu = "DISPUTE", table = "commission_dispute")
public ApiResponse<Void> startReview(@PathVariable Long disputeId) {
service.startReview(disputeId);
return ApiResponse.ok();
}
@PostMapping("/{disputeId}/approve")
@RequirePermission(menu = "DISPUTE", perm = "APPROVE")
@DataChangeLog(menu = "DISPUTE", table = "commission_dispute")
public ApiResponse<Void> approve(@PathVariable Long disputeId, @RequestBody Map<String, Object> body) {
String resolution = (String) body.get("resolution");
BigDecimal adjustmentAmount = body.get("adjustmentAmount") != null
? new BigDecimal(body.get("adjustmentAmount").toString()) : null;
Long correctionId = body.get("correctionId") != null
? Long.valueOf(body.get("correctionId").toString()) : null;
service.approve(disputeId, resolution, adjustmentAmount, correctionId);
return ApiResponse.ok();
}
@PostMapping("/{disputeId}/reject")
@RequirePermission(menu = "DISPUTE", perm = "APPROVE")
@DataChangeLog(menu = "DISPUTE", table = "commission_dispute")
public ApiResponse<Void> reject(@PathVariable Long disputeId, @RequestBody Map<String, String> body) {
service.reject(disputeId, body.get("resolution"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,60 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.IncentivePaymentService;
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.settle.IncentivePaymentResp;
import com.ga.core.vo.settle.IncentivePaymentSaveReq;
import com.ga.core.vo.settle.IncentivePaymentSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "시책 지급 관리")
@RestController
@RequestMapping("/api/incentive-payments")
@RequiredArgsConstructor
public class IncentivePaymentController {
private final IncentivePaymentService service;
@GetMapping
@RequirePermission(menu = "INCENTIVE_PAY", perm = "READ")
public ApiResponse<PageResponse<IncentivePaymentResp>> list(@ModelAttribute IncentivePaymentSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{incentivePayId}")
@RequirePermission(menu = "INCENTIVE_PAY", perm = "READ")
public ApiResponse<IncentivePaymentResp> detail(@PathVariable Long incentivePayId) {
return ApiResponse.ok(service.detail(incentivePayId));
}
@PostMapping
@RequirePermission(menu = "INCENTIVE_PAY", perm = "CREATE")
@DataChangeLog(menu = "INCENTIVE_PAY", table = "incentive_payment")
public ApiResponse<Long> create(@Valid @RequestBody IncentivePaymentSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{incentivePayId}/confirm")
@RequirePermission(menu = "INCENTIVE_PAY", perm = "APPROVE")
@DataChangeLog(menu = "INCENTIVE_PAY", table = "incentive_payment")
public ApiResponse<Void> confirm(@PathVariable Long incentivePayId) {
service.confirm(incentivePayId);
return ApiResponse.ok();
}
@PostMapping("/{incentivePayId}/cancel")
@RequirePermission(menu = "INCENTIVE_PAY", perm = "UPDATE")
@DataChangeLog(menu = "INCENTIVE_PAY", table = "incentive_payment")
public ApiResponse<Void> cancel(@PathVariable Long incentivePayId, @RequestBody Map<String, String> body) {
service.cancel(incentivePayId, body.get("cancelReason"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,60 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.IncentiveProgramService;
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.settle.IncentiveProgramResp;
import com.ga.core.vo.settle.IncentiveProgramSaveReq;
import com.ga.core.vo.settle.IncentiveProgramSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "시책 프로그램 관리")
@RestController
@RequestMapping("/api/incentive-programs")
@RequiredArgsConstructor
public class IncentiveProgramController {
private final IncentiveProgramService service;
@GetMapping
@RequirePermission(menu = "INCENTIVE_PROG", perm = "READ")
public ApiResponse<PageResponse<IncentiveProgramResp>> list(@ModelAttribute IncentiveProgramSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{programId}")
@RequirePermission(menu = "INCENTIVE_PROG", perm = "READ")
public ApiResponse<IncentiveProgramResp> detail(@PathVariable Long programId) {
return ApiResponse.ok(service.detail(programId));
}
@PostMapping
@RequirePermission(menu = "INCENTIVE_PROG", perm = "CREATE")
@DataChangeLog(menu = "INCENTIVE_PROG", table = "incentive_program")
public ApiResponse<Long> create(@Valid @RequestBody IncentiveProgramSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{programId}")
@RequirePermission(menu = "INCENTIVE_PROG", perm = "UPDATE")
@DataChangeLog(menu = "INCENTIVE_PROG", table = "incentive_program")
public ApiResponse<Void> update(@PathVariable Long programId, @Valid @RequestBody IncentiveProgramSaveReq req) {
service.update(programId, req);
return ApiResponse.ok();
}
@PutMapping("/{programId}/active")
@RequirePermission(menu = "INCENTIVE_PROG", perm = "UPDATE")
@DataChangeLog(menu = "INCENTIVE_PROG", table = "incentive_program")
public ApiResponse<Void> toggleActive(@PathVariable Long programId, @RequestBody Map<String, Boolean> body) {
service.toggleActive(programId, Boolean.TRUE.equals(body.get("isActive")));
return ApiResponse.ok();
}
}
@@ -0,0 +1,53 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.PeriodCloseService;
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.settle.PeriodCloseResp;
import com.ga.core.vo.settle.PeriodCloseSaveReq;
import com.ga.core.vo.settle.PeriodCloseSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "정산 마감 관리")
@RestController
@RequestMapping("/api/period-close")
@RequiredArgsConstructor
public class PeriodCloseController {
private final PeriodCloseService service;
@GetMapping
@RequirePermission(menu = "PERIOD_CLOSE", perm = "READ")
public ApiResponse<PageResponse<PeriodCloseResp>> list(@ModelAttribute PeriodCloseSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{closeId}")
@RequirePermission(menu = "PERIOD_CLOSE", perm = "READ")
public ApiResponse<PeriodCloseResp> detail(@PathVariable Long closeId) {
return ApiResponse.ok(service.detail(closeId));
}
@PostMapping
@RequirePermission(menu = "PERIOD_CLOSE", perm = "CREATE")
@DataChangeLog(menu = "PERIOD_CLOSE", table = "period_close")
public ApiResponse<Void> close(@Valid @RequestBody PeriodCloseSaveReq req) {
service.close(req);
return ApiResponse.ok();
}
@PostMapping("/{closeId}/reopen")
@RequirePermission(menu = "PERIOD_CLOSE", perm = "UPDATE")
@DataChangeLog(menu = "PERIOD_CLOSE", table = "period_close")
public ApiResponse<Void> reopen(@PathVariable Long closeId, @RequestBody Map<String, String> body) {
service.reopen(closeId, body.get("reopenReason"));
return ApiResponse.ok();
}
}
@@ -59,7 +59,7 @@ public class SettleController {
@RequirePermission(menu = "SETTLE_LIST", perm = "APPROVE")
@DataChangeLog(menu = "SETTLE_LIST", table = "settle_master")
public ApiResponse<Void> hold(@PathVariable Long settleId, @RequestBody Map<String, String> body) {
service.hold(settleId, body.get("reason"));
service.hold(settleId, body.get("reason"), body.get("reasonCode"));
return ApiResponse.ok();
}
@@ -0,0 +1,67 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.SettleCorrectionService;
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.settle.SettleCorrectionResp;
import com.ga.core.vo.settle.SettleCorrectionSaveReq;
import com.ga.core.vo.settle.SettleCorrectionSearchParam;
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;
import java.util.Map;
@Tag(name = "정산 정정 관리")
@RestController
@RequestMapping("/api/settle-corrections")
@RequiredArgsConstructor
public class SettleCorrectionController {
private final SettleCorrectionService service;
@GetMapping
@RequirePermission(menu = "SETTLE_CORR", perm = "READ")
public ApiResponse<PageResponse<SettleCorrectionResp>> list(@ModelAttribute SettleCorrectionSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{correctionId}")
@RequirePermission(menu = "SETTLE_CORR", perm = "READ")
public ApiResponse<SettleCorrectionResp> detail(@PathVariable Long correctionId) {
return ApiResponse.ok(service.detail(correctionId));
}
@GetMapping("/by-settle/{originalSettleId}")
@RequirePermission(menu = "SETTLE_CORR", perm = "READ")
public ApiResponse<List<SettleCorrectionResp>> listByOriginalSettle(@PathVariable Long originalSettleId) {
return ApiResponse.ok(service.listByOriginalSettle(originalSettleId));
}
@PostMapping
@RequirePermission(menu = "SETTLE_CORR", perm = "CREATE")
@DataChangeLog(menu = "SETTLE_CORR", table = "settle_correction")
public ApiResponse<Long> create(@Valid @RequestBody SettleCorrectionSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{correctionId}/approve")
@RequirePermission(menu = "SETTLE_CORR", perm = "APPROVE")
@DataChangeLog(menu = "SETTLE_CORR", table = "settle_correction")
public ApiResponse<Void> approve(@PathVariable Long correctionId) {
service.approve(correctionId);
return ApiResponse.ok();
}
@PostMapping("/{correctionId}/cancel")
@RequirePermission(menu = "SETTLE_CORR", perm = "UPDATE")
@DataChangeLog(menu = "SETTLE_CORR", table = "settle_correction")
public ApiResponse<Void> cancel(@PathVariable Long correctionId, @RequestBody Map<String, String> body) {
service.cancel(correctionId, body.get("cancelReason"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,52 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.TaxInvoiceService;
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.settle.TaxInvoiceResp;
import com.ga.core.vo.settle.TaxInvoiceSaveReq;
import com.ga.core.vo.settle.TaxInvoiceSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "세금계산서 관리")
@RestController
@RequestMapping("/api/tax-invoices")
@RequiredArgsConstructor
public class TaxInvoiceController {
private final TaxInvoiceService service;
@GetMapping
@RequirePermission(menu = "TAX_INVOICE", perm = "READ")
public ApiResponse<PageResponse<TaxInvoiceResp>> list(@ModelAttribute TaxInvoiceSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{invoiceId}")
@RequirePermission(menu = "TAX_INVOICE", perm = "READ")
public ApiResponse<TaxInvoiceResp> detail(@PathVariable Long invoiceId) {
return ApiResponse.ok(service.detail(invoiceId));
}
@PostMapping
@RequirePermission(menu = "TAX_INVOICE", perm = "CREATE")
@DataChangeLog(menu = "TAX_INVOICE", table = "tax_invoice")
public ApiResponse<Long> create(@Valid @RequestBody TaxInvoiceSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{invoiceId}/cancel")
@RequirePermission(menu = "TAX_INVOICE", perm = "UPDATE")
@DataChangeLog(menu = "TAX_INVOICE", table = "tax_invoice")
public ApiResponse<Void> cancel(@PathVariable Long invoiceId, @RequestBody Map<String, String> body) {
service.cancel(invoiceId, body.get("cancelReason"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,65 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.TerminationSettlementService;
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.settle.TerminationSettlementResp;
import com.ga.core.vo.settle.TerminationSettlementSaveReq;
import com.ga.core.vo.settle.TerminationSettlementSearchParam;
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/termination-settlements")
@RequiredArgsConstructor
public class TerminationSettlementController {
private final TerminationSettlementService service;
@GetMapping
@RequirePermission(menu = "TERM_SETTLE", perm = "READ")
public ApiResponse<PageResponse<TerminationSettlementResp>> list(@ModelAttribute TerminationSettlementSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{termSettleId}")
@RequirePermission(menu = "TERM_SETTLE", perm = "READ")
public ApiResponse<TerminationSettlementResp> detail(@PathVariable Long termSettleId) {
return ApiResponse.ok(service.detail(termSettleId));
}
@PostMapping
@RequirePermission(menu = "TERM_SETTLE", perm = "CREATE")
@DataChangeLog(menu = "TERM_SETTLE", table = "termination_settlement")
public ApiResponse<Long> create(@Valid @RequestBody TerminationSettlementSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{termSettleId}")
@RequirePermission(menu = "TERM_SETTLE", perm = "UPDATE")
@DataChangeLog(menu = "TERM_SETTLE", table = "termination_settlement")
public ApiResponse<Void> update(@PathVariable Long termSettleId, @Valid @RequestBody TerminationSettlementSaveReq req) {
service.update(termSettleId, req);
return ApiResponse.ok();
}
@PostMapping("/process")
@RequirePermission(menu = "TERM_SETTLE", perm = "CREATE")
@DataChangeLog(menu = "TERM_SETTLE", table = "termination_settlement")
public ApiResponse<Long> process(@Valid @RequestBody TerminationSettlementSaveReq req) {
return ApiResponse.ok(service.process(req));
}
@PostMapping("/{termSettleId}/confirm")
@RequirePermission(menu = "TERM_SETTLE", perm = "APPROVE")
@DataChangeLog(menu = "TERM_SETTLE", table = "termination_settlement")
public ApiResponse<Void> confirm(@PathVariable Long termSettleId) {
service.confirm(termSettleId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,91 @@
package com.ga.api.controller.training;
import com.ga.api.service.training.AgentTrainingService;
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.training.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
@Tag(name = "보수교육 관리")
@RestController
@RequiredArgsConstructor
public class AgentTrainingController {
private final AgentTrainingService service;
// ── Training ──────────────────────────────────────────────────────────────
@GetMapping("/api/agent-trainings")
@RequirePermission(menu = "AGENT_TRAINING", perm = "READ")
public ApiResponse<PageResponse<AgentTrainingResp>> list(@ModelAttribute AgentTrainingSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/api/agent-trainings/{trainingId}")
@RequirePermission(menu = "AGENT_TRAINING", perm = "READ")
public ApiResponse<AgentTrainingResp> detail(@PathVariable Long trainingId) {
return ApiResponse.ok(service.detail(trainingId));
}
@GetMapping("/api/agent-trainings/annual-hours")
@RequirePermission(menu = "AGENT_TRAINING", perm = "READ")
public ApiResponse<BigDecimal> annualHours(@RequestParam Long agentId,
@RequestParam int year) {
return ApiResponse.ok(service.annualHours(agentId, year));
}
@PostMapping("/api/agent-trainings")
@RequirePermission(menu = "AGENT_TRAINING", perm = "CREATE")
@DataChangeLog(menu = "AGENT_TRAINING", table = "agent_training")
public ApiResponse<Long> create(@Valid @RequestBody AgentTrainingSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/api/agent-trainings/{trainingId}")
@RequirePermission(menu = "AGENT_TRAINING", perm = "UPDATE")
@DataChangeLog(menu = "AGENT_TRAINING", table = "agent_training")
public ApiResponse<Void> update(@PathVariable Long trainingId,
@Valid @RequestBody AgentTrainingSaveReq req) {
service.update(trainingId, req);
return ApiResponse.ok();
}
@DeleteMapping("/api/agent-trainings/{trainingId}")
@RequirePermission(menu = "AGENT_TRAINING", perm = "DELETE")
@DataChangeLog(menu = "AGENT_TRAINING", table = "agent_training")
public ApiResponse<Void> delete(@PathVariable Long trainingId) {
service.delete(trainingId);
return ApiResponse.ok();
}
// ── Requirement ───────────────────────────────────────────────────────────
@GetMapping("/api/training-requirements")
@RequirePermission(menu = "AGENT_TRAINING", perm = "READ")
public ApiResponse<List<TrainingRequirementResp>> listRequirements(
@ModelAttribute TrainingRequirementSearchParam param) {
return ApiResponse.ok(service.listRequirements(param));
}
@GetMapping("/api/training-requirements/{year}")
@RequirePermission(menu = "AGENT_TRAINING", perm = "READ")
public ApiResponse<TrainingRequirementResp> requirementByYear(@PathVariable int year) {
return ApiResponse.ok(service.requirementByYear(year));
}
@PostMapping("/api/training-requirements")
@RequirePermission(menu = "AGENT_TRAINING", perm = "UPDATE")
@DataChangeLog(menu = "AGENT_TRAINING", table = "training_requirement")
public ApiResponse<Void> saveRequirement(@Valid @RequestBody TrainingRequirementSaveReq req) {
service.saveRequirement(req);
return ApiResponse.ok();
}
}
@@ -0,0 +1,50 @@
package com.ga.api.controller.withdraw;
import com.ga.api.service.withdraw.WithdrawRequestService;
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.withdraw.WithdrawRequestResp;
import com.ga.core.vo.withdraw.WithdrawRequestSaveReq;
import com.ga.core.vo.withdraw.WithdrawRequestSearchParam;
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/withdraw-requests")
@RequiredArgsConstructor
public class WithdrawRequestController {
private final WithdrawRequestService service;
@GetMapping
@RequirePermission(menu = "WITHDRAW", perm = "READ")
public ApiResponse<PageResponse<WithdrawRequestResp>> list(@ModelAttribute WithdrawRequestSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{requestId}")
@RequirePermission(menu = "WITHDRAW", perm = "READ")
public ApiResponse<WithdrawRequestResp> detail(@PathVariable Long requestId) {
return ApiResponse.ok(service.detail(requestId));
}
@PostMapping
@RequirePermission(menu = "WITHDRAW", perm = "CREATE")
@DataChangeLog(menu = "WITHDRAW", table = "withdraw_request")
public ApiResponse<Long> create(@Valid @RequestBody WithdrawRequestSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PostMapping("/{requestId}/cancel")
@RequirePermission(menu = "WITHDRAW", perm = "UPDATE")
@DataChangeLog(menu = "WITHDRAW", table = "withdraw_request")
public ApiResponse<Void> cancel(@PathVariable Long requestId) {
service.cancel(requestId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,259 @@
package com.ga.api.service.approval;
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.approval.*;
import com.ga.core.mapper.notice.UserNotificationMapper;
import com.ga.core.mapper.settle.CommissionDisputeMapper;
import com.ga.core.mapper.settle.IncentivePaymentMapper;
import com.ga.core.mapper.settle.SettleMasterMapper;
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
import com.ga.core.vo.approval.*;
import com.ga.core.vo.notice.NotificationRefType;
// Note: ApprovalLineVO kept for createLine/updateLine methods
import com.ga.core.vo.notice.UserNotificationVO;
import com.ga.core.vo.settle.SettleStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ApprovalService {
private final ApprovalLineMapper lineMapper;
private final ApprovalLineStepMapper stepMapper;
private final ApprovalRequestMapper requestMapper;
private final ApprovalHistoryMapper historyMapper;
private final UserNotificationMapper notificationMapper;
// 콜백 대상 도메인 Mapper
private final WithdrawRequestMapper withdrawMapper;
private final SettleMasterMapper settleMasterMapper;
private final CommissionDisputeMapper disputeMapper;
private final IncentivePaymentMapper incentivePaymentMapper;
// ── Line ──────────────────────────────────────────────────────────────────
public PageResponse<ApprovalLineResp> listLines(ApprovalLineSearchParam param) {
param.startPage();
return PageResponse.of(lineMapper.selectList(param));
}
public ApprovalLineResp detailLine(Long lineId) {
ApprovalLineResp resp = lineMapper.selectById(lineId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<ApprovalLineStepResp> stepsOfLine(Long lineId) {
return stepMapper.selectByLine(lineId);
}
@Transactional
public Long createLine(ApprovalLineSaveReq req) {
ApprovalLineVO vo = new ApprovalLineVO();
vo.setLineCode(req.getLineCode());
vo.setLineName(req.getLineName());
vo.setMaxStep(req.getMaxStep());
vo.setTargetType(req.getTargetType());
vo.setIsActive(true);
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
lineMapper.insert(vo);
return vo.getLineId();
}
@Transactional
public void updateLine(Long lineId, ApprovalLineSaveReq req) {
ApprovalLineResp existing = lineMapper.selectById(lineId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
ApprovalLineVO vo = new ApprovalLineVO();
vo.setLineId(lineId);
vo.setLineCode(req.getLineCode());
vo.setLineName(req.getLineName());
vo.setMaxStep(req.getMaxStep());
vo.setTargetType(req.getTargetType());
lineMapper.update(vo);
}
@Transactional
public void addStep(ApprovalLineStepSaveReq req) {
ApprovalLineStepVO vo = new ApprovalLineStepVO();
vo.setLineId(req.getLineId());
vo.setStepNo(req.getStepNo());
vo.setRoleCode(req.getRoleCode());
stepMapper.insert(vo);
}
// ── Request ───────────────────────────────────────────────────────────────
public PageResponse<ApprovalRequestResp> listRequests(ApprovalRequestSearchParam param) {
param.startPage();
return PageResponse.of(requestMapper.selectList(param));
}
public ApprovalRequestResp detailRequest(Long requestId) {
ApprovalRequestResp resp = requestMapper.selectById(requestId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<ApprovalHistoryResp> historyOfRequest(Long requestId) {
return historyMapper.selectByRequest(requestId);
}
/**
* 결재 요청 생성 — PENDING status=1, 첫 결재자에게 알림 발송.
*/
@Transactional
public Long createRequest(ApprovalRequestSaveReq req) {
// 결재선 존재 확인
ApprovalLineResp line = lineMapper.selectById(req.getLineId());
if (line == null) throw new BizException(ErrorCode.NOT_FOUND, "결재선을 찾을 수 없습니다");
ApprovalRequestVO vo = new ApprovalRequestVO();
vo.setTargetType(req.getTargetType());
vo.setTargetId(req.getTargetId());
vo.setLineId(req.getLineId());
vo.setRequestedBy(req.getRequestedBy());
vo.setRequestedAt(LocalDateTime.now());
vo.setCurrentStep(1);
vo.setStatus(ApprovalStatus.PENDING.name());
requestMapper.insert(vo);
// 1단계 결재자에게 알림
sendStepNotification(vo.getRequestId(), req.getLineId(), 1, req.getTargetType());
return vo.getRequestId();
}
/**
* 결재 단계 진행 — history INSERT + step advance + 최종 처리.
* @param requestId 결재 요청 PK
* @param approverId 결재자 user_id
* @param action APPROVE / REJECT
* @param comment 코멘트
*/
@Transactional
public void advance(Long requestId, Long approverId, String action, String comment) {
ApprovalRequestResp reqResp = requestMapper.selectById(requestId);
if (reqResp == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!ApprovalStatus.PENDING.name().equals(reqResp.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "진행 중인 결재 요청이 아닙니다");
}
// 1. 결재 이력 INSERT
ApprovalHistoryVO history = new ApprovalHistoryVO();
history.setRequestId(requestId);
history.setStepNo(reqResp.getCurrentStep());
history.setApproverId(approverId);
history.setAction(action);
history.setComment(comment);
history.setProcessedAt(LocalDateTime.now());
historyMapper.insert(history);
// 2. advanceStep — DB에서 current_step 갱신
requestMapper.advanceStep(requestId, approverId, action, comment);
// 3. REJECT → 종료
if (ApprovalAction.REJECT.name().equals(action)) {
requestMapper.updateStatus(requestId, ApprovalStatus.REJECTED.name());
log.info("결재 요청 반려: requestId={}", requestId);
return;
}
// 4. APPROVE 처리
if (ApprovalAction.APPROVE.name().equals(action)) {
int maxStep = reqResp.getMaxStep() != null ? reqResp.getMaxStep() : 1;
int currentStep = reqResp.getCurrentStep();
if (currentStep < maxStep) {
// 다음 단계 알림
sendStepNotification(requestId, reqResp.getLineId(), currentStep + 1, reqResp.getTargetType());
} else {
// 최종 승인 — 콜백 처리
requestMapper.updateStatus(requestId, ApprovalStatus.APPROVED.name());
handleApprovalCallback(reqResp.getTargetType(), reqResp.getTargetId());
}
}
}
/**
* 결재 취소.
*/
@Transactional
public void cancel(Long requestId) {
ApprovalRequestResp reqResp = requestMapper.selectById(requestId);
if (reqResp == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!ApprovalStatus.PENDING.name().equals(reqResp.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "진행 중인 결재만 취소 가능합니다");
}
requestMapper.cancel(requestId);
}
// ── Callback ──────────────────────────────────────────────────────────────
/**
* 최종 승인 후 target_type 별 도메인 후처리.
* - WITHDRAW → withdraw_request.status=APPROVED (펌뱅킹 SENT는 P5)
* - SETTLE → settle_master.status=CONFIRMED
* - DISPUTE → commission_dispute.status=APPROVED (정정 생성은 P5)
* - INCENTIVE → incentive_payment.status=CONFIRMED
*/
private void handleApprovalCallback(String targetType, Long targetId) {
if (targetType == null || targetId == null) return;
try {
switch (targetType) {
case "WITHDRAW" -> {
withdrawMapper.updateStatus(targetId, "APPROVED", null);
log.info("출금신청 승인 완료: requestId={}", targetId);
}
case "SETTLE" -> {
settleMasterMapper.updateStatus(targetId, SettleStatus.CONFIRMED.name(),
SecurityUtil.getCurrentUserId());
log.info("정산 확정 완료: settleId={}", targetId);
}
case "DISPUTE" -> {
disputeMapper.resolve(targetId, "APPROVED", null, null, null,
SecurityUtil.getCurrentUserId());
log.info("이의신청 승인 완료: disputeId={}", targetId);
}
case "INCENTIVE" -> {
incentivePaymentMapper.updateStatus(targetId, "CONFIRMED",
SecurityUtil.getCurrentUserId());
log.info("시책 확정 완료: incentivePayId={}", targetId);
}
default -> log.warn("알 수 없는 결재 대상 타입: targetType={}", targetType);
}
} catch (Exception e) {
log.error("결재 콜백 실패: targetType={}, targetId={}", targetType, targetId, e);
throw new BizException(ErrorCode.INTERNAL_ERROR, "결재 후처리 중 오류가 발생했습니다");
}
}
/** 다음 결재 단계 담당자에게 알림 발송 */
private void sendStepNotification(Long requestId, Long lineId, int stepNo, String targetType) {
try {
ApprovalLineStepVO step = stepMapper.selectByLineAndStep(lineId, stepNo);
if (step == null) return;
// roleCode 기반 userId 매핑은 P5에서 고도화 — 현재는 메타만 저장
UserNotificationVO noti = new UserNotificationVO();
noti.setUserId(0L); // 실제 담당자 ID 조회 로직은 P5
noti.setTitle("[결재요청] " + (targetType != null ? targetType : "") + " 결재 " + stepNo + "단계");
noti.setContent("결재 요청 ID " + requestId + " 에 대한 " + stepNo + "단계 결재가 필요합니다.");
noti.setRefType(NotificationRefType.APPROVAL.name());
noti.setRefId(requestId);
noti.setIsRead(false);
notificationMapper.insert(noti);
} catch (Exception e) {
log.warn("결재 알림 발송 실패 (non-critical): requestId={}", requestId, e);
}
}
}
@@ -0,0 +1,111 @@
package com.ga.api.service.attachment;
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.attachment.AttachmentMapper;
import com.ga.core.vo.attachment.AttachmentResp;
import com.ga.core.vo.attachment.AttachmentSearchParam;
import com.ga.core.vo.attachment.AttachmentVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AttachmentService {
private final AttachmentMapper mapper;
@Value("${app.attachment.path:./uploads}")
private String uploadBasePath;
public PageResponse<AttachmentResp> list(AttachmentSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public List<AttachmentResp> listByRef(String refType, Long refId) {
return mapper.selectByRef(refType, refId);
}
public AttachmentResp detail(Long attachmentId) {
AttachmentResp resp = mapper.selectById(attachmentId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 파일 업로드 + 메타 저장.
* 파일 저장 실패 시 경고 로그 후 메타만 저장하는 fallback.
*/
@Transactional
public Long upload(MultipartFile file, String refType, Long refId) {
String originalName = file.getOriginalFilename() != null ? file.getOriginalFilename() : "unknown";
String storedPath = null;
try {
Path dir = Paths.get(uploadBasePath, refType.toLowerCase());
Files.createDirectories(dir);
String storedName = UUID.randomUUID() + "_" + originalName;
Path dest = dir.resolve(storedName);
file.transferTo(dest);
storedPath = dest.toString();
} catch (IOException e) {
log.warn("파일 저장 실패 (fallback: 메타만 저장): refType={}, refId={}, file={}, error={}",
refType, refId, originalName, e.getMessage());
storedPath = "[STORAGE_FAILED]/" + originalName;
}
AttachmentVO vo = new AttachmentVO();
vo.setRefType(refType);
vo.setRefId(refId);
vo.setFileName(originalName);
vo.setFilePath(storedPath);
vo.setFileSize(file.getSize());
vo.setMimeType(file.getContentType());
vo.setUploadedBy(SecurityUtil.getCurrentUserId());
mapper.insert(vo);
return vo.getAttachmentId();
}
/**
* 파일 다운로드를 위한 실제 경로 반환.
*/
public Path resolveFilePath(Long attachmentId) {
AttachmentResp resp = mapper.selectById(attachmentId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
if (resp.getFilePath() == null || resp.getFilePath().startsWith("[STORAGE_FAILED]")) {
throw new BizException(ErrorCode.NOT_FOUND, "파일이 저장되지 않아 다운로드할 수 없습니다");
}
return Paths.get(resp.getFilePath());
}
@Transactional
public void delete(Long attachmentId) {
AttachmentResp resp = mapper.selectById(attachmentId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
// 실제 파일 삭제 시도 (실패해도 메타 삭제 진행)
if (resp.getFilePath() != null && !resp.getFilePath().startsWith("[STORAGE_FAILED]")) {
try {
Files.deleteIfExists(Paths.get(resp.getFilePath()));
} catch (IOException e) {
log.warn("파일 삭제 실패: path={}", resp.getFilePath(), e);
}
}
mapper.delete(attachmentId);
}
}
@@ -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.CollectionCommissionLedgerMapper;
import com.ga.core.mapper.commission.CollectionCommissionRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CollectionCommissionService {
private final CollectionCommissionRuleMapper ruleMapper;
private final CollectionCommissionLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<CollectionCommissionRuleResp> listRules(CollectionCommissionRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public CollectionCommissionRuleResp detailRule(Long ruleId) {
CollectionCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(CollectionCommissionRuleSaveReq req) {
CollectionCommissionRuleVO vo = new CollectionCommissionRuleVO();
vo.setProductId(req.getProductId());
vo.setPaymentCycle(req.getPaymentCycle());
vo.setCommissionRate(req.getCommissionRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, CollectionCommissionRuleSaveReq req) {
CollectionCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
CollectionCommissionRuleVO vo = new CollectionCommissionRuleVO();
vo.setRuleId(ruleId);
vo.setProductId(req.getProductId());
vo.setPaymentCycle(req.getPaymentCycle());
vo.setCommissionRate(req.getCommissionRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
CollectionCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<CollectionCommissionLedgerResp> listLedgers(CollectionCommissionLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public CollectionCommissionLedgerResp detailLedger(Long ledgerId) {
CollectionCommissionLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(CollectionCommissionLedgerSaveReq req) {
CollectionCommissionLedgerVO vo = new CollectionCommissionLedgerVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setPremiumAmount(req.getPremiumAmount());
vo.setCommissionRate(req.getCommissionRate());
// 수금수수료 = 보험료 × 수수료율
vo.setCommissionAmount(req.getPremiumAmount().multiply(req.getCommissionRate()));
vo.setPaymentCycle(req.getPaymentCycle());
vo.setPaidAt(req.getPaidAt());
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,54 @@
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.CommissionMasterMapper;
import com.ga.core.vo.commission.CommissionMasterResp;
import com.ga.core.vo.commission.CommissionMasterSearchParam;
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 CommissionMasterService {
private final CommissionMasterMapper mapper;
public PageResponse<CommissionMasterResp> list(CommissionMasterSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public CommissionMasterResp detail(Long masterId) {
CommissionMasterResp resp = mapper.selectById(masterId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<CommissionMasterResp> listByAgentAndMonth(Long agentId, String settleMonth) {
return mapper.selectByAgentAndMonth(agentId, settleMonth);
}
public List<CommissionMasterResp> aggregatedByMonth(String settleMonth) {
return mapper.selectAggregatedByMonth(settleMonth);
}
@Transactional
public void updateStatus(Long masterId, String status) {
CommissionMasterResp resp = mapper.selectById(masterId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.updateStatus(masterId, status);
}
@Transactional
public void delete(Long masterId) {
CommissionMasterResp resp = mapper.selectById(masterId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.delete(masterId);
}
}
@@ -0,0 +1,43 @@
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.OverrideLedgerMapper;
import com.ga.core.mapper.commission.OverrideSummaryMapper;
import com.ga.core.vo.commission.*;
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 OverrideLedgerService {
private final OverrideLedgerMapper ledgerMapper;
private final OverrideSummaryMapper summaryMapper;
public PageResponse<OverrideLedgerResp> list(OverrideLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public OverrideLedgerResp detail(Long ledgerId) {
OverrideLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<OverrideSummaryResp> summaryList(OverrideSummarySearchParam param) {
return summaryMapper.selectList(param);
}
public OverrideSummaryResp summaryByAgentAndMonth(Long toAgentId, String settleMonth) {
OverrideSummaryResp resp = summaryMapper.selectByAgentAndMonth(toAgentId, settleMonth);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
}
@@ -0,0 +1,100 @@
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.PersistencyBonusMapper;
import com.ga.core.mapper.commission.PersistencyBonusRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PersistencyBonusService {
private final PersistencyBonusRuleMapper ruleMapper;
private final PersistencyBonusMapper bonusMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<PersistencyBonusRuleResp> listRules(PersistencyBonusRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public PersistencyBonusRuleResp detailRule(Long ruleId) {
PersistencyBonusRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(PersistencyBonusRuleSaveReq req) {
PersistencyBonusRuleVO vo = new PersistencyBonusRuleVO();
vo.setGradeId(req.getGradeId());
vo.setRetentionMonth(req.getRetentionMonth());
vo.setRetentionRateThreshold(req.getRetentionRateThreshold());
vo.setBonusType(req.getBonusType());
vo.setBonusValue(req.getBonusValue());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, PersistencyBonusRuleSaveReq req) {
PersistencyBonusRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
PersistencyBonusRuleVO vo = new PersistencyBonusRuleVO();
vo.setRuleId(ruleId);
vo.setGradeId(req.getGradeId());
vo.setRetentionMonth(req.getRetentionMonth());
vo.setRetentionRateThreshold(req.getRetentionRateThreshold());
vo.setBonusType(req.getBonusType());
vo.setBonusValue(req.getBonusValue());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
PersistencyBonusRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Bonus ─────────────────────────────────────────────────────────────────
public PageResponse<PersistencyBonusResp> listBonuses(PersistencyBonusSearchParam param) {
param.startPage();
return PageResponse.of(bonusMapper.selectList(param));
}
public PersistencyBonusResp detailBonus(Long bonusId) {
PersistencyBonusResp resp = bonusMapper.selectById(bonusId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 수동 지급 등록/갱신 — UNIQUE(agent_id, base_month, retention_month) 기반 upsert.
*/
@Transactional
public void upsertBonus(PersistencyBonusSaveReq req) {
PersistencyBonusVO vo = new PersistencyBonusVO();
vo.setAgentId(req.getAgentId());
vo.setRuleId(req.getRuleId());
vo.setBaseMonth(req.getBaseMonth());
vo.setRetentionMonth(req.getRetentionMonth());
vo.setSettleMonth(req.getSettleMonth());
vo.setRetentionRate(req.getRetentionRate());
vo.setBonusAmount(req.getBonusAmount());
vo.setStatus("CALCULATED");
bonusMapper.upsert(vo);
}
}
@@ -0,0 +1,108 @@
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.ReinstatementCommissionLedgerMapper;
import com.ga.core.mapper.commission.ReinstatementCommissionRuleMapper;
import com.ga.core.vo.commission.*;
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 ReinstatementCommissionService {
private final ReinstatementCommissionRuleMapper ruleMapper;
private final ReinstatementCommissionLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<ReinstatementCommissionRuleResp> listRules(ReinstatementCommissionRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public ReinstatementCommissionRuleResp detailRule(Long ruleId) {
ReinstatementCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(ReinstatementCommissionRuleSaveReq req) {
ReinstatementCommissionRuleVO vo = new ReinstatementCommissionRuleVO();
vo.setProductId(req.getProductId());
vo.setPayType(req.getPayType());
vo.setRateValue(req.getRateValue());
vo.setFixedAmount(req.getFixedAmount());
vo.setChargebackReversalRate(req.getChargebackReversalRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, ReinstatementCommissionRuleSaveReq req) {
ReinstatementCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ReinstatementCommissionRuleVO vo = new ReinstatementCommissionRuleVO();
vo.setRuleId(ruleId);
vo.setProductId(req.getProductId());
vo.setPayType(req.getPayType());
vo.setRateValue(req.getRateValue());
vo.setFixedAmount(req.getFixedAmount());
vo.setChargebackReversalRate(req.getChargebackReversalRate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
ReinstatementCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<ReinstatementCommissionLedgerResp> listLedgers(ReinstatementCommissionLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public ReinstatementCommissionLedgerResp detailLedger(Long ledgerId) {
ReinstatementCommissionLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 수동 등록 — chargebackReversal 은 요청값 그대로.
* netCommission = commissionAmount + chargebackReversal 로 자동 계산.
*/
@Transactional
public Long createLedger(ReinstatementCommissionLedgerSaveReq req) {
ReinstatementCommissionLedgerVO vo = new ReinstatementCommissionLedgerVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setRuleId(req.getRuleId());
vo.setSettleMonth(req.getSettleMonth());
vo.setReinstatementDate(req.getReinstatementDate());
vo.setReinstatementPremium(req.getReinstatementPremium());
vo.setCommissionAmount(req.getCommissionAmount());
BigDecimal reversal = req.getChargebackReversal() != null ? req.getChargebackReversal() : BigDecimal.ZERO;
vo.setChargebackReversal(reversal);
vo.setNetCommission(req.getCommissionAmount().add(reversal));
vo.setPayType(req.getPayType());
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,97 @@
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.RenewalCommissionLedgerMapper;
import com.ga.core.mapper.commission.RenewalCommissionRuleMapper;
import com.ga.core.vo.commission.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RenewalCommissionService {
private final RenewalCommissionRuleMapper ruleMapper;
private final RenewalCommissionLedgerMapper ledgerMapper;
// ── Rule ──────────────────────────────────────────────────────────────────
public PageResponse<RenewalCommissionRuleResp> listRules(RenewalCommissionRuleSearchParam param) {
param.startPage();
return PageResponse.of(ruleMapper.selectList(param));
}
public RenewalCommissionRuleResp detailRule(Long ruleId) {
RenewalCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createRule(RenewalCommissionRuleSaveReq req) {
RenewalCommissionRuleVO vo = new RenewalCommissionRuleVO();
vo.setProductId(req.getProductId());
vo.setPayType(req.getPayType());
vo.setRateValue(req.getRateValue());
vo.setFixedAmount(req.getFixedAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.insert(vo);
return vo.getRuleId();
}
@Transactional
public void updateRule(Long ruleId, RenewalCommissionRuleSaveReq req) {
RenewalCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
RenewalCommissionRuleVO vo = new RenewalCommissionRuleVO();
vo.setRuleId(ruleId);
vo.setProductId(req.getProductId());
vo.setPayType(req.getPayType());
vo.setRateValue(req.getRateValue());
vo.setFixedAmount(req.getFixedAmount());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
ruleMapper.update(vo);
}
@Transactional
public void deleteRule(Long ruleId) {
RenewalCommissionRuleResp resp = ruleMapper.selectById(ruleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
ruleMapper.delete(ruleId);
}
// ── Ledger ────────────────────────────────────────────────────────────────
public PageResponse<RenewalCommissionLedgerResp> listLedgers(RenewalCommissionLedgerSearchParam param) {
param.startPage();
return PageResponse.of(ledgerMapper.selectList(param));
}
public RenewalCommissionLedgerResp detailLedger(Long ledgerId) {
RenewalCommissionLedgerResp resp = ledgerMapper.selectById(ledgerId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long createLedger(RenewalCommissionLedgerSaveReq req) {
RenewalCommissionLedgerVO vo = new RenewalCommissionLedgerVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setRuleId(req.getRuleId());
vo.setSettleMonth(req.getSettleMonth());
vo.setRenewalDate(req.getRenewalDate());
vo.setRenewalPremium(req.getRenewalPremium());
vo.setCommissionAmount(req.getCommissionAmount());
vo.setPayType(req.getPayType());
vo.setStatus("CALCULATED");
ledgerMapper.insert(vo);
return vo.getLedgerId();
}
}
@@ -0,0 +1,86 @@
package com.ga.api.service.complaint;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.complaint.ComplaintMapper;
import com.ga.core.vo.complaint.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ComplaintService {
private final ComplaintMapper mapper;
public PageResponse<ComplaintResp> list(ComplaintSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public ComplaintResp detail(Long complaintId) {
ComplaintResp resp = mapper.selectById(complaintId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(ComplaintSaveReq req) {
ComplaintVO vo = new ComplaintVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setCustomerName(req.getCustomerName());
vo.setComplaintDate(req.getComplaintDate());
vo.setChannel(req.getChannel());
vo.setComplaintType(req.getComplaintType());
vo.setSeverity(req.getSeverity() != null ? req.getSeverity() : "MID");
vo.setStatus(ComplaintStatus.OPEN.name());
vo.setResolution(req.getResolution());
// chargebackTriggered=true 면 P5에서 chargeback_ledger 자동 생성 예정 — 지금은 플래그만 저장
vo.setChargebackTriggered(req.getChargebackTriggered());
mapper.insert(vo);
return vo.getComplaintId();
}
@Transactional
public void update(Long complaintId, ComplaintSaveReq req) {
ComplaintResp existing = mapper.selectById(complaintId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
ComplaintVO vo = new ComplaintVO();
vo.setComplaintId(complaintId);
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setCustomerName(req.getCustomerName());
vo.setComplaintDate(req.getComplaintDate());
vo.setChannel(req.getChannel());
vo.setComplaintType(req.getComplaintType());
vo.setSeverity(req.getSeverity());
vo.setResolution(req.getResolution());
vo.setChargebackTriggered(req.getChargebackTriggered());
mapper.update(vo);
}
/**
* 처리완료 — status=CLOSED.
* chargebackTriggered=true 인 건의 실제 환수 생성은 P5 배치에서.
*/
@Transactional
public void close(Long complaintId, String resolution) {
ComplaintResp existing = mapper.selectById(complaintId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
if (ComplaintStatus.CLOSED.name().equals(existing.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 처리완료된 민원입니다");
}
mapper.updateStatus(complaintId, ComplaintStatus.CLOSED.name(), resolution);
}
@Transactional
public void delete(Long complaintId) {
ComplaintResp existing = mapper.selectById(complaintId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.delete(complaintId);
}
}
@@ -0,0 +1,79 @@
package com.ga.api.service.contract;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.contract.AgentContractMapper;
import com.ga.core.vo.contract.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AgentContractService {
private final AgentContractMapper mapper;
public PageResponse<AgentContractResp> list(AgentContractSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public AgentContractResp detail(Long contractId) {
AgentContractResp resp = mapper.selectById(contractId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(AgentContractSaveReq req) {
AgentContractVO vo = new AgentContractVO();
vo.setAgentId(req.getAgentId());
vo.setCarrierId(req.getCarrierId());
vo.setContractNo(req.getContractNo());
vo.setSignedDate(req.getSignedDate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
vo.setOnboardingBonusAmount(req.getOnboardingBonusAmount());
vo.setStatus(AgentContractStatus.ACTIVE.name());
mapper.insert(vo);
return vo.getContractId();
}
@Transactional
public void update(Long contractId, AgentContractSaveReq req) {
AgentContractResp existing = mapper.selectById(contractId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
AgentContractVO vo = new AgentContractVO();
vo.setContractId(contractId);
vo.setAgentId(req.getAgentId());
vo.setCarrierId(req.getCarrierId());
vo.setContractNo(req.getContractNo());
vo.setSignedDate(req.getSignedDate());
vo.setEffectiveFrom(req.getEffectiveFrom());
vo.setEffectiveTo(req.getEffectiveTo());
vo.setOnboardingBonusAmount(req.getOnboardingBonusAmount());
mapper.update(vo);
}
@Transactional
public void terminate(Long contractId, String reason) {
AgentContractResp existing = mapper.selectById(contractId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
if (AgentContractStatus.TERMINATED.name().equals(existing.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 해촉된 위촉계약입니다");
}
mapper.updateStatus(contractId, AgentContractStatus.TERMINATED.name(), reason);
}
@Transactional
public void delete(Long contractId) {
AgentContractResp existing = mapper.selectById(contractId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.delete(contractId);
}
}
@@ -0,0 +1,62 @@
package com.ga.api.service.endorse;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.endorse.ContractEndorsementMapper;
import com.ga.core.vo.endorse.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ContractEndorsementService {
private final ContractEndorsementMapper mapper;
public PageResponse<ContractEndorsementResp> list(ContractEndorsementSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public ContractEndorsementResp detail(Long endorsementId) {
ContractEndorsementResp resp = mapper.selectById(endorsementId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(ContractEndorsementSaveReq req) {
ContractEndorsementVO vo = new ContractEndorsementVO();
vo.setContractId(req.getContractId());
vo.setAgentId(req.getAgentId());
vo.setEndorsementType(req.getEndorsementType());
vo.setOldValue(req.getOldValue());
vo.setNewValue(req.getNewValue());
vo.setReason(req.getReason());
vo.setRequiresCommissionRecalc(req.getRequiresCommissionRecalc());
vo.setProcessed(false);
mapper.insert(vo);
return vo.getEndorsementId();
}
/**
* 수동 처리 완료 표시 — 실제 수수료 재계산은 배치 담당(P5).
* status 변경(processed=true) 만 수행.
*/
@Transactional
public void process(Long endorsementId) {
ContractEndorsementResp existing = mapper.selectById(endorsementId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.markProcessed(endorsementId);
}
@Transactional
public void delete(Long endorsementId) {
ContractEndorsementResp existing = mapper.selectById(endorsementId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.delete(endorsementId);
}
}
@@ -0,0 +1,40 @@
package com.ga.api.service.kpi;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.kpi.KpiMapper;
import com.ga.core.vo.kpi.KpiCumulativeResp;
import com.ga.core.vo.kpi.KpiMonthlySummaryResp;
import com.ga.core.vo.kpi.KpiOrgSummaryResp;
import com.ga.core.vo.kpi.KpiRetentionResp;
import com.ga.core.vo.kpi.KpiSearchParam;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class KpiService {
private final KpiMapper kpiMapper;
public PageResponse<KpiMonthlySummaryResp> monthlySummary(KpiSearchParam param) {
param.startPage();
return PageResponse.of(kpiMapper.selectMonthlySummary(param));
}
public PageResponse<KpiOrgSummaryResp> orgSummary(KpiSearchParam param) {
param.startPage();
return PageResponse.of(kpiMapper.selectOrgSummary(param));
}
public PageResponse<KpiRetentionResp> retention(KpiSearchParam param) {
param.startPage();
return PageResponse.of(kpiMapper.selectRetention(param));
}
public PageResponse<KpiCumulativeResp> cumulative(KpiSearchParam param) {
param.startPage();
return PageResponse.of(kpiMapper.selectCumulative(param));
}
}
@@ -0,0 +1,71 @@
package com.ga.api.service.license;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.license.AgentLicenseMapper;
import com.ga.core.vo.license.*;
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 AgentLicenseService {
private final AgentLicenseMapper mapper;
public PageResponse<AgentLicenseResp> list(AgentLicenseSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public AgentLicenseResp detail(Long licenseId) {
AgentLicenseResp resp = mapper.selectById(licenseId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<AgentLicenseResp> expiring(int days) {
return mapper.selectExpiringWithin(days);
}
@Transactional
public Long create(AgentLicenseSaveReq req) {
AgentLicenseVO vo = new AgentLicenseVO();
vo.setAgentId(req.getAgentId());
vo.setLicenseType(req.getLicenseType());
vo.setLicenseNo(req.getLicenseNo());
vo.setIssuedDate(req.getIssuedDate());
vo.setExpiresDate(req.getExpiresDate());
vo.setRenewedFrom(req.getRenewedFrom());
vo.setStatus(LicenseStatus.ACTIVE.name());
mapper.insert(vo);
return vo.getLicenseId();
}
@Transactional
public void update(Long licenseId, AgentLicenseSaveReq req) {
AgentLicenseResp existing = mapper.selectById(licenseId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
AgentLicenseVO vo = new AgentLicenseVO();
vo.setLicenseId(licenseId);
vo.setAgentId(req.getAgentId());
vo.setLicenseType(req.getLicenseType());
vo.setLicenseNo(req.getLicenseNo());
vo.setIssuedDate(req.getIssuedDate());
vo.setExpiresDate(req.getExpiresDate());
vo.setRenewedFrom(req.getRenewedFrom());
mapper.update(vo);
}
@Transactional
public void delete(Long licenseId) {
AgentLicenseResp existing = mapper.selectById(licenseId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
mapper.delete(licenseId);
}
}
@@ -0,0 +1,97 @@
package com.ga.api.service.notice;
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.notice.NoticeMapper;
import com.ga.core.mapper.notice.UserNotificationMapper;
import com.ga.core.vo.notice.*;
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 NoticeService {
private final NoticeMapper noticeMapper;
private final UserNotificationMapper notificationMapper;
// ── Notice ────────────────────────────────────────────────────────────────
public PageResponse<NoticeResp> list(NoticeSearchParam param) {
param.startPage();
return PageResponse.of(noticeMapper.selectList(param));
}
public NoticeResp detail(Long noticeId) {
NoticeResp resp = noticeMapper.selectById(noticeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<NoticeResp> activeList() {
return noticeMapper.selectActive();
}
@Transactional
public Long create(NoticeSaveReq req) {
NoticeVO vo = new NoticeVO();
vo.setTitle(req.getTitle());
vo.setContent(req.getContent());
vo.setNoticeType(req.getNoticeType());
vo.setStartDate(req.getStartDate());
vo.setEndDate(req.getEndDate());
vo.setIsActive(true);
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
noticeMapper.insert(vo);
return vo.getNoticeId();
}
@Transactional
public void update(Long noticeId, NoticeSaveReq req) {
NoticeResp existing = noticeMapper.selectById(noticeId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
NoticeVO vo = new NoticeVO();
vo.setNoticeId(noticeId);
vo.setTitle(req.getTitle());
vo.setContent(req.getContent());
vo.setNoticeType(req.getNoticeType());
vo.setStartDate(req.getStartDate());
vo.setEndDate(req.getEndDate());
noticeMapper.update(vo);
}
@Transactional
public void delete(Long noticeId) {
NoticeResp existing = noticeMapper.selectById(noticeId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
noticeMapper.delete(noticeId);
}
// ── UserNotification ──────────────────────────────────────────────────────
public PageResponse<UserNotificationResp> listNotifications(UserNotificationSearchParam param) {
param.startPage();
return PageResponse.of(notificationMapper.selectList(param));
}
public int unreadCount(Long userId) {
return notificationMapper.selectUnreadCount(userId);
}
@Transactional
public void markRead(Long notificationId, Long userId) {
int affected = notificationMapper.markRead(notificationId, userId);
if (affected == 0) throw new BizException(ErrorCode.NOT_FOUND);
}
@Transactional
public void markAllRead(Long userId) {
notificationMapper.markAllRead(userId);
}
}
@@ -0,0 +1,67 @@
package com.ga.api.service.settle;
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.settle.CarrierReconciliationOutMapper;
import com.ga.core.vo.settle.CarrierReconciliationOutResp;
import com.ga.core.vo.settle.CarrierReconciliationOutSaveReq;
import com.ga.core.vo.settle.CarrierReconciliationOutSearchParam;
import com.ga.core.vo.settle.CarrierReconciliationOutVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CarrierReconciliationOutService {
private final CarrierReconciliationOutMapper reconOutMapper;
public PageResponse<CarrierReconciliationOutResp> list(CarrierReconciliationOutSearchParam param) {
param.startPage();
return PageResponse.of(reconOutMapper.selectList(param));
}
public CarrierReconciliationOutResp detail(Long reconOutId) {
CarrierReconciliationOutResp resp = reconOutMapper.selectById(reconOutId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(CarrierReconciliationOutSaveReq req) {
// 보험사+정산월 중복 확인
CarrierReconciliationOutVO existing = reconOutMapper.selectByCarrierMonth(req.getCarrierId(), req.getSettleMonth());
if (existing != null) {
throw new BizException(ErrorCode.DUPLICATE_DATA,
"이미 등록된 회신 이력입니다: carrierId=" + req.getCarrierId() + ", settleMonth=" + req.getSettleMonth());
}
CarrierReconciliationOutVO vo = new CarrierReconciliationOutVO();
vo.setCarrierId(req.getCarrierId());
vo.setSettleMonth(req.getSettleMonth());
vo.setFilePath(req.getFilePath());
vo.setFileName(req.getFileName());
vo.setFileSize(req.getFileSize());
vo.setRecordCount(req.getRecordCount());
vo.setStatus("READY");
vo.setRetryCount(0);
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
reconOutMapper.insert(vo);
return vo.getReconOutId();
}
@Transactional
public void retry(Long reconOutId) {
CarrierReconciliationOutVO vo = reconOutMapper.selectVoById(reconOutId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!"FAIL".equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "실패 상태만 재전송 가능합니다");
}
// 상태를 READY로 초기화하여 배치가 재처리
reconOutMapper.updateStatus(reconOutId, "READY");
}
}
@@ -0,0 +1,83 @@
package com.ga.api.service.settle;
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.settle.CommissionDisputeMapper;
import com.ga.core.vo.settle.CommissionDisputeResp;
import com.ga.core.vo.settle.CommissionDisputeSaveReq;
import com.ga.core.vo.settle.CommissionDisputeSearchParam;
import com.ga.core.vo.settle.CommissionDisputeVO;
import com.ga.core.vo.settle.DisputeStatus;
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 CommissionDisputeService {
private final CommissionDisputeMapper disputeMapper;
public PageResponse<CommissionDisputeResp> list(CommissionDisputeSearchParam param) {
param.startPage();
return PageResponse.of(disputeMapper.selectList(param));
}
public CommissionDisputeResp detail(Long disputeId) {
CommissionDisputeResp resp = disputeMapper.selectById(disputeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(CommissionDisputeSaveReq req) {
CommissionDisputeVO vo = new CommissionDisputeVO();
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setSettleId(req.getSettleId());
vo.setDisputedAmount(req.getDisputedAmount());
vo.setClaimAmount(req.getClaimAmount());
vo.setReason(req.getReason());
vo.setEvidenceUrl(req.getEvidenceUrl());
vo.setStatus(DisputeStatus.OPEN.name());
disputeMapper.insert(vo);
return vo.getDisputeId();
}
@Transactional
public void approve(Long disputeId, String resolution, BigDecimal adjustmentAmount, Long correctionId) {
CommissionDisputeVO vo = disputeMapper.selectVoById(disputeId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!DisputeStatus.IN_REVIEW.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "검토 중 상태만 승인 가능합니다");
}
disputeMapper.resolve(disputeId, DisputeStatus.APPROVED.name(),
resolution, adjustmentAmount, correctionId, SecurityUtil.getCurrentUserId());
}
@Transactional
public void reject(Long disputeId, String resolution) {
CommissionDisputeVO vo = disputeMapper.selectVoById(disputeId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!DisputeStatus.IN_REVIEW.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "검토 중 상태만 기각 가능합니다");
}
disputeMapper.resolve(disputeId, DisputeStatus.REJECTED.name(),
resolution, null, null, SecurityUtil.getCurrentUserId());
}
@Transactional
public void startReview(Long disputeId) {
CommissionDisputeVO vo = disputeMapper.selectVoById(disputeId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!DisputeStatus.OPEN.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "OPEN 상태만 검토 시작 가능합니다");
}
disputeMapper.startReview(disputeId, SecurityUtil.getCurrentUserId());
}
}
@@ -0,0 +1,73 @@
package com.ga.api.service.settle;
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.settle.IncentivePaymentMapper;
import com.ga.core.vo.settle.IncentivePayStatus;
import com.ga.core.vo.settle.IncentivePaymentResp;
import com.ga.core.vo.settle.IncentivePaymentSaveReq;
import com.ga.core.vo.settle.IncentivePaymentSearchParam;
import com.ga.core.vo.settle.IncentivePaymentVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class IncentivePaymentService {
private final IncentivePaymentMapper paymentMapper;
private final PeriodCloseService periodCloseService;
public PageResponse<IncentivePaymentResp> list(IncentivePaymentSearchParam param) {
param.startPage();
return PageResponse.of(paymentMapper.selectList(param));
}
public IncentivePaymentResp detail(Long incentivePayId) {
IncentivePaymentResp resp = paymentMapper.selectById(incentivePayId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(IncentivePaymentSaveReq req) {
// 마감 가드
periodCloseService.assertNotClosed(req.getSettleMonth());
IncentivePaymentVO vo = new IncentivePaymentVO();
vo.setProgramId(req.getProgramId());
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setBaseAmount(req.getBaseAmount());
vo.setIncentiveAmount(req.getIncentiveAmount());
vo.setStatus(IncentivePayStatus.CALCULATED.name());
paymentMapper.insert(vo);
return vo.getIncentivePayId();
}
@Transactional
public void confirm(Long incentivePayId) {
IncentivePaymentVO vo = paymentMapper.selectVoById(incentivePayId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!IncentivePayStatus.CALCULATED.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "CALCULATED 상태만 확정 가능합니다");
}
// 마감 가드
periodCloseService.assertNotClosed(vo.getSettleMonth());
paymentMapper.updateStatus(incentivePayId, IncentivePayStatus.CONFIRMED.name(), SecurityUtil.getCurrentUserId());
}
@Transactional
public void cancel(Long incentivePayId, String cancelReason) {
IncentivePaymentVO vo = paymentMapper.selectVoById(incentivePayId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (IncentivePayStatus.PAID.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "지급완료된 시책은 취소할 수 없습니다");
}
paymentMapper.updateStatus(incentivePayId, IncentivePayStatus.CANCELLED.name(), SecurityUtil.getCurrentUserId());
}
}
@@ -0,0 +1,84 @@
package com.ga.api.service.settle;
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.settle.IncentiveProgramMapper;
import com.ga.core.vo.settle.IncentiveProgramResp;
import com.ga.core.vo.settle.IncentiveProgramSaveReq;
import com.ga.core.vo.settle.IncentiveProgramSearchParam;
import com.ga.core.vo.settle.IncentiveProgramVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class IncentiveProgramService {
private final IncentiveProgramMapper programMapper;
public PageResponse<IncentiveProgramResp> list(IncentiveProgramSearchParam param) {
param.startPage();
return PageResponse.of(programMapper.selectList(param));
}
public IncentiveProgramResp detail(Long programId) {
IncentiveProgramResp resp = programMapper.selectById(programId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(IncentiveProgramSaveReq req) {
IncentiveProgramVO vo = new IncentiveProgramVO();
vo.setProgramName(req.getProgramName());
vo.setProgramType(req.getProgramType());
vo.setTargetType(req.getTargetType());
vo.setTargetGradeId(req.getTargetGradeId());
vo.setTargetOrgId(req.getTargetOrgId());
vo.setProductCategory(req.getProductCategory());
vo.setStartMonth(req.getStartMonth());
vo.setEndMonth(req.getEndMonth());
vo.setConditionDesc(req.getConditionDesc());
vo.setPayRate(req.getPayRate());
vo.setPayAmountFixed(req.getPayAmountFixed());
vo.setPayType(req.getPayType());
vo.setBudgetAmount(req.getBudgetAmount());
vo.setIsActive(true);
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
programMapper.insert(vo);
return vo.getProgramId();
}
@Transactional
public void update(Long programId, IncentiveProgramSaveReq req) {
IncentiveProgramVO existing = programMapper.selectVoById(programId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
existing.setProgramName(req.getProgramName());
existing.setProgramType(req.getProgramType());
existing.setTargetType(req.getTargetType());
existing.setTargetGradeId(req.getTargetGradeId());
existing.setTargetOrgId(req.getTargetOrgId());
existing.setProductCategory(req.getProductCategory());
existing.setStartMonth(req.getStartMonth());
existing.setEndMonth(req.getEndMonth());
existing.setConditionDesc(req.getConditionDesc());
existing.setPayRate(req.getPayRate());
existing.setPayAmountFixed(req.getPayAmountFixed());
existing.setPayType(req.getPayType());
existing.setBudgetAmount(req.getBudgetAmount());
existing.setUpdatedBy(SecurityUtil.getCurrentUserId());
programMapper.update(existing);
}
@Transactional
public void toggleActive(Long programId, boolean isActive) {
IncentiveProgramVO existing = programMapper.selectVoById(programId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
programMapper.updateActive(programId, isActive, SecurityUtil.getCurrentUserId());
}
}
@@ -0,0 +1,63 @@
package com.ga.api.service.settle;
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.settle.PeriodCloseMapper;
import com.ga.core.vo.settle.PeriodCloseResp;
import com.ga.core.vo.settle.PeriodCloseSaveReq;
import com.ga.core.vo.settle.PeriodCloseSearchParam;
import com.ga.core.vo.settle.PeriodCloseVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PeriodCloseService {
private final PeriodCloseMapper periodCloseMapper;
public PageResponse<PeriodCloseResp> list(PeriodCloseSearchParam param) {
param.startPage();
return PageResponse.of(periodCloseMapper.selectList(param));
}
public PeriodCloseResp detail(Long closeId) {
PeriodCloseResp resp = periodCloseMapper.selectById(closeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public void close(PeriodCloseSaveReq req) {
// 이미 마감된 월인지 확인
if (periodCloseMapper.isClosedMonth(req.getCloseMonth())) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 마감된 정산월입니다: " + req.getCloseMonth());
}
PeriodCloseVO vo = new PeriodCloseVO();
vo.setCloseMonth(req.getCloseMonth());
vo.setNote(req.getNote());
vo.setClosedBy(SecurityUtil.getCurrentUserId());
periodCloseMapper.insert(vo);
}
@Transactional
public void reopen(Long closeId, String reopenReason) {
PeriodCloseResp resp = periodCloseMapper.selectById(closeId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
periodCloseMapper.reopen(closeId, SecurityUtil.getCurrentUserId(), reopenReason);
}
/**
* 마감 가드: 해당 정산월이 마감 상태이면 예외 발생.
* settle_correction, payment 등 변경 서비스에서 호출.
*/
public void assertNotClosed(String settleMonth) {
if (periodCloseMapper.isClosedMonth(settleMonth)) {
throw new BizException(ErrorCode.NOT_PERMITTED, "마감된 기간입니다: " + settleMonth);
}
}
}
@@ -0,0 +1,81 @@
package com.ga.api.service.settle;
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.settle.SettleCorrectionMapper;
import com.ga.core.vo.settle.CorrectionStatus;
import com.ga.core.vo.settle.SettleCorrectionResp;
import com.ga.core.vo.settle.SettleCorrectionSaveReq;
import com.ga.core.vo.settle.SettleCorrectionSearchParam;
import com.ga.core.vo.settle.SettleCorrectionVO;
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 SettleCorrectionService {
private final SettleCorrectionMapper correctionMapper;
private final PeriodCloseService periodCloseService;
public PageResponse<SettleCorrectionResp> list(SettleCorrectionSearchParam param) {
param.startPage();
return PageResponse.of(correctionMapper.selectList(param));
}
public SettleCorrectionResp detail(Long correctionId) {
SettleCorrectionResp resp = correctionMapper.selectById(correctionId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
public List<SettleCorrectionResp> listByOriginalSettle(Long originalSettleId) {
return correctionMapper.selectByOriginalSettleId(originalSettleId);
}
@Transactional
public Long create(SettleCorrectionSaveReq req) {
// 마감 가드
periodCloseService.assertNotClosed(req.getSettleMonth());
SettleCorrectionVO vo = new SettleCorrectionVO();
vo.setOriginalSettleId(req.getOriginalSettleId());
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setBeforeAmount(req.getBeforeAmount());
vo.setAfterAmount(req.getAfterAmount());
vo.setDiffAmount(req.getAfterAmount().subtract(req.getBeforeAmount()));
vo.setReasonCode(req.getReasonCode());
vo.setReasonDetail(req.getReasonDetail());
vo.setStatus(CorrectionStatus.DRAFT.name());
vo.setRequestedBy(SecurityUtil.getCurrentUserId());
correctionMapper.insert(vo);
return vo.getCorrectionId();
}
@Transactional
public void approve(Long correctionId) {
SettleCorrectionVO vo = correctionMapper.selectVoById(correctionId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!CorrectionStatus.DRAFT.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "DRAFT 상태만 승인 가능합니다");
}
correctionMapper.updateStatus(correctionId, CorrectionStatus.APPROVED.name(), SecurityUtil.getCurrentUserId());
}
@Transactional
public void cancel(Long correctionId, String cancelReason) {
SettleCorrectionVO vo = correctionMapper.selectVoById(correctionId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (CorrectionStatus.APPLIED.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "이미 반영된 정정은 취소할 수 없습니다");
}
correctionMapper.cancel(correctionId, cancelReason, SecurityUtil.getCurrentUserId());
}
}
@@ -56,13 +56,13 @@ public class SettleService {
}
@Transactional
public void hold(Long settleId, String reason) {
public void hold(Long settleId, String reason, String reasonCode) {
SettleMasterVO vo = masterMapper.selectById(settleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (SettleStatus.PAID.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.NOT_PERMITTED, "지급완료된 정산은 보류할 수 없습니다");
}
masterMapper.updateHold(settleId, reason);
masterMapper.updateHold(settleId, reason, reasonCode);
}
@Transactional
@@ -0,0 +1,70 @@
package com.ga.api.service.settle;
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.settle.TaxInvoiceMapper;
import com.ga.core.vo.settle.TaxInvoiceResp;
import com.ga.core.vo.settle.TaxInvoiceSaveReq;
import com.ga.core.vo.settle.TaxInvoiceSearchParam;
import com.ga.core.vo.settle.TaxInvoiceVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TaxInvoiceService {
private final TaxInvoiceMapper taxInvoiceMapper;
public PageResponse<TaxInvoiceResp> list(TaxInvoiceSearchParam param) {
param.startPage();
return PageResponse.of(taxInvoiceMapper.selectList(param));
}
public TaxInvoiceResp detail(Long invoiceId) {
TaxInvoiceResp resp = taxInvoiceMapper.selectById(invoiceId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(TaxInvoiceSaveReq req) {
// 발행번호 중복 확인
if (taxInvoiceMapper.selectByInvoiceNo(req.getInvoiceNo()) != null) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 등록된 발행번호입니다: " + req.getInvoiceNo());
}
TaxInvoiceVO vo = new TaxInvoiceVO();
vo.setInvoiceNo(req.getInvoiceNo());
vo.setInvoiceType(req.getInvoiceType());
vo.setIssueDate(req.getIssueDate());
vo.setSupplyAmount(req.getSupplyAmount());
vo.setTaxAmount(req.getTaxAmount());
vo.setTotalAmount(req.getSupplyAmount().add(req.getTaxAmount()));
vo.setCounterpartName(req.getCounterpartName());
vo.setCounterpartBizNo(req.getCounterpartBizNo());
vo.setCounterpartRep(req.getCounterpartRep());
vo.setCounterpartAddr(req.getCounterpartAddr());
vo.setAgentId(req.getAgentId());
vo.setSettleMonth(req.getSettleMonth());
vo.setSettleId(req.getSettleId());
vo.setDescription(req.getDescription());
vo.setIssuedBy(SecurityUtil.getCurrentUserId());
taxInvoiceMapper.insert(vo);
return vo.getInvoiceId();
}
@Transactional
public void cancel(Long invoiceId, String cancelReason) {
TaxInvoiceResp resp = taxInvoiceMapper.selectById(invoiceId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
taxInvoiceMapper.cancel(invoiceId, LocalDate.now(), cancelReason);
}
}
@@ -0,0 +1,101 @@
package com.ga.api.service.settle;
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.settle.TerminationSettlementMapper;
import com.ga.core.vo.settle.TermSettleStatus;
import com.ga.core.vo.settle.TerminationSettlementResp;
import com.ga.core.vo.settle.TerminationSettlementSaveReq;
import com.ga.core.vo.settle.TerminationSettlementSearchParam;
import com.ga.core.vo.settle.TerminationSettlementVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TerminationSettlementService {
private final TerminationSettlementMapper termSettleMapper;
public PageResponse<TerminationSettlementResp> list(TerminationSettlementSearchParam param) {
param.startPage();
return PageResponse.of(termSettleMapper.selectList(param));
}
public TerminationSettlementResp detail(Long termSettleId) {
TerminationSettlementResp resp = termSettleMapper.selectById(termSettleId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(TerminationSettlementSaveReq req) {
// 해당 설계사의 해촉 정산이 이미 있는지 확인
TerminationSettlementVO existing = termSettleMapper.selectByAgent(req.getAgentId());
if (existing != null && !TermSettleStatus.CANCELLED.name().equals(existing.getStatus())) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 처리 중인 해촉 정산이 있습니다");
}
TerminationSettlementVO vo = new TerminationSettlementVO();
vo.setAgentId(req.getAgentId());
vo.setTerminationDate(req.getTerminationDate());
vo.setOrgHistoryId(req.getOrgHistoryId());
vo.setCalcMonth(req.getCalcMonth());
vo.setUnpaidRecruitAmount(req.getUnpaidRecruitAmount());
vo.setUnpaidInstallmentAmount(req.getUnpaidInstallmentAmount());
vo.setUnpaidIncentiveAmount(req.getUnpaidIncentiveAmount());
vo.setChargebackExpectedAmount(req.getChargebackExpectedAmount());
vo.setChargebackWaivedAmount(req.getChargebackWaivedAmount());
vo.setDeductionAmount(req.getDeductionAmount());
vo.setFinalPayAmount(req.getFinalPayAmount());
vo.setNote(req.getNote());
vo.setStatus(TermSettleStatus.CALCULATING.name());
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
termSettleMapper.insert(vo);
return vo.getTermSettleId();
}
@Transactional
public void update(Long termSettleId, TerminationSettlementSaveReq req) {
TerminationSettlementVO vo = termSettleMapper.selectVoById(termSettleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!TermSettleStatus.CALCULATING.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "산출 중 상태만 수정 가능합니다");
}
vo.setUnpaidRecruitAmount(req.getUnpaidRecruitAmount());
vo.setUnpaidInstallmentAmount(req.getUnpaidInstallmentAmount());
vo.setUnpaidIncentiveAmount(req.getUnpaidIncentiveAmount());
vo.setChargebackExpectedAmount(req.getChargebackExpectedAmount());
vo.setChargebackWaivedAmount(req.getChargebackWaivedAmount());
vo.setDeductionAmount(req.getDeductionAmount());
vo.setFinalPayAmount(req.getFinalPayAmount());
vo.setNote(req.getNote());
vo.setUpdatedBy(SecurityUtil.getCurrentUserId());
termSettleMapper.update(vo);
}
@Transactional
public void confirm(Long termSettleId) {
TerminationSettlementVO vo = termSettleMapper.selectVoById(termSettleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!TermSettleStatus.CALCULATING.name().equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "산출 중 상태만 확정 가능합니다");
}
termSettleMapper.updateStatus(termSettleId, TermSettleStatus.CONFIRMED.name(), SecurityUtil.getCurrentUserId());
}
/**
* 해촉 시 자동 생성 처리 (POST /process).
* agentId를 받아 CALCULATING 상태로 해촉 정산 레코드 생성.
* 실제 금액 산출은 배치 또는 별도 update 호출로 완성.
*/
@Transactional
public Long process(TerminationSettlementSaveReq req) {
return create(req);
}
}
@@ -0,0 +1,110 @@
package com.ga.api.service.training;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.training.AgentTrainingMapper;
import com.ga.core.mapper.training.TrainingRequirementMapper;
import com.ga.core.vo.training.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AgentTrainingService {
private final AgentTrainingMapper trainingMapper;
private final TrainingRequirementMapper requirementMapper;
// ── Training ──────────────────────────────────────────────────────────────
public PageResponse<AgentTrainingResp> list(AgentTrainingSearchParam param) {
param.startPage();
return PageResponse.of(trainingMapper.selectList(param));
}
public AgentTrainingResp detail(Long trainingId) {
AgentTrainingResp resp = trainingMapper.selectById(trainingId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
/**
* 설계사 + 연도 기준 연간 이수 시간 합계.
* 의무 시간 충족 여부도 함께 반환.
*/
public BigDecimal annualHours(Long agentId, int year) {
BigDecimal hours = trainingMapper.selectAnnualHoursByAgent(agentId, year);
return hours != null ? hours : BigDecimal.ZERO;
}
@Transactional
public Long create(AgentTrainingSaveReq req) {
AgentTrainingVO vo = new AgentTrainingVO();
vo.setAgentId(req.getAgentId());
vo.setCourseName(req.getCourseName());
vo.setCourseType(req.getCourseType());
vo.setTrainingDate(req.getTrainingDate());
vo.setHours(req.getHours());
vo.setCertificateNo(req.getCertificateNo());
vo.setInstructor(req.getInstructor());
trainingMapper.insert(vo);
return vo.getTrainingId();
}
@Transactional
public void update(Long trainingId, AgentTrainingSaveReq req) {
AgentTrainingResp existing = trainingMapper.selectById(trainingId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
AgentTrainingVO vo = new AgentTrainingVO();
vo.setTrainingId(trainingId);
vo.setAgentId(req.getAgentId());
vo.setCourseName(req.getCourseName());
vo.setCourseType(req.getCourseType());
vo.setTrainingDate(req.getTrainingDate());
vo.setHours(req.getHours());
vo.setCertificateNo(req.getCertificateNo());
vo.setInstructor(req.getInstructor());
trainingMapper.update(vo);
}
@Transactional
public void delete(Long trainingId) {
AgentTrainingResp existing = trainingMapper.selectById(trainingId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
trainingMapper.delete(trainingId);
}
// ── Requirement ───────────────────────────────────────────────────────────
public List<TrainingRequirementResp> listRequirements(TrainingRequirementSearchParam param) {
return requirementMapper.selectList(param);
}
public TrainingRequirementResp requirementByYear(int year) {
TrainingRequirementResp resp = requirementMapper.selectByYear(year);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public void saveRequirement(TrainingRequirementSaveReq req) {
TrainingRequirementVO existing = new TrainingRequirementVO();
existing.setTargetYear(req.getTargetYear());
existing.setRequiredHours(req.getRequiredHours());
existing.setDescription(req.getDescription());
// upsert: try update first, insert if 0 rows
TrainingRequirementResp curr = requirementMapper.selectByYear(req.getTargetYear());
if (curr == null) {
requirementMapper.insert(existing);
} else {
existing.setReqId(curr.getReqId());
requirementMapper.update(existing);
}
}
}
@@ -0,0 +1,106 @@
package com.ga.api.service.withdraw;
import com.ga.api.service.approval.ApprovalService;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
import com.ga.core.vo.approval.ApprovalRequestSaveReq;
import com.ga.core.vo.withdraw.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class WithdrawRequestService {
private final WithdrawRequestMapper mapper;
private final ApprovalService approvalService;
/** 출금 신청 기본 결재선 코드 (application.yml 외부화 권장) */
@Value("${app.withdraw.approval-line-id:1}")
private Long defaultApprovalLineId;
public PageResponse<WithdrawRequestResp> list(WithdrawRequestSearchParam param) {
param.startPage();
java.util.List<WithdrawRequestResp> rows = mapper.selectList(param);
rows.forEach(this::maskAccountNo);
return PageResponse.of(rows);
}
public WithdrawRequestResp detail(Long requestId) {
WithdrawRequestResp resp = mapper.selectById(requestId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
maskAccountNo(resp);
return resp;
}
/**
* 출금 신청 등록 + 결재 요청 자동 연동.
* account_no 마스킹은 응답 시에만 적용 — VO 저장 시 원문 저장.
*/
@Transactional
public Long create(WithdrawRequestSaveReq req) {
WithdrawRequestVO vo = new WithdrawRequestVO();
vo.setSettleMasterId(req.getSettleMasterId());
vo.setAgentId(req.getAgentId());
vo.setAmount(req.getAmount());
vo.setRequestedBy(req.getRequestedBy());
vo.setRequestedAt(LocalDateTime.now());
vo.setBankCode(req.getBankCode());
vo.setAccountNo(req.getAccountNo()); // 원문 저장 (EncryptTypeHandler가 암호화)
vo.setStatus(WithdrawStatus.REQUESTED.name());
mapper.insert(vo);
// 결재 요청 자동 생성
try {
ApprovalRequestSaveReq approvalReq = new ApprovalRequestSaveReq();
approvalReq.setTargetType("WITHDRAW");
approvalReq.setTargetId(vo.getRequestId());
approvalReq.setLineId(defaultApprovalLineId);
approvalReq.setRequestedBy(req.getRequestedBy());
Long approvalRequestId = approvalService.createRequest(approvalReq);
// 결재 요청 ID를 withdraw_request에 업데이트 (approve 후 연동 위해)
log.info("출금신청 결재 요청 생성: withdrawId={}, approvalRequestId={}", vo.getRequestId(), approvalRequestId);
} catch (Exception e) {
log.warn("결재 요청 생성 실패 (출금신청은 유지): withdrawId={}, error={}", vo.getRequestId(), e.getMessage());
}
return vo.getRequestId();
}
@Transactional
public void cancel(Long requestId) {
WithdrawRequestResp existing = mapper.selectById(requestId);
if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!WithdrawStatus.REQUESTED.name().equals(existing.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "신청 상태에서만 취소 가능합니다");
}
mapper.cancel(requestId);
}
/**
* account_no 마스킹: 뒤 4자리만 표출.
* 원본: "123-456-78901234" → 마스킹: "*** *** *1234"
*/
private void maskAccountNo(WithdrawRequestResp resp) {
String raw = resp.getAccountNo();
if (raw == null || raw.isBlank()) return;
// 숫자만 추출 후 뒤 4자리만 남기고 마스킹
String digits = raw.replaceAll("[^0-9]", "");
if (digits.length() <= 4) {
resp.setAccountNo("*** *** *" + digits);
} else {
String last4 = digits.substring(digits.length() - 4);
resp.setAccountNo("*** *** *" + last4);
}
}
}