2026-05-11 00:08:32 +09:00
|
|
|
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';
|
2026-05-10 22:36:51 +09:00
|
|
|
import PageContainer from '@/components/common/PageContainer';
|
2026-05-11 00:08:32 +09:00
|
|
|
import PermissionButton from '@/components/common/PermissionButton';
|
|
|
|
|
import { ruleApi, ExceptionCodeResp, ExceptionCodeSaveReq } from '@/api/rule';
|
2026-05-10 22:36:51 +09:00
|
|
|
|
|
|
|
|
const DIRECTION_COLOR = { PLUS: 'success', MINUS: 'error' } as const;
|
|
|
|
|
|
2026-05-11 00:08:32 +09:00
|
|
|
const CATEGORY_OPTIONS = [
|
|
|
|
|
{ value: 'BONUS', label: '보너스' },
|
|
|
|
|
{ value: 'PENALTY', label: '패널티' },
|
|
|
|
|
{ value: 'ADJUSTMENT', label: '조정' },
|
|
|
|
|
{ value: 'OTHER', label: '기타' },
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-10 22:36:51 +09:00
|
|
|
export default function ExceptionCodeList() {
|
2026-05-11 00:08:32 +09:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-10 22:36:51 +09:00
|
|
|
const columns: ProColumns<ExceptionCodeResp>[] = [
|
|
|
|
|
{ title: '예외코드', dataIndex: 'exceptionCode', width: 120, search: false,
|
|
|
|
|
render: (_, r) => <strong>{r.exceptionCode}</strong> },
|
|
|
|
|
{ title: '예외명', dataIndex: 'exceptionName', width: 160, search: false },
|
|
|
|
|
{ title: '카테고리', dataIndex: 'category', width: 110, search: false },
|
|
|
|
|
{
|
|
|
|
|
title: '방향', dataIndex: 'direction', width: 80, align: 'center', search: false,
|
|
|
|
|
render: (_, r) => <Tag color={DIRECTION_COLOR[r.direction as 'PLUS' | 'MINUS'] ?? 'default'}>
|
|
|
|
|
{r.direction === 'PLUS' ? '가산 (+)' : '차감 (-)'}</Tag>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '자동계산', dataIndex: 'autoCalcYn', width: 90, align: 'center', search: false,
|
|
|
|
|
render: (_, r) => r.autoCalcYn === 'Y' ? <Tag color="processing">자동</Tag> : <Tag>수동</Tag>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '승인 필요', dataIndex: 'approvalRequired', width: 90, align: 'center', search: false,
|
|
|
|
|
render: (_, r) => r.approvalRequired === 'Y' ? <Tag color="warning">필요</Tag> : <Tag>불요</Tag>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '과세', dataIndex: 'taxIncluded', width: 80, align: 'center', search: false,
|
|
|
|
|
render: (_, r) => r.taxIncluded === 'Y' ? <Tag color="success">과세</Tag> : <Tag>비과세</Tag>,
|
|
|
|
|
},
|
|
|
|
|
{ title: '설명', dataIndex: 'description', ellipsis: true, search: false },
|
|
|
|
|
{
|
|
|
|
|
title: '활성', dataIndex: 'isActive', width: 70, align: 'center', search: false,
|
|
|
|
|
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
|
|
|
|
{r.isActive === 'Y' ? 'Y' : 'N'}</Tag>,
|
|
|
|
|
},
|
2026-05-11 00:08:32 +09:00
|
|
|
{
|
|
|
|
|
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>,
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-05-10 22:36:51 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PageContainer title="예외 코드" description="가산/차감/승인/세금 정책 마스터 (예외금액 원장에서 사용)">
|
|
|
|
|
<ProTable<ExceptionCodeResp>
|
2026-05-11 00:08:32 +09:00
|
|
|
actionRef={actionRef}
|
2026-05-10 22:36:51 +09:00
|
|
|
rowKey="exceptionCode"
|
|
|
|
|
columns={columns}
|
|
|
|
|
search={false}
|
|
|
|
|
request={async () => {
|
|
|
|
|
const list = await ruleApi.exceptionCodes();
|
|
|
|
|
return { data: list, total: list.length, success: true };
|
|
|
|
|
}}
|
|
|
|
|
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
|
|
|
|
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
2026-05-11 00:08:32 +09:00
|
|
|
toolBarRender={() => [
|
|
|
|
|
<PermissionButton key="create" menuCode="RULE_EXCEPTION_CODE" permCode="CREATE" type="primary"
|
|
|
|
|
onClick={openCreate}>+ 예외코드 등록</PermissionButton>,
|
|
|
|
|
]}
|
2026-05-10 22:36:51 +09:00
|
|
|
dateFormatter="string"
|
|
|
|
|
/>
|
2026-05-11 00:08:32 +09:00
|
|
|
|
|
|
|
|
<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>
|
2026-05-10 22:36:51 +09:00
|
|
|
</PageContainer>
|
|
|
|
|
);
|
|
|
|
|
}
|