fix: карточка консолидации
This commit is contained in:
parent
5ee54bd94f
commit
723c84cc72
@ -1,6 +1,6 @@
|
||||
import { DownloadOutlined, FilterOutlined } from '@ant-design/icons';
|
||||
import { Alert, Button, Space, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Space, Tooltip, message } from 'antd';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ApiError } from '../api/client';
|
||||
import PageTitle from '../components/PageTitle';
|
||||
@ -20,13 +20,17 @@ import { OsvTable } from '../widgets/ConsolidatedOsv/OsvTable';
|
||||
import { OsvTabs } from '../widgets/ConsolidatedOsv/OsvTabs';
|
||||
import { mapEntryId } from '../widgets/ConsolidatedOsv/counterpartyMapping';
|
||||
import { OSV_PAGE_SIZE, useConsolidatedOsv } from '../widgets/ConsolidatedOsv/useConsolidatedOsv';
|
||||
import { useCurrentUser } from '../auth/MeContext';
|
||||
import ChangeStatusModal from '../widgets/StatusChangeModal/ChangeStatusModal';
|
||||
|
||||
function OsvContent({ versionId }: { versionId: string }) {
|
||||
const [card, setCard] = useState<VersionCard | null>(null);
|
||||
const [statusModalOpen, setModalOpen] = useState(false);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
// строка, для которой открыт выбор организации («Указать» или ИНН сопоставленного ЮЛ)
|
||||
const [mapping, setMapping] = useState<ConsolidatedRowOut | null>(null);
|
||||
const osv = useConsolidatedOsv(versionId);
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
useEffect(() => {
|
||||
let actual = true;
|
||||
@ -70,9 +74,37 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
description={russianStatus}
|
||||
extra={
|
||||
card && (
|
||||
<Space size={8}>
|
||||
<Space size={8} style={{ display: 'flex', gap: 4 }}>
|
||||
<span style={{ color: '#667085', fontSize: 12 }}>Статус отчёта</span>
|
||||
<TableTag text={russianStatus} {...preset} />
|
||||
<TableTag
|
||||
style={{
|
||||
padding: '3px 10px',
|
||||
width: 158,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: 11,
|
||||
}}
|
||||
text={russianStatus}
|
||||
{...preset}
|
||||
/>
|
||||
{card.lock === null || (card.lock && card.lock.locked_by !== currentUser.id) ? (
|
||||
<Tooltip title='Сначала возьмите версию в работу'>
|
||||
<Button size='middle' style={{ fontSize: 12 }} type='default' disabled>
|
||||
Изменить статус
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
size='middle'
|
||||
onClick={() => {
|
||||
setModalOpen(true);
|
||||
}}
|
||||
style={{ fontSize: 12 }}
|
||||
type='default'>
|
||||
Изменить статус
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@ -144,6 +176,14 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
onMapped={osv.refresh}
|
||||
/>
|
||||
)}
|
||||
<ChangeStatusModal
|
||||
open={statusModalOpen}
|
||||
versionId={versionId}
|
||||
onSuccess={setCard}
|
||||
setClose={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { api, apiFetchBlob } from '../../api/client';
|
||||
import type { ConsolidationCreate } from '../../types/api';
|
||||
import type { ConsolidationCreate, VersionStatusUpdate } from '../../types/api';
|
||||
import type { LockOut } from '../../types/consolidations/common_types';
|
||||
import type { IConsolidation } from '../../types/consolidations/consolidations';
|
||||
import type { ChecklistBackendShape, VersionCard } from '../../types/consolidations/version_members';
|
||||
@ -18,3 +18,6 @@ export const lockVersion = (versionId: string, reason?: string | null) => {
|
||||
}
|
||||
return api.post<LockOut>(`/consolidations/versions/${versionId}/lock`, body);
|
||||
};
|
||||
|
||||
export const updateStatus = (versionId: string, body: VersionStatusUpdate) =>
|
||||
api.post<VersionCard>(`/consolidation/versions/${versionId}/status`, body);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
// DTO API arm-core (/api/v1). Повторяют arm-core/api/src/schemas/; Decimal и даты — строки.
|
||||
|
||||
import { ConsStatus } from './consolidations/common_types';
|
||||
|
||||
export type Quarter = 'Q1' | 'Q2' | 'Q3' | 'Q4';
|
||||
export type DocumentType = 'BALANCE' | 'INCOME' | 'OSV' | 'DISCLOSURE' | 'OTHER';
|
||||
export type DocumentStatus = 'RECOGNIZED' | 'NEEDS_CHECK' | 'CONFIRMED' | 'ERROR';
|
||||
@ -394,3 +396,8 @@ export interface LegalEntityBrief {
|
||||
name: string;
|
||||
inn: string;
|
||||
}
|
||||
|
||||
export interface VersionStatusUpdate {
|
||||
to_status: ConsStatus;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export type ConsRussianStatus =
|
||||
| 'Верификация РФ'
|
||||
| 'Инициация'
|
||||
| 'Обработка документов'
|
||||
| 'Коррекция данных'
|
||||
| 'Корректировка данных'
|
||||
| 'Консолидация ГО'
|
||||
| 'Консолидация РФ'
|
||||
| 'Отменён'
|
||||
|
||||
@ -7,7 +7,7 @@ export const statusMapping: Record<ConsStatus, ConsRussianStatus> = {
|
||||
VERIFICATION_RF: 'Верификация РФ',
|
||||
INITIATED: 'Инициация',
|
||||
DOCUMENTS_PROCESSING: 'Обработка документов',
|
||||
DATA_CORRECTION: 'Коррекция данных',
|
||||
DATA_CORRECTION: 'Корректировка данных',
|
||||
CONSOLIDATION_GO: 'Консолидация ГО',
|
||||
CONSOLIDATION_RF: 'Консолидация РФ',
|
||||
CANCELLED: 'Отменён',
|
||||
@ -25,7 +25,7 @@ export const statusColorMapping: Record<ConsRussianStatus, Preset> = {
|
||||
'Консолидация ГО': TAG_PRESETS.purple,
|
||||
'Консолидация РФ': TAG_PRESETS.purple,
|
||||
Корректировка: TAG_PRESETS.gold,
|
||||
'Коррекция данных': TAG_PRESETS.gold,
|
||||
'Корректировка данных': TAG_PRESETS.gold,
|
||||
'Обработка документов': TAG_PRESETS.gold,
|
||||
Отклонён: TAG_PRESETS.red,
|
||||
Отменён: TAG_PRESETS.red,
|
||||
|
||||
111
web/src/widgets/StatusChangeModal/ChangeStatusModal.tsx
Normal file
111
web/src/widgets/StatusChangeModal/ChangeStatusModal.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { Form, Input, message, Modal, Select } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ConsStatus } from '../../types/consolidations/common_types';
|
||||
import { VersionStatusUpdate } from '../../types/api';
|
||||
import { getVersionCard, updateStatus } from '../../services/api/consolidations';
|
||||
import { statusColorMapping, statusMapping } from '../ConsoleTable/consts';
|
||||
import { TableTag } from '../../components/TableTag/TableTag';
|
||||
import { TAG_PRESETS } from '../../components/TableTag/presets';
|
||||
import { ApiError } from '../../api/client';
|
||||
import { VersionCard } from '../../types/consolidations/version_members';
|
||||
|
||||
type StatusModalProps = {
|
||||
open: boolean;
|
||||
versionId: string;
|
||||
setClose: () => void;
|
||||
onSuccess: (card: VersionCard) => void;
|
||||
};
|
||||
const known409Codes = ['VERSION_NOT_EDITABLE', 'LOCK_REQUIRED', 'LOCKED_BY_OTHER', 'TRANSITION_NOT_ALLOWED'];
|
||||
const errorCodeMap = {
|
||||
VERSION_NOT_EDITABLE: 'Версию нельзя отредактировать',
|
||||
LOCK_REQUIRED: 'Сначала возьмите версию в работу',
|
||||
LOCKED_BY_OTHER: 'Версия находится в работе у другого пользователя',
|
||||
TRANSITION_NOT_ALLOWED: 'Данный переход недоступен',
|
||||
};
|
||||
|
||||
/*
|
||||
"Инициация", "Обработка документов", "Корректировка данных" -> "Отменена" (комментарий обязателен);
|
||||
из "Обработка документов", "Корректировка данных" -> "Инициация" (комментарий обязателен во фронте)
|
||||
*/
|
||||
|
||||
async function mockStatusMappingFetch() {
|
||||
const mapping: Record<ConsStatus, ConsStatus[]> = {
|
||||
INITIATED: ['CANCELLED'],
|
||||
DOCUMENTS_PROCESSING: ['CANCELLED', 'INITIATED'],
|
||||
DATA_CORRECTION: ['CANCELLED', 'INITIATED'],
|
||||
};
|
||||
return mapping;
|
||||
}
|
||||
|
||||
export default function ChangeStatusModal({ open, versionId, setClose, onSuccess }: StatusModalProps) {
|
||||
const [currentStatus, setCurrentStatus] = useState<ConsStatus | null>(null);
|
||||
const [nextStatusOptions, setNextStatusOptions] = useState<ConsStatus[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm<VersionStatusUpdate>();
|
||||
|
||||
const nextStatus = Form.useWatch('to_status', form);
|
||||
const comment = Form.useWatch('comment', form);
|
||||
useEffect(() => {
|
||||
async function getAllData() {
|
||||
if (!versionId) return;
|
||||
setLoading(true);
|
||||
const versionCard = await getVersionCard(versionId);
|
||||
const status = versionCard.status;
|
||||
const allNextOptions = await mockStatusMappingFetch();
|
||||
setCurrentStatus(status);
|
||||
setNextStatusOptions(allNextOptions[status]);
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
getAllData();
|
||||
}, [versionId]);
|
||||
const russianStatus = currentStatus ? statusMapping[currentStatus] : '';
|
||||
const badgePreset = russianStatus ? statusColorMapping[russianStatus] : TAG_PRESETS.default;
|
||||
const isSubmitDisabled = !nextStatus || !comment.trim();
|
||||
|
||||
const handleOk = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
const newCard = await updateStatus(versionId, { ...values, comment: values.comment.trim() });
|
||||
onSuccess(newCard);
|
||||
setClose();
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 409 && e.code) {
|
||||
if (known409Codes.includes(e.code)) {
|
||||
message.error(errorCodeMap[e.code]);
|
||||
}
|
||||
} else {
|
||||
message.error('Непредвиденная ошибка');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title='Изменить статус'
|
||||
okText='Изменить статус'
|
||||
cancelText='Отмена'
|
||||
okButtonProps={{ disabled: isSubmitDisabled || loading }}
|
||||
loading={loading}
|
||||
onOk={handleOk}
|
||||
onCancel={setClose}
|
||||
destroyOnHidden>
|
||||
<div style={{ display: 'flex' }}>
|
||||
Текущий этап: <TableTag text={russianStatus} {...badgePreset} />
|
||||
</div>
|
||||
|
||||
<Form form={form} layout='vertical' requiredMark={false} initialValues={{ comment: '', to_status: nextStatusOptions[0] }}>
|
||||
<Form.Item name='to_status' label='Новый этап'>
|
||||
<Select
|
||||
options={nextStatusOptions.map((i) => ({ label: i, value: i }))}
|
||||
labelRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}
|
||||
optionRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item name='comment' rules={[{ required: true, message: 'Комментарий необходим' }]} label='Комментарий'>
|
||||
<Input.TextArea maxLength={1000} placeholder='Укажите комментарий к изменению статуса'></Input.TextArea>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user