feat: ga-core + ga-api + ga-batch + V13-V15 + Docker + frontend skeleton

ga-core (22 tables):
- user/role: VO + Mapper + XML (login + RBAC)
- org: Grade, Organization (CTE recursive tree), Agent + history
- product: InsuranceCompany, Product, Contract
- rule: CommissionRate, PayoutRule, OverrideRule, ChargebackRule, ExceptionTypeCode
- receive: CompanyProfile, FieldMapping, CodeMapping, RawCommissionData, ParseError
- ledger: Recruit, Maintain, Exception (batch insert + cursor)
- settle: SettleMaster (UPSERT), Override, Chargeback, Payment, Reconciliation, BatchJobLog
- All XMLs use EncryptTypeHandler for PII fields

ga-api:
- AuthController: login (lock on N fails), refresh, me, password, logout
- Domain controllers: Agent, Organization, Contract, Company, Product, Rule,
  Receive, Ledger (recruit/maintain/exception + approve), Settle (confirm/hold/release),
  Payment, User, Role (matrix), BatchTrigger
- All use @RequirePermission + @DataChangeLog

ga-batch:
- MonthlySettlementJob: 8 Steps (receiveData, transformData, reconcile,
  calcRecruit, calcMaintain, chargebackException, calcOverride, aggregate)
- DataReceiver strategy + ManualReceiver, MappingEngine stub
- CommissionCalculator (pct/tax via MoneyUtil), JobLauncherController

DB V13~V15:
- V13: 14 indexes (settle_master, ledgers, contract, chargeback, payment, raw, logs)
- V14: create_monthly_partition() helper function
- V15: v_agent_monthly_summary, v_org_settle_summary, v_chargeback_risk views

Infra:
- docker-compose: postgres + redis + api + batch + frontend
- Multi-stage Dockerfile (gradle build → JRE)

