feat: 추천 작업 5종 일괄 진행 (4 agents 병렬)

[A] 등록 모달 7페이지 (frontend)
- CompanyList / ProductList / Commission / Payout / Override / Chargeback / ExceptionCode
- ModalForm 580px, Switch Y/N 변환, 날짜 dayjs, PermissionButton
- api/company/product/rule.ts 에 SaveReq + create/update/remove 추가

[B] BatchRun + RoleList 보강 (frontend)
- BatchRun: 정산월/보험사 선택 → 실행, antd Steps 8단계 + 5초 폴링
  (refetchInterval 자동 정지), KPI 4 카드, 최근 이력
- RoleList: 좌(역할 목록) 우(메뉴×6권한 매트릭스 체크박스),
  DIRECTORY 들여쓰기, 전체 토글, Modal.confirm 후 저장

[C] 이체파일 + 은행 어댑터 (api)
- BankTransferFileGenerator 인터페이스 + GenericCsvTransferGenerator
  (UTF-8 BOM, 표준 CSV) + KbBankTransferGenerator (088, 고정폭 + 합계)
- BankTransferFactory (Spring Map 빈)
- TransferFileService: PENDING 조회 → 파일 생성 → file_storage 적재 →
  pay_file_ref 갱신 → status=SENT
- POST /api/payments/transfer-file (DataChangeLog + EXPORT 권한)
- account_no EncryptTypeHandler 자동 복호화 후 파일에 사용
- FileService.saveBytes(byte[]) 오버로드 추가
- PaymentList: 이체파일 생성 ModalForm + 다운로드 컬럼

[D] MockMvc Controller 테스트 (api)
- AbstractControllerTest (SpringBootTest + MockMvc, JwtFilter doAnswer
  로 chain 통과 처리), TestSecurityConfig (filter 빈 override)
- 5 클래스 36 테스트: Agent/Contract/Ledger/Settle/User Controller
- happy path + validation 400 + 권한 403 + 인증 401

