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:
@@ -1,11 +1,57 @@
|
||||
import { Tag } from 'antd';
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Radio, Switch, Tag, message } from 'antd';
|
||||
import {
|
||||
ModalForm, ProForm, ProFormSelect, ProFormText, ProFormTextArea,
|
||||
ProTable, type ActionType, type ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { ruleApi, ExceptionCodeResp } from '@/api/rule';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import { ruleApi, ExceptionCodeResp, ExceptionCodeSaveReq } from '@/api/rule';
|
||||
|
||||
const DIRECTION_COLOR = { PLUS: 'success', MINUS: 'error' } as const;
|
||||
|
||||
const CATEGORY_OPTIONS = [
|
||||
{ value: 'BONUS', label: '보너스' },
|
||||
{ value: 'PENALTY', label: '패널티' },
|
||||
{ value: 'ADJUSTMENT', label: '조정' },
|
||||
{ value: 'OTHER', label: '기타' },
|
||||
];
|
||||
|
||||
export default function ExceptionCodeList() {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [modal, setModal] = useState<{ open: boolean; editCode?: string }>({ open: false });
|
||||
const [editRecord, setEditRecord] = useState<ExceptionCodeResp | null>(null);
|
||||
|
||||
const openCreate = () => {
|
||||
setEditRecord(null);
|
||||
setModal({ open: true });
|
||||
};
|
||||
|
||||
const openEdit = (record: ExceptionCodeResp) => {
|
||||
setEditRecord(record);
|
||||
setModal({ open: true, editCode: record.exceptionCode });
|
||||
};
|
||||
|
||||
const onSubmit = async (v: any) => {
|
||||
const req: ExceptionCodeSaveReq = {
|
||||
...v,
|
||||
autoCalcYn: v.autoCalcYn ? 'Y' : 'N',
|
||||
approvalRequired: v.approvalRequired ? 'Y' : 'N',
|
||||
taxIncluded: v.taxIncluded ? 'Y' : 'N',
|
||||
isActive: v.isActive ? 'Y' : 'N',
|
||||
};
|
||||
if (modal.editCode) {
|
||||
await ruleApi.updateExceptionCode(modal.editCode, req);
|
||||
message.success('수정 완료');
|
||||
} else {
|
||||
await ruleApi.createExceptionCode(req);
|
||||
message.success('등록 완료');
|
||||
}
|
||||
setModal({ open: false });
|
||||
actionRef.current?.reload();
|
||||
return true;
|
||||
};
|
||||
|
||||
const columns: ProColumns<ExceptionCodeResp>[] = [
|
||||
{ title: '예외코드', dataIndex: 'exceptionCode', width: 120, search: false,
|
||||
render: (_, r) => <strong>{r.exceptionCode}</strong> },
|
||||
@@ -34,11 +80,19 @@ export default function ExceptionCodeList() {
|
||||
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
||||
{r.isActive === 'Y' ? 'Y' : 'N'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '액션', valueType: 'option', width: 100, fixed: 'right',
|
||||
render: (_, r) => [
|
||||
<PermissionButton key="edit" menuCode="RULE_EXCEPTION_CODE" permCode="UPDATE" size="small" type="link"
|
||||
onClick={() => openEdit(r)}>수정</PermissionButton>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="예외 코드" description="가산/차감/승인/세금 정책 마스터 (예외금액 원장에서 사용)">
|
||||
<ProTable<ExceptionCodeResp>
|
||||
actionRef={actionRef}
|
||||
rowKey="exceptionCode"
|
||||
columns={columns}
|
||||
search={false}
|
||||
@@ -48,8 +102,70 @@ export default function ExceptionCodeList() {
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
toolBarRender={() => [
|
||||
<PermissionButton key="create" menuCode="RULE_EXCEPTION_CODE" permCode="CREATE" type="primary"
|
||||
onClick={openCreate}>+ 예외코드 등록</PermissionButton>,
|
||||
]}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
|
||||
<ModalForm
|
||||
title={modal.editCode ? '예외코드 수정' : '예외코드 등록'}
|
||||
open={modal.open}
|
||||
onOpenChange={(o) => !o && setModal({ open: false })}
|
||||
width={580}
|
||||
layout="vertical"
|
||||
key={modal.editCode ?? 'new'}
|
||||
initialValues={editRecord ? {
|
||||
...editRecord,
|
||||
autoCalcYn: editRecord.autoCalcYn === 'Y',
|
||||
approvalRequired: editRecord.approvalRequired === 'Y',
|
||||
taxIncluded: editRecord.taxIncluded === 'Y',
|
||||
isActive: editRecord.isActive === 'Y',
|
||||
} : { direction: 'PLUS', autoCalcYn: false, approvalRequired: false, taxIncluded: false, isActive: true }}
|
||||
onFinish={onSubmit}
|
||||
submitter={{ searchConfig: { submitText: modal.editCode ? '수정' : '등록', resetText: '취소' } }}
|
||||
modalProps={{ destroyOnClose: true, maskClosable: false }}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="exceptionCode" label="예외코드" rules={[{ required: true }]}
|
||||
width="md" placeholder="EX001"
|
||||
disabled={!!modal.editCode}
|
||||
/>
|
||||
<ProFormText
|
||||
name="exceptionName" label="예외명" rules={[{ required: true }]}
|
||||
width="md" placeholder="특별 가산금"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
name="category" label="카테고리" width="md"
|
||||
options={CATEGORY_OPTIONS}
|
||||
/>
|
||||
<ProForm.Item name="direction" label="방향" rules={[{ required: true }]}>
|
||||
<Radio.Group>
|
||||
<Radio value="PLUS">가산 (+)</Radio>
|
||||
<Radio value="MINUS">차감 (-)</Radio>
|
||||
</Radio.Group>
|
||||
</ProForm.Item>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProForm.Item name="autoCalcYn" label="자동계산" valuePropName="checked">
|
||||
<Switch checkedChildren="자동" unCheckedChildren="수동" />
|
||||
</ProForm.Item>
|
||||
<ProForm.Item name="approvalRequired" label="승인 필요" valuePropName="checked" style={{ marginLeft: 32 }}>
|
||||
<Switch checkedChildren="필요" unCheckedChildren="불요" />
|
||||
</ProForm.Item>
|
||||
<ProForm.Item name="taxIncluded" label="과세" valuePropName="checked" style={{ marginLeft: 32 }}>
|
||||
<Switch checkedChildren="과세" unCheckedChildren="비과세" />
|
||||
</ProForm.Item>
|
||||
<ProForm.Item name="isActive" label="활성" valuePropName="checked" style={{ marginLeft: 32 }}>
|
||||
<Switch checkedChildren="활성" unCheckedChildren="비활성" />
|
||||
</ProForm.Item>
|
||||
</ProForm.Group>
|
||||
<ProFormTextArea name="description" label="설명" placeholder="예외 코드 상세 설명" />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user