ga-frontend (skeleton):
- Vite + React 18 + TypeScript + AntD 5 + AG Grid + React Query + Zustand
- API request layer (axios + JWT), LoginPage, MainLayout (dynamic menu), Dashboard
- nginx.conf for /api proxying
This commit is contained in:
GA Pro
2026-05-09 22:00:53 +09:00
parent cef4e48e27
commit 7f3c564605
147 changed files with 6987 additions and 79 deletions
@@ -0,0 +1,53 @@
package com.ga.api.auth;
import com.ga.common.model.ApiResponse;
import com.ga.core.vo.user.UserResp;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "인증")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ApiResponse<LoginResp> login(@Valid @RequestBody LoginReq req, HttpServletRequest http) {
return ApiResponse.ok(authService.login(req, ipOf(http), http.getHeader("User-Agent")));
}
@PostMapping("/refresh")
public ApiResponse<LoginResp> refresh(@RequestBody Map<String, String> body) {
return ApiResponse.ok(authService.refresh(body.get("refreshToken")));
}
@GetMapping("/me")
public ApiResponse<UserResp> me() {
return ApiResponse.ok(authService.me());
}
@PostMapping("/password")
public ApiResponse<Void> changePassword(@RequestBody Map<String, String> body) {
authService.changePassword(body.get("oldPassword"), body.get("newPassword"));
return ApiResponse.ok();
}
@PostMapping("/logout")
public ApiResponse<Void> logout() {
authService.logout();
return ApiResponse.ok();
}
private String ipOf(HttpServletRequest req) {
String xf = req.getHeader("X-Forwarded-For");
if (xf != null && !xf.isBlank()) return xf.split(",")[0].trim();
return req.getRemoteAddr();
}
}
@@ -0,0 +1,139 @@
package com.ga.api.auth;
import com.ga.common.auth.JwtTokenProvider;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.system.SystemConfigService;
import com.ga.common.system.SystemLogMapper;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.user.UserMapper;
import com.ga.core.vo.user.UserResp;
import com.ga.core.vo.user.UserVO;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider tokenProvider;
private final SystemConfigService configService;
private final SystemLogMapper systemLogMapper;
@Transactional
public LoginResp login(LoginReq req, String ipAddress, String userAgent) {
UserVO user = userMapper.selectByLoginId(req.getLoginId());
int failLimit = configService.getInt("LOGIN_FAIL_LIMIT", 5);
if (user == null) {
saveLoginLog(null, req.getLoginId(), "FAIL", "USER_NOT_FOUND", ipAddress, userAgent);
throw new BizException(ErrorCode.LOGIN_FAIL);
}
if ("LOCKED".equals(user.getStatus())) {
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "ACCOUNT_LOCKED", ipAddress, userAgent);
throw new BizException(ErrorCode.ACCOUNT_LOCKED);
}
if ("RETIRED".equals(user.getStatus())) {
throw new BizException(ErrorCode.ACCOUNT_RETIRED);
}
if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
userMapper.incrementFailCount(user.getUserId());
int newFail = (user.getFailCount() == null ? 0 : user.getFailCount()) + 1;
if (newFail >= failLimit) {
userMapper.updateStatus(user.getUserId(), "LOCKED");
}
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "BAD_PASSWORD", ipAddress, userAgent);
throw new BizException(ErrorCode.LOGIN_FAIL);
}
// 로그인 성공
userMapper.updateLastLogin(user.getUserId());
List<String> roles = userMapper.selectRoleCodes(user.getUserId());
String access = tokenProvider.createAccessToken(user.getUserId(), user.getLoginId(), roles);
String refresh = tokenProvider.createRefreshToken(user.getUserId(), user.getLoginId());
saveLoginLog(user.getUserId(), req.getLoginId(), "SUCCESS", null, ipAddress, userAgent);
return LoginResp.builder()
.accessToken(access)
.refreshToken(refresh)
.accessTokenExpiresIn(tokenProvider.getAccessExpire())
.userId(user.getUserId())
.loginId(user.getLoginId())
.userName(user.getUserName())
.roles(roles)
.build();
}
public LoginResp refresh(String refreshToken) {
Claims claims = tokenProvider.parse(refreshToken);
if (!"REFRESH".equals(claims.get("type"))) {
throw new BizException(ErrorCode.TOKEN_INVALID);
}
Long userId = claims.get("uid", Long.class);
UserVO user = userMapper.selectById(userId);
if (user == null || !"ACTIVE".equals(user.getStatus())) {
throw new BizException(ErrorCode.UNAUTHORIZED);
}
List<String> roles = userMapper.selectRoleCodes(userId);
String newAccess = tokenProvider.createAccessToken(userId, user.getLoginId(), roles);
return LoginResp.builder()
.accessToken(newAccess)
.accessTokenExpiresIn(tokenProvider.getAccessExpire())
.userId(userId)
.loginId(user.getLoginId())
.userName(user.getUserName())
.roles(roles)
.build();
}
public UserResp me() {
Long userId = SecurityUtil.getCurrentUserId();
if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
UserResp resp = userMapper.selectDetailById(userId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
resp.setRoleCodes(userMapper.selectRoleCodes(userId));
return resp;
}
@Transactional
public void changePassword(String oldPassword, String newPassword) {
Long userId = SecurityUtil.getCurrentUserId();
if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
UserVO user = userMapper.selectById(userId);
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new BizException(ErrorCode.LOGIN_FAIL, "기존 비밀번호가 일치하지 않습니다");
}
validatePasswordPolicy(newPassword);
userMapper.updatePassword(userId, passwordEncoder.encode(newPassword));
}
public void logout() {
Long userId = SecurityUtil.getCurrentUserId();
saveLoginLog(userId, SecurityUtil.getCurrentLoginId(), "LOGOUT", null, null, null);
}
private void saveLoginLog(Long userId, String loginId, String type, String reason, String ip, String ua) {
try {
systemLogMapper.insertLoginLog(userId, loginId, type, reason, ip, ua);
} catch (Exception e) {
log.warn("Login log save failed: {}", e.getMessage());
}
}
private void validatePasswordPolicy(String password) {
int min = configService.getInt("PASSWORD_MIN_LEN", 8);
if (password == null || password.length() < min) {
throw new BizException(ErrorCode.PASSWORD_POLICY,
"비밀번호는 " + min + "자 이상이어야 합니다");
}
}
}
@@ -0,0 +1,12 @@
package com.ga.api.auth;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class LoginReq {
@NotBlank(message = "아이디는 필수입니다")
private String loginId;
@NotBlank(message = "비밀번호는 필수입니다")
private String password;
}
@@ -0,0 +1,18 @@
package com.ga.api.auth;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class LoginResp {
private String accessToken;
private String refreshToken;
private long accessTokenExpiresIn; // ms
private Long userId;
private String loginId;
private String userName;
private List<String> roles;
}
@@ -0,0 +1,71 @@
package com.ga.api.controller.batch;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.settle.BatchJobLogMapper;
import com.ga.core.vo.settle.BatchJobLogVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 배치 트리거 — 실제 배치 잡 실행은 ga-batch 모듈의 별도 Spring Boot 프로세스에서 수행.
* ga-api 에서는 batch_job_log 만 조회/요청 등록.
*
* 운영 환경에서는 메시지 큐(Kafka, Redis Streams) 또는
* Spring Batch JobLauncher REST API 로 ga-batch 프로세스를 호출하는 것을 권장.
*/
@Slf4j
@Tag(name = "배치")
@RestController
@RequestMapping("/api/batch")
@RequiredArgsConstructor
public class BatchTriggerController {
private final BatchJobLogMapper jobLogMapper;
/** 정산 배치 시작 요청 (실제 실행은 ga-batch 프로세스가 폴링/큐) */
@PostMapping("/settlement/run")
@RequirePermission(menu = "BATCH_RUN", perm = "APPROVE")
public ApiResponse<Long> runSettlement(@RequestBody Map<String, String> body) {
String settleMonth = body.get("settleMonth");
if (settleMonth == null || !settleMonth.matches("\\d{6}")) {
throw new BizException(ErrorCode.INVALID_PARAMETER, "settleMonth (yyyyMM) 가 필요합니다");
}
if (jobLogMapper.isRunning("MONTHLY_SETTLEMENT") > 0) {
throw new BizException(ErrorCode.BATCH_RUNNING);
}
BatchJobLogVO log = new BatchJobLogVO();
log.setJobName("MONTHLY_SETTLEMENT");
log.setSettleMonth(settleMonth);
log.setStatus("REQUESTED");
log.setTriggeredBy(com.ga.common.util.SecurityUtil.getCurrentUserId());
jobLogMapper.insert(log);
return ApiResponse.ok(log.getJobId());
}
@GetMapping("/status")
@RequirePermission(menu = "BATCH_RUN", perm = "READ")
public ApiResponse<BatchJobLogVO> latest(@RequestParam(defaultValue = "MONTHLY_SETTLEMENT") String jobName) {
return ApiResponse.ok(jobLogMapper.selectLatest(jobName));
}
@GetMapping("/jobs")
@RequirePermission(menu = "BATCH_RUN", perm = "READ")
public ApiResponse<List<BatchJobLogVO>> list(@RequestParam(required = false) String jobName,
@RequestParam(required = false) String settleMonth) {
return ApiResponse.ok(jobLogMapper.selectList(jobName, settleMonth));
}
@GetMapping("/jobs/{jobId}")
@RequirePermission(menu = "BATCH_RUN", perm = "READ")
public ApiResponse<BatchJobLogVO> detail(@PathVariable Long jobId) {
return ApiResponse.ok(jobLogMapper.selectById(jobId));
}
}
@@ -0,0 +1,99 @@
package com.ga.api.controller.ledger;
import com.ga.api.service.ledger.LedgerService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.excel.ExcelService;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.ledger.MaintainLedgerMapper;
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
import com.ga.core.vo.ledger.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "원장 관리")
@RestController
@RequestMapping("/api/ledger")
@RequiredArgsConstructor
public class LedgerController {
private final LedgerService service;
private final RecruitLedgerMapper recruitMapper;
private final MaintainLedgerMapper maintainMapper;
private final ExcelService excelService;
// ===== 모집 =====
@GetMapping("/recruit")
@RequirePermission(menu = "LEDGER_RECRUIT", perm = "READ")
public ApiResponse<PageResponse<LedgerResp>> listRecruit(@ModelAttribute LedgerSearchParam param) {
return ApiResponse.ok(service.listRecruit(param));
}
@GetMapping("/recruit/{ledgerId}")
@RequirePermission(menu = "LEDGER_RECRUIT", perm = "READ")
public ApiResponse<LedgerResp> detailRecruit(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailRecruit(ledgerId));
}
@GetMapping("/recruit/export")
@RequirePermission(menu = "LEDGER_RECRUIT", perm = "EXPORT")
public void exportRecruit(@ModelAttribute LedgerSearchParam param, HttpServletResponse res) {
excelService.exportLargeExcel("모집수수료원장", LedgerExcelVO.class,
() -> recruitMapper.selectCursorForExcel(param), res);
}
// ===== 유지 =====
@GetMapping("/maintain")
@RequirePermission(menu = "LEDGER_MAINTAIN", perm = "READ")
public ApiResponse<PageResponse<LedgerResp>> listMaintain(@ModelAttribute LedgerSearchParam param) {
return ApiResponse.ok(service.listMaintain(param));
}
@GetMapping("/maintain/{ledgerId}")
@RequirePermission(menu = "LEDGER_MAINTAIN", perm = "READ")
public ApiResponse<LedgerResp> detailMaintain(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailMaintain(ledgerId));
}
@GetMapping("/maintain/export")
@RequirePermission(menu = "LEDGER_MAINTAIN", perm = "EXPORT")
public void exportMaintain(@ModelAttribute LedgerSearchParam param, HttpServletResponse res) {
excelService.exportLargeExcel("유지수수료원장", LedgerExcelVO.class,
() -> maintainMapper.selectCursorForExcel(param), res);
}
// ===== 예외 =====
@GetMapping("/exception")
@RequirePermission(menu = "LEDGER_EXCEPTION", perm = "READ")
public ApiResponse<PageResponse<ExceptionLedgerResp>> listException(@ModelAttribute LedgerSearchParam param) {
return ApiResponse.ok(service.listException(param));
}
@GetMapping("/exception/{ledgerId}")
@RequirePermission(menu = "LEDGER_EXCEPTION", perm = "READ")
public ApiResponse<ExceptionLedgerResp> detailException(@PathVariable Long ledgerId) {
return ApiResponse.ok(service.detailException(ledgerId));
}
@PostMapping("/exception")
@RequirePermission(menu = "LEDGER_EXCEPTION", perm = "CREATE")
@DataChangeLog(menu = "LEDGER_EXCEPTION", table = "exception_ledger")
public ApiResponse<Long> createException(@Valid @RequestBody ExceptionLedgerSaveReq req) {
return ApiResponse.ok(service.createException(req));
}
@PutMapping("/exception/{ledgerId}/approve")
@RequirePermission(menu = "LEDGER_EXCEPTION", perm = "APPROVE")
@DataChangeLog(menu = "LEDGER_EXCEPTION", table = "exception_ledger")
public ApiResponse<Void> approveException(@PathVariable Long ledgerId,
@RequestBody Map<String, String> body) {
service.approveException(ledgerId, body.get("status"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,77 @@
package com.ga.api.controller.org;
import com.ga.api.service.org.AgentService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.excel.ExcelService;
import com.ga.common.model.ApiResponse;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.org.AgentMapper;
import com.ga.core.vo.org.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
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/agents")
@RequiredArgsConstructor
public class AgentController {
private final AgentService agentService;
private final AgentMapper agentMapper;
private final ExcelService excelService;
@GetMapping
@RequirePermission(menu = "ORG_AGENT", perm = "READ")
public ApiResponse<PageResponse<AgentResp>> list(@ModelAttribute AgentSearchParam param) {
return ApiResponse.ok(agentService.list(param));
}
@GetMapping("/{agentId}")
@RequirePermission(menu = "ORG_AGENT", perm = "READ")
public ApiResponse<AgentResp> detail(@PathVariable Long agentId) {
return ApiResponse.ok(agentService.detail(agentId));
}
@PostMapping
@RequirePermission(menu = "ORG_AGENT", perm = "CREATE")
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
public ApiResponse<Long> create(@Valid @RequestBody AgentSaveReq req) {
return ApiResponse.ok(agentService.create(req));
}
@PutMapping("/{agentId}")
@RequirePermission(menu = "ORG_AGENT", perm = "UPDATE")
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
public ApiResponse<Void> update(@PathVariable Long agentId, @Valid @RequestBody AgentSaveReq req) {
agentService.update(agentId, req);
return ApiResponse.ok();
}
@PutMapping("/{agentId}/status")
@RequirePermission(menu = "ORG_AGENT", perm = "UPDATE")
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
public ApiResponse<Void> changeStatus(@PathVariable Long agentId, @RequestBody Map<String, String> body) {
agentService.changeStatus(agentId, body.get("status"));
return ApiResponse.ok();
}
@GetMapping("/{agentId}/history")
@RequirePermission(menu = "ORG_AGENT", perm = "READ")
public ApiResponse<List<AgentOrgHistoryVO>> history(@PathVariable Long agentId) {
return ApiResponse.ok(agentService.history(agentId));
}
@GetMapping("/export")
@RequirePermission(menu = "ORG_AGENT", perm = "EXPORT")
public void export(@ModelAttribute AgentSearchParam param, HttpServletResponse response) {
excelService.exportLargeExcel("설계사목록", AgentExcelVO.class,
() -> agentMapper.selectCursorForExcel(param), response);
}
}
@@ -0,0 +1,65 @@
package com.ga.api.controller.org;
import com.ga.api.service.org.OrganizationService;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.core.vo.org.OrganizationResp;
import com.ga.core.vo.org.OrganizationVO;
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/orgs")
@RequiredArgsConstructor
public class OrganizationController {
private final OrganizationService service;
@GetMapping("/tree")
@RequirePermission(menu = "ORG_TREE", perm = "READ")
public ApiResponse<List<OrganizationResp>> tree() {
return ApiResponse.ok(service.getTree());
}
@GetMapping
@RequirePermission(menu = "ORG_TREE", perm = "READ")
public ApiResponse<List<OrganizationResp>> list(@RequestParam(required = false) String keyword,
@RequestParam(required = false) String orgType,
@RequestParam(required = false) String isActive) {
return ApiResponse.ok(service.list(keyword, orgType, isActive));
}
@GetMapping("/{orgId}")
@RequirePermission(menu = "ORG_TREE", perm = "READ")
public ApiResponse<OrganizationVO> detail(@PathVariable Long orgId) {
return ApiResponse.ok(service.get(orgId));
}
@PostMapping
@RequirePermission(menu = "ORG_TREE", perm = "CREATE")
@DataChangeLog(menu = "ORG_TREE", table = "organization")
public ApiResponse<Long> create(@RequestBody OrganizationVO vo) {
return ApiResponse.ok(service.create(vo));
}
@PutMapping("/{orgId}")
@RequirePermission(menu = "ORG_TREE", perm = "UPDATE")
@DataChangeLog(menu = "ORG_TREE", table = "organization")
public ApiResponse<Void> update(@PathVariable Long orgId, @RequestBody OrganizationVO vo) {
service.update(orgId, vo);
return ApiResponse.ok();
}
@DeleteMapping("/{orgId}")
@RequirePermission(menu = "ORG_TREE", perm = "DELETE")
@DataChangeLog(menu = "ORG_TREE", table = "organization")
public ApiResponse<Void> delete(@PathVariable Long orgId) {
service.delete(orgId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,50 @@
package com.ga.api.controller.product;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.product.InsuranceCompanyMapper;
import com.ga.core.vo.product.InsuranceCompanyVO;
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/companies")
@RequiredArgsConstructor
public class CompanyController {
private final InsuranceCompanyMapper mapper;
@GetMapping
@RequirePermission(menu = "COMPANY", perm = "READ")
public ApiResponse<List<InsuranceCompanyVO>> list(@RequestParam(required = false, defaultValue = "false") boolean activeOnly) {
return ApiResponse.ok(activeOnly ? mapper.selectActive() : mapper.selectAll());
}
@GetMapping("/{companyId}")
@RequirePermission(menu = "COMPANY", perm = "READ")
public ApiResponse<InsuranceCompanyVO> detail(@PathVariable Long companyId) {
return ApiResponse.ok(mapper.selectById(companyId));
}
@PostMapping
@RequirePermission(menu = "COMPANY", perm = "CREATE")
@DataChangeLog(menu = "COMPANY", table = "insurance_company")
public ApiResponse<Long> create(@RequestBody InsuranceCompanyVO vo) {
mapper.insert(vo);
return ApiResponse.ok(vo.getCompanyId());
}
@PutMapping("/{companyId}")
@RequirePermission(menu = "COMPANY", perm = "UPDATE")
@DataChangeLog(menu = "COMPANY", table = "insurance_company")
public ApiResponse<Void> update(@PathVariable Long companyId, @RequestBody InsuranceCompanyVO vo) {
vo.setCompanyId(companyId);
mapper.update(vo);
return ApiResponse.ok();
}
}
@@ -0,0 +1,60 @@
package com.ga.api.controller.product;
import com.ga.api.service.product.ContractService;
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.product.ContractResp;
import com.ga.core.vo.product.ContractSaveReq;
import com.ga.core.vo.product.ContractSearchParam;
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/contracts")
@RequiredArgsConstructor
public class ContractController {
private final ContractService service;
@GetMapping
@RequirePermission(menu = "CONTRACT_LIST", perm = "READ")
public ApiResponse<PageResponse<ContractResp>> list(@ModelAttribute ContractSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{contractId}")
@RequirePermission(menu = "CONTRACT_LIST", perm = "READ")
public ApiResponse<ContractResp> detail(@PathVariable Long contractId) {
return ApiResponse.ok(service.detail(contractId));
}
@PostMapping
@RequirePermission(menu = "CONTRACT_LIST", perm = "CREATE")
@DataChangeLog(menu = "CONTRACT_LIST", table = "contract")
public ApiResponse<Long> create(@Valid @RequestBody ContractSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{contractId}")
@RequirePermission(menu = "CONTRACT_LIST", perm = "UPDATE")
@DataChangeLog(menu = "CONTRACT_LIST", table = "contract")
public ApiResponse<Void> update(@PathVariable Long contractId, @Valid @RequestBody ContractSaveReq req) {
service.update(contractId, req);
return ApiResponse.ok();
}
@PutMapping("/{contractId}/status")
@RequirePermission(menu = "CONTRACT_LIST", perm = "UPDATE")
@DataChangeLog(menu = "CONTRACT_LIST", table = "contract")
public ApiResponse<Void> changeStatus(@PathVariable Long contractId, @RequestBody Map<String, String> body) {
service.changeStatus(contractId, body.get("status"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,68 @@
package com.ga.api.controller.product;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.product.ProductMapper;
import com.ga.core.vo.product.ProductResp;
import com.ga.core.vo.product.ProductVO;
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/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductMapper mapper;
@GetMapping
@RequirePermission(menu = "PRODUCT", perm = "READ")
public ApiResponse<List<ProductResp>> list(@RequestParam(required = false) Long companyId,
@RequestParam(required = false) String insuranceType,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String isActive) {
return ApiResponse.ok(mapper.selectList(companyId, insuranceType, keyword, isActive));
}
@GetMapping("/{productId}")
@RequirePermission(menu = "PRODUCT", perm = "READ")
public ApiResponse<ProductResp> detail(@PathVariable Long productId) {
ProductResp r = mapper.selectDetailById(productId);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
return ApiResponse.ok(r);
}
@PostMapping
@RequirePermission(menu = "PRODUCT", perm = "CREATE")
@DataChangeLog(menu = "PRODUCT", table = "product")
public ApiResponse<Long> create(@RequestBody ProductVO vo) {
if (mapper.existsByCode(vo.getCompanyId(), vo.getProductCode()) > 0) {
throw new BizException(ErrorCode.DUPLICATE_DATA);
}
mapper.insert(vo);
return ApiResponse.ok(vo.getProductId());
}
@PutMapping("/{productId}")
@RequirePermission(menu = "PRODUCT", perm = "UPDATE")
@DataChangeLog(menu = "PRODUCT", table = "product")
public ApiResponse<Void> update(@PathVariable Long productId, @RequestBody ProductVO vo) {
vo.setProductId(productId);
mapper.update(vo);
return ApiResponse.ok();
}
@DeleteMapping("/{productId}")
@RequirePermission(menu = "PRODUCT", perm = "DELETE")
public ApiResponse<Void> delete(@PathVariable Long productId) {
mapper.deleteById(productId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,110 @@
package com.ga.api.controller.receive;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.receive.ReceiveMapper;
import com.ga.core.vo.receive.*;
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/receive")
@RequiredArgsConstructor
public class ReceiveController {
private final ReceiveMapper mapper;
// 보험사 프로파일
@GetMapping("/profiles")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "READ")
public ApiResponse<List<CompanyProfileVO>> profiles() {
return ApiResponse.ok(mapper.selectAllProfiles());
}
@PutMapping("/profiles/{companyCode}")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "UPDATE")
public ApiResponse<Void> updateProfile(@PathVariable String companyCode, @RequestBody CompanyProfileVO vo) {
vo.setCompanyCode(companyCode);
if (mapper.selectProfile(companyCode) == null) mapper.insertProfile(vo);
else mapper.updateProfile(vo);
return ApiResponse.ok();
}
// 필드 매핑
@GetMapping("/mapping/{companyCode}/fields")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "READ")
public ApiResponse<List<CompanyFieldMappingVO>> fields(@PathVariable String companyCode,
@RequestParam(required = false) String targetTable) {
return ApiResponse.ok(mapper.selectFieldMappings(companyCode, targetTable));
}
@PostMapping("/mapping/{companyCode}/fields")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "CREATE")
public ApiResponse<Long> createField(@PathVariable String companyCode, @RequestBody CompanyFieldMappingVO vo) {
vo.setCompanyCode(companyCode);
mapper.insertFieldMapping(vo);
return ApiResponse.ok(vo.getMappingId());
}
@PutMapping("/mapping/fields/{mappingId}")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "UPDATE")
public ApiResponse<Void> updateField(@PathVariable Long mappingId, @RequestBody CompanyFieldMappingVO vo) {
vo.setMappingId(mappingId);
mapper.updateFieldMapping(vo);
return ApiResponse.ok();
}
@DeleteMapping("/mapping/fields/{mappingId}")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "DELETE")
public ApiResponse<Void> deleteField(@PathVariable Long mappingId) {
mapper.deleteFieldMapping(mappingId);
return ApiResponse.ok();
}
// 코드 매핑
@GetMapping("/mapping/{companyCode}/codes")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "READ")
public ApiResponse<List<CodeMappingVO>> codes(@PathVariable String companyCode,
@RequestParam(required = false) String codeType) {
return ApiResponse.ok(mapper.selectCodeMappings(companyCode, codeType));
}
@PostMapping("/mapping/{companyCode}/codes")
@RequirePermission(menu = "RECEIVE_MAPPING", perm = "CREATE")
public ApiResponse<Long> createCode(@PathVariable String companyCode, @RequestBody CodeMappingVO vo) {
vo.setCompanyCode(companyCode);
mapper.insertCodeMapping(vo);
return ApiResponse.ok(vo.getCodeId());
}
// 원본 데이터 + 에러
@GetMapping("/raw")
@RequirePermission(menu = "RECEIVE_DATA", perm = "READ")
public ApiResponse<List<RawCommissionDataVO>> rawList(@RequestParam(required = false) String companyCode,
@RequestParam(required = false) String settleMonth,
@RequestParam(required = false) String parseStatus) {
return ApiResponse.ok(mapper.selectRawList(companyCode, settleMonth, parseStatus));
}
@GetMapping("/errors")
@RequirePermission(menu = "RECEIVE_DATA", perm = "READ")
public ApiResponse<List<ParseErrorLogVO>> errors(@RequestParam(required = false) String companyCode,
@RequestParam(required = false) String resolveStatus) {
return ApiResponse.ok(mapper.selectErrors(companyCode, resolveStatus));
}
@PutMapping("/errors/{errorId}/resolve")
@RequirePermission(menu = "RECEIVE_DATA", perm = "UPDATE")
public ApiResponse<Void> resolveError(@PathVariable Long errorId, @RequestBody Map<String, String> body) {
mapper.resolveError(errorId,
body.getOrDefault("status", "RESOLVED"),
body.get("note"),
com.ga.common.util.SecurityUtil.getCurrentUserId());
return ApiResponse.ok();
}
}
@@ -0,0 +1,166 @@
package com.ga.api.controller.rule;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.rule.RuleMapper;
import com.ga.core.vo.rule.*;
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/rules")
@RequiredArgsConstructor
public class RuleController {
private final RuleMapper mapper;
// ========== commission_rate ==========
@GetMapping("/commission-rates")
@RequirePermission(menu = "RULE_COMMISSION", perm = "READ")
public ApiResponse<List<CommissionRateVO>> listCommissionRates(@RequestParam(required = false) Long productId,
@RequestParam(required = false) Integer year) {
return ApiResponse.ok(mapper.selectCommissionRates(productId, year));
}
@PostMapping("/commission-rates")
@RequirePermission(menu = "RULE_COMMISSION", perm = "CREATE")
@DataChangeLog(menu = "RULE_COMMISSION", table = "commission_rate")
public ApiResponse<Long> createCommissionRate(@RequestBody CommissionRateVO vo) {
mapper.insertCommissionRate(vo);
return ApiResponse.ok(vo.getRateId());
}
@PutMapping("/commission-rates/{rateId}")
@RequirePermission(menu = "RULE_COMMISSION", perm = "UPDATE")
@DataChangeLog(menu = "RULE_COMMISSION", table = "commission_rate")
public ApiResponse<Void> updateCommissionRate(@PathVariable Long rateId, @RequestBody CommissionRateVO vo) {
vo.setRateId(rateId);
mapper.updateCommissionRate(vo);
return ApiResponse.ok();
}
@DeleteMapping("/commission-rates/{rateId}")
@RequirePermission(menu = "RULE_COMMISSION", perm = "DELETE")
public ApiResponse<Void> deleteCommissionRate(@PathVariable Long rateId) {
mapper.deleteCommissionRate(rateId);
return ApiResponse.ok();
}
// ========== payout_rule ==========
@GetMapping("/payout")
@RequirePermission(menu = "RULE_PAYOUT", perm = "READ")
public ApiResponse<List<PayoutRuleVO>> listPayoutRules(@RequestParam(required = false) Long gradeId,
@RequestParam(required = false) String insuranceType) {
return ApiResponse.ok(mapper.selectPayoutRules(gradeId, insuranceType));
}
@PostMapping("/payout")
@RequirePermission(menu = "RULE_PAYOUT", perm = "CREATE")
@DataChangeLog(menu = "RULE_PAYOUT", table = "payout_rule")
public ApiResponse<Long> createPayoutRule(@RequestBody PayoutRuleVO vo) {
mapper.insertPayoutRule(vo);
return ApiResponse.ok(vo.getRuleId());
}
@PutMapping("/payout/{ruleId}")
@RequirePermission(menu = "RULE_PAYOUT", perm = "UPDATE")
@DataChangeLog(menu = "RULE_PAYOUT", table = "payout_rule")
public ApiResponse<Void> updatePayoutRule(@PathVariable Long ruleId, @RequestBody PayoutRuleVO vo) {
vo.setRuleId(ruleId);
mapper.updatePayoutRule(vo);
return ApiResponse.ok();
}
@DeleteMapping("/payout/{ruleId}")
@RequirePermission(menu = "RULE_PAYOUT", perm = "DELETE")
public ApiResponse<Void> deletePayoutRule(@PathVariable Long ruleId) {
mapper.deletePayoutRule(ruleId);
return ApiResponse.ok();
}
// ========== override_rule ==========
@GetMapping("/override")
@RequirePermission(menu = "RULE_OVERRIDE", perm = "READ")
public ApiResponse<List<OverrideRuleVO>> listOverrideRules() {
return ApiResponse.ok(mapper.selectOverrideRules());
}
@PostMapping("/override")
@RequirePermission(menu = "RULE_OVERRIDE", perm = "CREATE")
public ApiResponse<Long> createOverrideRule(@RequestBody OverrideRuleVO vo) {
mapper.insertOverrideRule(vo);
return ApiResponse.ok(vo.getOverrideId());
}
@PutMapping("/override/{overrideId}")
@RequirePermission(menu = "RULE_OVERRIDE", perm = "UPDATE")
public ApiResponse<Void> updateOverrideRule(@PathVariable Long overrideId, @RequestBody OverrideRuleVO vo) {
vo.setOverrideId(overrideId);
mapper.updateOverrideRule(vo);
return ApiResponse.ok();
}
@DeleteMapping("/override/{overrideId}")
@RequirePermission(menu = "RULE_OVERRIDE", perm = "DELETE")
public ApiResponse<Void> deleteOverrideRule(@PathVariable Long overrideId) {
mapper.deleteOverrideRule(overrideId);
return ApiResponse.ok();
}
// ========== chargeback_rule ==========
@GetMapping("/chargeback")
@RequirePermission(menu = "RULE_CHARGEBACK", perm = "READ")
public ApiResponse<List<ChargebackRuleVO>> listChargebackRules(@RequestParam(required = false) String companyCode) {
return ApiResponse.ok(mapper.selectChargebackRules(companyCode));
}
@PostMapping("/chargeback")
@RequirePermission(menu = "RULE_CHARGEBACK", perm = "CREATE")
public ApiResponse<Long> createChargebackRule(@RequestBody ChargebackRuleVO vo) {
mapper.insertChargebackRule(vo);
return ApiResponse.ok(vo.getCbRuleId());
}
@PutMapping("/chargeback/{cbRuleId}")
@RequirePermission(menu = "RULE_CHARGEBACK", perm = "UPDATE")
public ApiResponse<Void> updateChargebackRule(@PathVariable Long cbRuleId, @RequestBody ChargebackRuleVO vo) {
vo.setCbRuleId(cbRuleId);
mapper.updateChargebackRule(vo);
return ApiResponse.ok();
}
@DeleteMapping("/chargeback/{cbRuleId}")
@RequirePermission(menu = "RULE_CHARGEBACK", perm = "DELETE")
public ApiResponse<Void> deleteChargebackRule(@PathVariable Long cbRuleId) {
mapper.deleteChargebackRule(cbRuleId);
return ApiResponse.ok();
}
// ========== exception_type_code ==========
@GetMapping("/exception-codes")
@RequirePermission(menu = "RULE_EXCEPTION", perm = "READ")
public ApiResponse<List<ExceptionTypeCodeVO>> listExceptionCodes() {
return ApiResponse.ok(mapper.selectExceptionCodes());
}
@PostMapping("/exception-codes")
@RequirePermission(menu = "RULE_EXCEPTION", perm = "CREATE")
public ApiResponse<Void> createExceptionCode(@RequestBody ExceptionTypeCodeVO vo) {
mapper.insertExceptionCode(vo);
return ApiResponse.ok();
}
@PutMapping("/exception-codes/{exceptionCode}")
@RequirePermission(menu = "RULE_EXCEPTION", perm = "UPDATE")
public ApiResponse<Void> updateExceptionCode(@PathVariable String exceptionCode,
@RequestBody ExceptionTypeCodeVO vo) {
vo.setExceptionCode(exceptionCode);
mapper.updateExceptionCode(vo);
return ApiResponse.ok();
}
}
@@ -0,0 +1,38 @@
package com.ga.api.controller.settle;
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.mapper.settle.PaymentMapper;
import com.ga.core.vo.settle.PaymentResp;
import com.ga.core.vo.settle.SettleSearchParam;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "지급 관리")
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
public class PaymentController {
private final PaymentMapper mapper;
@GetMapping
@RequirePermission(menu = "PAYMENT", perm = "READ")
public ApiResponse<PageResponse<PaymentResp>> list(@ModelAttribute SettleSearchParam param) {
param.startPage();
return ApiResponse.ok(PageResponse.of(mapper.selectList(param)));
}
@PutMapping("/{paymentId}/status")
@RequirePermission(menu = "PAYMENT", perm = "UPDATE")
@DataChangeLog(menu = "PAYMENT", table = "payment")
public ApiResponse<Void> updateStatus(@PathVariable Long paymentId, @RequestBody Map<String, String> body) {
mapper.updateStatus(paymentId, body.get("status"), body.get("failReason"));
return ApiResponse.ok();
}
}
@@ -0,0 +1,73 @@
package com.ga.api.controller.settle;
import com.ga.api.service.settle.SettleService;
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.SettleMasterResp;
import com.ga.core.vo.settle.SettleSearchParam;
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/settle")
@RequiredArgsConstructor
public class SettleController {
private final SettleService service;
@GetMapping
@RequirePermission(menu = "SETTLE_LIST", perm = "READ")
public ApiResponse<PageResponse<SettleMasterResp>> list(@ModelAttribute SettleSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{agentId}/months/{settleMonth}")
@RequirePermission(menu = "SETTLE_LIST", perm = "READ")
public ApiResponse<SettleMasterResp> detail(@PathVariable Long agentId,
@PathVariable String settleMonth) {
return ApiResponse.ok(service.detail(agentId, settleMonth));
}
@GetMapping("/summary")
@RequirePermission(menu = "SETTLE_LIST", perm = "READ")
public ApiResponse<Map<String, Object>> summary(@RequestParam String settleMonth) {
return ApiResponse.ok(service.summary(settleMonth));
}
@GetMapping("/org-summary")
@RequirePermission(menu = "SETTLE_LIST", perm = "READ")
public ApiResponse<List<Map<String, Object>>> orgSummary(@RequestParam String settleMonth) {
return ApiResponse.ok(service.orgSummary(settleMonth));
}
@PutMapping("/{settleId}/confirm")
@RequirePermission(menu = "SETTLE_LIST", perm = "APPROVE")
@DataChangeLog(menu = "SETTLE_LIST", table = "settle_master")
public ApiResponse<Void> confirm(@PathVariable Long settleId) {
service.confirm(settleId);
return ApiResponse.ok();
}
@PutMapping("/{settleId}/hold")
@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"));
return ApiResponse.ok();
}
@PutMapping("/{settleId}/release")
@RequirePermission(menu = "SETTLE_LIST", perm = "APPROVE")
@DataChangeLog(menu = "SETTLE_LIST", table = "settle_master")
public ApiResponse<Void> release(@PathVariable Long settleId) {
service.release(settleId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,81 @@
package com.ga.api.controller.system;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.annotation.RequirePermission;
import com.ga.common.model.ApiResponse;
import com.ga.core.mapper.user.RoleMapper;
import com.ga.core.vo.user.RoleVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "역할 관리")
@RestController
@RequestMapping("/api/system/roles")
@RequiredArgsConstructor
public class RoleController {
private final RoleMapper mapper;
@GetMapping
@RequirePermission(menu = "SYSTEM_ROLE", perm = "READ")
public ApiResponse<List<RoleVO>> list() {
return ApiResponse.ok(mapper.selectAll());
}
@GetMapping("/{roleId}")
@RequirePermission(menu = "SYSTEM_ROLE", perm = "READ")
public ApiResponse<RoleVO> detail(@PathVariable Long roleId) {
return ApiResponse.ok(mapper.selectById(roleId));
}
@GetMapping("/{roleId}/permissions")
@RequirePermission(menu = "SYSTEM_ROLE", perm = "READ")
public ApiResponse<List<Map<String, Object>>> permissions(@PathVariable Long roleId) {
return ApiResponse.ok(mapper.selectRolePermissions(roleId));
}
@PostMapping
@RequirePermission(menu = "SYSTEM_ROLE", perm = "CREATE")
@DataChangeLog(menu = "SYSTEM_ROLE", table = "role")
public ApiResponse<Long> create(@RequestBody RoleVO vo) {
mapper.insert(vo);
return ApiResponse.ok(vo.getRoleId());
}
@PutMapping("/{roleId}")
@RequirePermission(menu = "SYSTEM_ROLE", perm = "UPDATE")
@DataChangeLog(menu = "SYSTEM_ROLE", table = "role")
public ApiResponse<Void> update(@PathVariable Long roleId, @RequestBody RoleVO vo) {
vo.setRoleId(roleId);
mapper.update(vo);
return ApiResponse.ok();
}
@DeleteMapping("/{roleId}")
@RequirePermission(menu = "SYSTEM_ROLE", perm = "DELETE")
public ApiResponse<Void> delete(@PathVariable Long roleId) {
mapper.deleteById(roleId);
return ApiResponse.ok();
}
/** 권한 매트릭스 일괄 저장 */
@PutMapping("/{roleId}/permissions")
@RequirePermission(menu = "SYSTEM_ROLE", perm = "UPDATE")
@Transactional
public ApiResponse<Void> savePermissions(@PathVariable Long roleId,
@RequestBody List<Map<String, Object>> permissions) {
mapper.deleteRolePermissions(roleId);
for (Map<String, Object> p : permissions) {
Long menuId = ((Number) p.get("menuId")).longValue();
String permCode = (String) p.get("permCode");
String isGranted = (String) p.getOrDefault("isGranted", "Y");
mapper.insertRolePermission(roleId, menuId, permCode, isGranted);
}
return ApiResponse.ok();
}
}
@@ -0,0 +1,64 @@
package com.ga.api.controller.system;
import com.ga.api.service.system.UserService;
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.user.UserResp;
import com.ga.core.vo.user.UserSaveReq;
import com.ga.core.vo.user.UserSearchParam;
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/system/users")
@RequiredArgsConstructor
public class UserController {
private final UserService service;
@GetMapping
@RequirePermission(menu = "SYSTEM_USER", perm = "READ")
public ApiResponse<PageResponse<UserResp>> list(@ModelAttribute UserSearchParam param) {
return ApiResponse.ok(service.list(param));
}
@GetMapping("/{userId}")
@RequirePermission(menu = "SYSTEM_USER", perm = "READ")
public ApiResponse<UserResp> detail(@PathVariable Long userId) {
return ApiResponse.ok(service.detail(userId));
}
@PostMapping
@RequirePermission(menu = "SYSTEM_USER", perm = "CREATE")
@DataChangeLog(menu = "SYSTEM_USER", table = "users")
public ApiResponse<Long> create(@Valid @RequestBody UserSaveReq req) {
return ApiResponse.ok(service.create(req));
}
@PutMapping("/{userId}")
@RequirePermission(menu = "SYSTEM_USER", perm = "UPDATE")
@DataChangeLog(menu = "SYSTEM_USER", table = "users")
public ApiResponse<Void> update(@PathVariable Long userId, @Valid @RequestBody UserSaveReq req) {
service.update(userId, req);
return ApiResponse.ok();
}
@PutMapping("/{userId}/reset-password")
@RequirePermission(menu = "SYSTEM_USER", perm = "UPDATE")
public ApiResponse<Void> resetPassword(@PathVariable Long userId) {
service.resetPassword(userId);
return ApiResponse.ok();
}
@PutMapping("/{userId}/unlock")
@RequirePermission(menu = "SYSTEM_USER", perm = "UPDATE")
public ApiResponse<Void> unlock(@PathVariable Long userId) {
service.unlock(userId);
return ApiResponse.ok();
}
}
@@ -0,0 +1,85 @@
package com.ga.api.service.ledger;
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.ledger.ExceptionLedgerMapper;
import com.ga.core.mapper.ledger.MaintainLedgerMapper;
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
import com.ga.core.vo.ledger.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class LedgerService {
private final RecruitLedgerMapper recruitMapper;
private final MaintainLedgerMapper maintainMapper;
private final ExceptionLedgerMapper exceptionMapper;
public PageResponse<LedgerResp> listRecruit(LedgerSearchParam param) {
param.startPage();
return PageResponse.of(recruitMapper.selectList(param));
}
public PageResponse<LedgerResp> listMaintain(LedgerSearchParam param) {
param.startPage();
return PageResponse.of(maintainMapper.selectList(param));
}
public PageResponse<ExceptionLedgerResp> listException(LedgerSearchParam param) {
param.startPage();
return PageResponse.of(exceptionMapper.selectList(param));
}
public LedgerResp detailRecruit(Long ledgerId) {
LedgerResp r = recruitMapper.selectDetailById(ledgerId);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
return r;
}
public LedgerResp detailMaintain(Long ledgerId) {
LedgerResp r = maintainMapper.selectDetailById(ledgerId);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
return r;
}
public ExceptionLedgerResp detailException(Long ledgerId) {
ExceptionLedgerResp r = exceptionMapper.selectDetailById(ledgerId);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
return r;
}
@Transactional
public Long createException(ExceptionLedgerSaveReq req) {
ExceptionLedgerVO vo = new ExceptionLedgerVO();
vo.setAgentId(req.getAgentId());
vo.setExceptionCode(req.getExceptionCode());
vo.setSettleMonth(req.getSettleMonth());
vo.setAmount(req.getAmount());
vo.setPolicyNo(req.getPolicyNo());
vo.setCompanyCode(req.getCompanyCode());
vo.setInsuranceType(req.getInsuranceType());
vo.setDescription(req.getDescription());
vo.setRecurringYn(req.getRecurringYn());
vo.setRecurringMonths(req.getRecurringMonths());
vo.setRecurringLeft(req.getRecurringMonths());
vo.setApproveStatus("PENDING");
vo.setStatus("NEW");
exceptionMapper.insert(vo);
return vo.getLedgerId();
}
@Transactional
public void approveException(Long ledgerId, String status) {
if (!"APPROVED".equals(status) && !"REJECTED".equals(status)) {
throw new BizException(ErrorCode.INVALID_PARAMETER, "status는 APPROVED/REJECTED만 가능합니다");
}
Long approverId = SecurityUtil.getCurrentUserId();
if (approverId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
exceptionMapper.updateApprove(ledgerId, status, approverId);
}
}
@@ -0,0 +1,98 @@
package com.ga.api.service.org;
import com.ga.common.code.CommonCodeService;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.org.AgentMapper;
import com.ga.core.vo.org.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class AgentService {
private final AgentMapper mapper;
private final CommonCodeService codeService;
public PageResponse<AgentResp> list(AgentSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public AgentResp detail(Long agentId) {
AgentResp resp = mapper.selectDetailById(agentId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(AgentSaveReq req) {
if (req.getLicenseNo() != null && mapper.existsByLicenseNo(req.getLicenseNo()) > 0) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 등록된 사번입니다");
}
AgentVO vo = toVO(req, null);
vo.setStatus("ACTIVE");
mapper.insert(vo);
// 입사 이력 기록
AgentOrgHistoryVO h = new AgentOrgHistoryVO();
h.setAgentId(vo.getAgentId());
h.setToOrgId(vo.getOrgId());
h.setToGradeId(vo.getGradeId());
h.setChangeType("JOIN");
h.setChangeDate(req.getJoinDate() != null ? req.getJoinDate() : java.time.LocalDate.now());
mapper.insertHistory(h);
return vo.getAgentId();
}
@Transactional
public void update(Long agentId, AgentSaveReq req) {
AgentVO old = mapper.selectById(agentId);
if (old == null) throw new BizException(ErrorCode.NOT_FOUND);
AgentVO vo = toVO(req, agentId);
vo.setStatus(old.getStatus());
mapper.update(vo);
// 조직/직급 변경 시 이력
if (!java.util.Objects.equals(old.getOrgId(), vo.getOrgId())
|| !java.util.Objects.equals(old.getGradeId(), vo.getGradeId())) {
AgentOrgHistoryVO h = new AgentOrgHistoryVO();
h.setAgentId(agentId);
h.setFromOrgId(old.getOrgId());
h.setToOrgId(vo.getOrgId());
h.setFromGradeId(old.getGradeId());
h.setToGradeId(vo.getGradeId());
h.setChangeType(!java.util.Objects.equals(old.getGradeId(), vo.getGradeId()) ? "PROMOTE" : "TRANSFER");
h.setChangeDate(java.time.LocalDate.now());
mapper.insertHistory(h);
}
}
@Transactional
public void changeStatus(Long agentId, String status) {
codeService.validateCode("AGENT_STATUS", status);
mapper.updateStatus(agentId, status);
}
public java.util.List<AgentOrgHistoryVO> history(Long agentId) {
return mapper.selectHistory(agentId);
}
private AgentVO toVO(AgentSaveReq req, Long agentId) {
AgentVO vo = new AgentVO();
vo.setAgentId(agentId);
vo.setOrgId(req.getOrgId());
vo.setGradeId(req.getGradeId());
vo.setAgentName(req.getAgentName());
vo.setResidentNo(req.getResidentNo());
vo.setLicenseNo(req.getLicenseNo());
vo.setPhone(req.getPhone());
vo.setEmail(req.getEmail());
vo.setBankCode(req.getBankCode());
vo.setAccountNo(req.getAccountNo());
vo.setJoinDate(req.getJoinDate());
return vo;
}
}
@@ -0,0 +1,74 @@
package com.ga.api.service.org;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.core.mapper.org.OrganizationMapper;
import com.ga.core.vo.org.OrganizationResp;
import com.ga.core.vo.org.OrganizationVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
@RequiredArgsConstructor
public class OrganizationService {
private final OrganizationMapper mapper;
public List<OrganizationResp> getTree() {
List<OrganizationResp> flat = mapper.selectAllForTree();
return buildTree(flat);
}
public List<OrganizationResp> list(String keyword, String orgType, String isActive) {
return mapper.selectList(keyword, orgType, isActive);
}
public OrganizationVO get(Long orgId) {
OrganizationVO vo = mapper.selectById(orgId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
return vo;
}
@Transactional
public Long create(OrganizationVO vo) {
mapper.insert(vo);
return vo.getOrgId();
}
@Transactional
public void update(Long orgId, OrganizationVO vo) {
get(orgId);
vo.setOrgId(orgId);
mapper.update(vo);
}
@Transactional
public void delete(Long orgId) {
if (mapper.countChildren(orgId) > 0) {
throw new BizException(ErrorCode.DEPENDENT_DATA, "하위 조직이 있어 삭제할 수 없습니다");
}
if (mapper.countAgents(orgId) > 0) {
throw new BizException(ErrorCode.DEPENDENT_DATA, "소속 설계사가 있어 삭제할 수 없습니다");
}
mapper.deleteById(orgId);
}
private List<OrganizationResp> buildTree(List<OrganizationResp> flat) {
Map<Long, OrganizationResp> byId = new LinkedHashMap<>();
for (OrganizationResp o : flat) byId.put(o.getOrgId(), o);
List<OrganizationResp> roots = new ArrayList<>();
for (OrganizationResp o : flat) {
if (o.getParentOrgId() == null) {
roots.add(o);
} else {
OrganizationResp parent = byId.get(o.getParentOrgId());
if (parent != null) parent.getChildren().add(o);
else roots.add(o);
}
}
return roots;
}
}
@@ -0,0 +1,74 @@
package com.ga.api.service.product;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.product.ContractMapper;
import com.ga.core.vo.product.ContractResp;
import com.ga.core.vo.product.ContractSaveReq;
import com.ga.core.vo.product.ContractSearchParam;
import com.ga.core.vo.product.ContractVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class ContractService {
private final ContractMapper mapper;
public PageResponse<ContractResp> list(ContractSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public ContractResp detail(Long contractId) {
ContractResp resp = mapper.selectDetailById(contractId);
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
return resp;
}
@Transactional
public Long create(ContractSaveReq req) {
if (mapper.existsByPolicyNo(req.getPolicyNo()) > 0) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 등록된 증권번호입니다");
}
ContractVO vo = new ContractVO();
vo.setAgentId(req.getAgentId());
vo.setProductId(req.getProductId());
vo.setPolicyNo(req.getPolicyNo());
vo.setContractorName(req.getContractorName());
vo.setInsuredName(req.getInsuredName());
vo.setPremium(req.getPremium());
vo.setPayCycle(req.getPayCycle());
vo.setPayPeriod(req.getPayPeriod());
vo.setCoveragePeriod(req.getCoveragePeriod());
vo.setStatus("ACTIVE");
vo.setContractDate(req.getContractDate());
vo.setEffectiveDate(req.getEffectiveDate());
mapper.insert(vo);
return vo.getContractId();
}
@Transactional
public void update(Long contractId, ContractSaveReq req) {
ContractVO vo = mapper.selectById(contractId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
vo.setAgentId(req.getAgentId());
vo.setProductId(req.getProductId());
vo.setContractorName(req.getContractorName());
vo.setInsuredName(req.getInsuredName());
vo.setPremium(req.getPremium());
vo.setPayCycle(req.getPayCycle());
vo.setPayPeriod(req.getPayPeriod());
vo.setCoveragePeriod(req.getCoveragePeriod());
vo.setEffectiveDate(req.getEffectiveDate());
mapper.update(vo);
}
@Transactional
public void changeStatus(Long contractId, String status) {
mapper.updateStatus(contractId, status);
}
}
@@ -0,0 +1,72 @@
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.SettleMasterMapper;
import com.ga.core.vo.settle.SettleMasterResp;
import com.ga.core.vo.settle.SettleMasterVO;
import com.ga.core.vo.settle.SettleSearchParam;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class SettleService {
private final SettleMasterMapper masterMapper;
public PageResponse<SettleMasterResp> list(SettleSearchParam param) {
param.startPage();
return PageResponse.of(masterMapper.selectList(param));
}
public SettleMasterResp detail(Long agentId, String settleMonth) {
SettleMasterResp r = masterMapper.selectDetail(agentId, settleMonth);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
return r;
}
public Map<String, Object> summary(String settleMonth) {
return masterMapper.selectMonthSummary(settleMonth);
}
public List<Map<String, Object>> orgSummary(String settleMonth) {
return masterMapper.selectOrgSummary(settleMonth);
}
@Transactional
public void confirm(Long settleId) {
SettleMasterVO vo = masterMapper.selectById(settleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if ("CONFIRMED".equals(vo.getStatus()) || "PAID".equals(vo.getStatus())) {
throw new BizException(ErrorCode.ALREADY_CONFIRMED);
}
masterMapper.updateStatus(settleId, "CONFIRMED", SecurityUtil.getCurrentUserId());
}
@Transactional
public void hold(Long settleId, String reason) {
SettleMasterVO vo = masterMapper.selectById(settleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if ("PAID".equals(vo.getStatus())) {
throw new BizException(ErrorCode.NOT_PERMITTED, "지급완료된 정산은 보류할 수 없습니다");
}
masterMapper.updateHold(settleId, reason);
}
@Transactional
public void release(Long settleId) {
SettleMasterVO vo = masterMapper.selectById(settleId);
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
if (!"HOLD".equals(vo.getStatus())) {
throw new BizException(ErrorCode.INVALID_STATUS, "보류 상태가 아닙니다");
}
masterMapper.updateStatus(settleId, "CALCULATED", SecurityUtil.getCurrentUserId());
}
}
@@ -0,0 +1,100 @@
package com.ga.api.service.system;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.common.system.SystemConfigService;
import com.ga.core.mapper.user.UserMapper;
import com.ga.core.vo.user.UserResp;
import com.ga.core.vo.user.UserSaveReq;
import com.ga.core.vo.user.UserSearchParam;
import com.ga.core.vo.user.UserVO;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper mapper;
private final PasswordEncoder passwordEncoder;
private final SystemConfigService configService;
public PageResponse<UserResp> list(UserSearchParam param) {
param.startPage();
return PageResponse.of(mapper.selectList(param));
}
public UserResp detail(Long userId) {
UserResp r = mapper.selectDetailById(userId);
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
r.setRoleCodes(mapper.selectRoleCodes(userId));
return r;
}
@Transactional
public Long create(UserSaveReq req) {
if (mapper.selectByLoginId(req.getLoginId()) != null) {
throw new BizException(ErrorCode.DUPLICATE_DATA, "이미 사용 중인 로그인 ID입니다");
}
String initialPwd = req.getPassword();
if (initialPwd == null || initialPwd.isBlank()) {
initialPwd = "init1234!"; // 임시 — 로그인 후 변경 강제
}
UserVO vo = new UserVO();
vo.setLoginId(req.getLoginId());
vo.setPassword(passwordEncoder.encode(initialPwd));
vo.setUserName(req.getUserName());
vo.setEmail(req.getEmail());
vo.setPhone(req.getPhone());
vo.setAgentId(req.getAgentId());
vo.setStatus(req.getStatus() != null ? req.getStatus() : "ACTIVE");
mapper.insert(vo);
if (req.getRoleIds() != null) {
req.getRoleIds().forEach(rid -> mapper.insertUserRole(vo.getUserId(), rid));
}
return vo.getUserId();
}
@Transactional
public void update(Long userId, UserSaveReq req) {
UserVO old = mapper.selectById(userId);
if (old == null) throw new BizException(ErrorCode.NOT_FOUND);
UserVO vo = new UserVO();
vo.setUserId(userId);
vo.setUserName(req.getUserName());
vo.setEmail(req.getEmail());
vo.setPhone(req.getPhone());
vo.setAgentId(req.getAgentId());
vo.setStatus(req.getStatus());
mapper.update(vo);
if (req.getRoleIds() != null) {
mapper.deleteUserRoles(userId);
req.getRoleIds().forEach(rid -> mapper.insertUserRole(userId, rid));
}
}
@Transactional
public void resetPassword(Long userId) {
int min = configService.getInt("PASSWORD_MIN_LEN", 8);
String temp = "reset" + System.currentTimeMillis() % 1_000_000;
if (temp.length() < min) temp = temp + "!@#";
mapper.updatePassword(userId, passwordEncoder.encode(temp));
}
@Transactional
public void unlock(Long userId) {
mapper.resetFailCount(userId);
mapper.updateStatus(userId, "ACTIVE");
}
public List<String> roleCodes(Long userId) {
return mapper.selectRoleCodes(userId);
}
}