검증:
- ./gradlew clean build 성공
- 백엔드 단위테스트 94건 (common 33 + batch 19 + api 36 + 기타 6) 통과
- 프론트 tsc -b / vite build 통과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
GA Pro
2026-05-11 00:08:32 +09:00
parent a314a95ecf
commit 87e4e3d1da
35 changed files with 2962 additions and 110 deletions
+47
View File
@@ -0,0 +1,47 @@
import api, { unwrap } from './request';
export interface BatchRunResp {
jobLogId: number;
}
export interface BatchJobLogResp {
jobLogId: number;
jobName: string;
status: 'REQUESTED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
stepName?: string;
totalCount?: number;
successCount?: number;
errorCount?: number;
startedAt?: string;
endedAt?: string;
durationMs?: number;
errorMessage?: string;
}
export interface BatchHistoryResp {
jobLogId: number;
jobName: string;
status: string;
stepName?: string;
startedAt?: string;
endedAt?: string;
durationMs?: number;
errorMessage?: string;
}
export const batchRunApi = {
run: (settleMonth: string, companyCode?: string) =>
unwrap<number>(
api.post('/api/batch/settlement/run', null, {
params: { settleMonth, companyCode },
}),
),
status: (jobLogId: number) =>
unwrap<BatchJobLogResp>(
api.get('/api/batch/settlement/status', { params: { jobLogId } }),
),
history: (size = 10) =>
unwrap<BatchHistoryResp[]>(
api.get('/api/batch/settlement/history', { params: { size } }),
),
};
+15
View File
@@ -12,6 +12,17 @@ export interface CompanyResp {
isActive: string;
}
export interface CompanySaveReq {
companyCode: string;
companyName: string;
companyType?: string;
bizNo?: string;
contactName?: string;
contactPhone?: string;
contactEmail?: string;
isActive: string;
}
export interface CompanySearchParam {
pageNum?: number;
pageSize?: number;
@@ -22,4 +33,8 @@ export interface CompanySearchParam {
export const companyApi = {
list: (p: CompanySearchParam) =>
unwrap<PageResponse<CompanyResp>>(api.get('/api/companies', { params: p })),
create: (req: CompanySaveReq) =>
unwrap<number>(api.post('/api/companies', req)),
update: (id: number, req: CompanySaveReq) =>
unwrap<void>(api.put(`/api/companies/${id}`, req)),
};
+10
View File
@@ -14,6 +14,14 @@ export interface PaymentResp {
payStatusName?: string;
failReason?: string;
settleMonth: string;
transferFileId?: number;
}
export interface TransferFileResp {
fileId: number;
fileName: string;
paymentCount: number;
totalAmount: number;
}
export const paymentApi = {
@@ -21,4 +29,6 @@ export const paymentApi = {
unwrap<PageResponse<PaymentResp>>(api.get('/api/payments', { params: p })),
updateStatus: (paymentId: number, status: string, failReason?: string) =>
unwrap<void>(api.put(`/api/payments/${paymentId}/status`, { status, failReason })),
generateTransferFile: (settleMonth: string, bankCode: string) =>
unwrap<TransferFileResp>(api.post('/api/payments/transfer-file', null, { params: { settleMonth, bankCode } })),
};
+18
View File
@@ -14,6 +14,18 @@ export interface ProductResp {
endDate?: string;
}
export interface ProductSaveReq {
productCode: string;
productName: string;
companyId: number;
insuranceType: string;
productGroup?: string;
payPeriodType?: string;
launchDate?: string;
endDate?: string;
isActive: string;
}
export interface ProductSearchParam {
pageNum?: number;
pageSize?: number;
@@ -26,4 +38,10 @@ export interface ProductSearchParam {
export const productApi = {
list: (p: ProductSearchParam) =>
unwrap<PageResponse<ProductResp>>(api.get('/api/products', { params: p })),
create: (req: ProductSaveReq) =>
unwrap<number>(api.post('/api/products', req)),
update: (id: number, req: ProductSaveReq) =>
unwrap<void>(api.put(`/api/products/${id}`, req)),
remove: (id: number) =>
unwrap<void>(api.delete(`/api/products/${id}`)),
};
+89
View File
@@ -13,6 +13,16 @@ export interface CommissionRateResp {
version: number;
}
export interface CommissionRateSaveReq {
productId: number;
commissionYear: number;
ratePct: number;
payMethodCond?: string;
minPremium?: number;
effectiveFrom?: string;
effectiveTo?: string;
}
export interface PayoutRuleResp {
ruleId: number;
gradeId: number;
@@ -27,6 +37,17 @@ export interface PayoutRuleResp {
version: number;
}
export interface PayoutRuleSaveReq {
gradeId: number;
insuranceType: string;
commissionYear: number;
payoutPct: number;
payMethodCond?: string;
performanceGrade?: string;
effectiveFrom?: string;
effectiveTo?: string;
}
export interface OverrideRuleResp {
overrideId: number;
fromGrade: number;
@@ -41,6 +62,17 @@ export interface OverrideRuleResp {
effectiveTo?: string;
}
export interface OverrideRuleSaveReq {
fromGrade: number;
toGrade: number;
overridePct: number;
calcType?: string;
insuranceTypeCond?: string;
capAmount?: number;
effectiveFrom?: string;
effectiveTo?: string;
}
export interface ChargebackRuleResp {
cbRuleId: number;
companyCode: string;
@@ -55,6 +87,19 @@ export interface ChargebackRuleResp {
effectiveTo?: string;
}
export interface ChargebackRuleSaveReq {
companyCode: string;
insuranceType: string;
lapseMonthFrom: number;
lapseMonthTo: number;
cbRate: number;
includeOverride: string;
installmentAllowed: string;
maxInstallments?: number;
effectiveFrom?: string;
effectiveTo?: string;
}
export interface ExceptionCodeResp {
exceptionCode: string;
exceptionName: string;
@@ -67,13 +112,57 @@ export interface ExceptionCodeResp {
isActive: string;
}
export interface ExceptionCodeSaveReq {
exceptionCode: string;
exceptionName: string;
category?: string;
direction: string;
autoCalcYn: string;
approvalRequired: string;
taxIncluded: string;
description?: string;
isActive: string;
}
export const ruleApi = {
commissionRates: (params?: { productId?: number; year?: number }) =>
unwrap<CommissionRateResp[]>(api.get('/api/rules/commission-rates', { params })),
createCommissionRate: (req: CommissionRateSaveReq) =>
unwrap<number>(api.post('/api/rules/commission-rates', req)),
updateCommissionRate: (id: number, req: CommissionRateSaveReq) =>
unwrap<void>(api.put(`/api/rules/commission-rates/${id}`, req)),
removeCommissionRate: (id: number) =>
unwrap<void>(api.delete(`/api/rules/commission-rates/${id}`)),
payoutRules: (params?: { gradeId?: number; insuranceType?: string }) =>
unwrap<PayoutRuleResp[]>(api.get('/api/rules/payout', { params })),
createPayoutRule: (req: PayoutRuleSaveReq) =>
unwrap<number>(api.post('/api/rules/payout', req)),
updatePayoutRule: (id: number, req: PayoutRuleSaveReq) =>
unwrap<void>(api.put(`/api/rules/payout/${id}`, req)),
removePayoutRule: (id: number) =>
unwrap<void>(api.delete(`/api/rules/payout/${id}`)),
overrideRules: () => unwrap<OverrideRuleResp[]>(api.get('/api/rules/override')),
createOverrideRule: (req: OverrideRuleSaveReq) =>
unwrap<number>(api.post('/api/rules/override', req)),
updateOverrideRule: (id: number, req: OverrideRuleSaveReq) =>
unwrap<void>(api.put(`/api/rules/override/${id}`, req)),
removeOverrideRule: (id: number) =>
unwrap<void>(api.delete(`/api/rules/override/${id}`)),
chargebackRules: (params?: { companyCode?: string }) =>
unwrap<ChargebackRuleResp[]>(api.get('/api/rules/chargeback', { params })),
createChargebackRule: (req: ChargebackRuleSaveReq) =>
unwrap<number>(api.post('/api/rules/chargeback', req)),
updateChargebackRule: (id: number, req: ChargebackRuleSaveReq) =>
unwrap<void>(api.put(`/api/rules/chargeback/${id}`, req)),
removeChargebackRule: (id: number) =>
unwrap<void>(api.delete(`/api/rules/chargeback/${id}`)),
exceptionCodes: () => unwrap<ExceptionCodeResp[]>(api.get('/api/rules/exception-codes')),
createExceptionCode: (req: ExceptionCodeSaveReq) =>
unwrap<void>(api.post('/api/rules/exception-codes', req)),
updateExceptionCode: (code: string, req: ExceptionCodeSaveReq) =>
unwrap<void>(api.put(`/api/rules/exception-codes/${code}`, req)),
};