feat: 테스트 데이터(V17) + 실 DB 프로파일 + 프론트 화면 확장
V17 (192.168.0.60:55432/trading_ai/ga 적용 완료):
- insurance_company 5 (삼성/한화/교보/디비/KB)
- product 20 (보험사별 4개)
- organization 7 (본부 1, 지점 3, 팀 3)
- agent 50 (GM 3 / DM 3 / SMGR 6 / SFC 12 / FC 26)
- contract 200 (ACTIVE 190 + LAPSE 10)
- commission_rate 100 (5상품 × 5년차 × 5보험사 = 일부)
실제 INSERT는 상품 20개 × 1년차 + 20 × 4년차 = 100건
- payout_rule 120 (직급 6 × 보험종류 4 × 연차 5)
- override_rule 10 (FC/SFC/MGR/SMGR/DM 직급 간 오버라이드)
- chargeback_rule 4 (실효월수 구간별 환수율)
운영 DB 프로파일:
- application-trading_ai.yml (ga-api, ga-batch)
- 192.168.0.60:55432 / trading_ai / currentSchema=ga
- Flyway baseline-version=17 (수동 적용 분 인정)
DB 포트 변경 반영: 5432 → 55432 (운영 변경)
프론트 추가 화면:
- DataGrid (AG Grid Community 래퍼; 합계행/셀편집/로딩 오버레이)
- 예외원장 (ExceptionLedger): 승인/반려 액션
- 지급관리 (PaymentList)
- 정산 배치 (BatchRun): 실행 + 진행률 폴링
- 시스템관리:
* UserList: 비밀번호 초기화 + 잠금 해제
* RoleList: 좌측 역할 + 우측 권한 매트릭스 (메뉴 × 6권한)
* CodeList: 좌측 그룹 + 우측 상세 코드, 시스템그룹 경고
설계사 상세/등록 폼:
- AgentDetail: 통계 카드 + 기본정보 + 수정 버튼
- AgentForm: 등록/수정 통합 (id='new' 또는 :id/edit)
라우터:
- /org/agents/{new|:id|:id/edit}
- /ledger/exception, /payments, /settle/batch
- /system/{users|roles|codes}
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Card, DatePicker, Descriptions, message, Progress, Space } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import { batchApi } from '@/api/settle';
|
||||
|
||||
export default function BatchRun() {
|
||||
const [settleMonth, setSettleMonth] = useState<Dayjs | null>(dayjs());
|
||||
|
||||
const { data: status, refetch } = useQuery({
|
||||
queryKey: ['batch', 'status'],
|
||||
queryFn: batchApi.status,
|
||||
refetchInterval: (q) => {
|
||||
const s = (q.state.data as any)?.status;
|
||||
return s === 'STARTED' || s === 'RUNNING' ? 3000 : false;
|
||||
},
|
||||
});
|
||||
|
||||
const onRun = async () => {
|
||||
if (!settleMonth) { message.warning('정산월을 선택하세요'); return; }
|
||||
try {
|
||||
await batchApi.run(settleMonth.format('YYYYMM'));
|
||||
message.success('배치 실행 요청됨');
|
||||
refetch();
|
||||
} catch {
|
||||
// 인터셉터에서 처리
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer title="정산 배치 실행">
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Space>
|
||||
<DatePicker picker="month" value={settleMonth} onChange={setSettleMonth} />
|
||||
<Button type="primary" onClick={onRun}>배치 실행</Button>
|
||||
<Button onClick={() => refetch()}>상태 새로고침</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{status && (
|
||||
<Card title="최근 배치 상태">
|
||||
<Descriptions column={2} bordered size="small">
|
||||
<Descriptions.Item label="작업명">{status.jobName}</Descriptions.Item>
|
||||
<Descriptions.Item label="정산월">{status.settleMonth}</Descriptions.Item>
|
||||
<Descriptions.Item label="상태"><CodeBadge groupCode="SETTLE_STATUS" value={status.status} /></Descriptions.Item>
|
||||
<Descriptions.Item label="현재 Step">{status.currentStep ?? '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="시작 시각">{status.startedAt ?? '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="종료 시각">{status.finishedAt ?? '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="소요 시간">{status.durationSec ? `${status.durationSec}초` : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="에러">{status.errorMessage ?? '-'}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{(status.progressPct ?? 0) > 0 && (
|
||||
<Progress
|
||||
percent={status.progressPct}
|
||||
status={status.status === 'FAILED' ? 'exception' : status.status === 'COMPLETED' ? 'success' : 'active'}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { paymentApi, PaymentResp } from '@/api/payment';
|
||||
|
||||
export default function PaymentList() {
|
||||
const [param, setParam] = useState<{ settleMonth?: string; status?: string; pageNum: number; pageSize: number }>({
|
||||
pageNum: 1, pageSize: 30,
|
||||
});
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['payment', 'list', param],
|
||||
queryFn: () => paymentApi.list(param),
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer title="지급 관리">
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월' },
|
||||
{ type: 'code', name: 'status', label: '지급상태', groupCode: 'PAYMENT_STATUS' },
|
||||
]}
|
||||
onSearch={(v) => setParam({ ...param, ...v, pageNum: 1 })}
|
||||
onReset={() => setParam({ pageNum: 1, pageSize: 30 })}
|
||||
/>
|
||||
|
||||
<Table<PaymentResp>
|
||||
rowKey="paymentId"
|
||||
loading={isLoading}
|
||||
dataSource={data?.list}
|
||||
size="small"
|
||||
pagination={{
|
||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||
showSizeChanger: true,
|
||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||
}}
|
||||
columns={[
|
||||
{ title: '정산월', dataIndex: 'settleMonth', width: 90 },
|
||||
{ title: '설계사', dataIndex: 'agentName', width: 100 },
|
||||
{ title: '은행', dataIndex: 'bankName', width: 100 },
|
||||
{ title: '계좌', dataIndex: 'accountNo', width: 180 },
|
||||
{
|
||||
title: '지급액', dataIndex: 'payAmount', width: 140, align: 'right',
|
||||
render: (v) => <strong><MoneyText value={v} /></strong>,
|
||||
},
|
||||
{ title: '지급일', dataIndex: 'payDate', width: 110 },
|
||||
{
|
||||
title: '상태', dataIndex: 'payStatus', width: 100,
|
||||
render: (v) => <CodeBadge groupCode="PAYMENT_STATUS" value={v} />,
|
||||
},
|
||||
{ title: '실패사유', dataIndex: 'failReason', ellipsis: true },
|
||||
]}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user