feat: модалка консолидации — предотметка ГСК, все ЮЛ, ошибки, переход к версии (#3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
This commit is contained in:
parent
27828219c9
commit
fec179b747
@ -1,5 +1,7 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import { ApiError } from '../api/client';
|
||||||
import PageTitle from '../components/PageTitle';
|
import PageTitle from '../components/PageTitle';
|
||||||
import { fetchOneCompanyGroup } from '../services/api/directories/company_groups';
|
import { fetchOneCompanyGroup } from '../services/api/directories/company_groups';
|
||||||
import { setSelectedVersion } from '../store/slices/consolidations/consolidationsSlice';
|
import { setSelectedVersion } from '../store/slices/consolidations/consolidationsSlice';
|
||||||
@ -12,6 +14,8 @@ import { DocumentsDrawer } from '../widgets/FilesDrawer/FilesDrawer';
|
|||||||
export function ConsolidationPage() {
|
export function ConsolidationPage() {
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const { group_id } = useParams();
|
const { group_id } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const versionParam = searchParams.get('version');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [comGroupName, setComGroupName] = useState('');
|
const [comGroupName, setComGroupName] = useState('');
|
||||||
@ -21,17 +25,22 @@ export function ConsolidationPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!group_id) return;
|
if (!group_id) return;
|
||||||
async function getAllInfo(id: string) {
|
async function getAllInfo(id: string) {
|
||||||
const data = await fetchOneCompanyGroup(id);
|
try {
|
||||||
setComGroupName(data.name);
|
const data = await fetchOneCompanyGroup(id);
|
||||||
setLocationPath(`Отчетность>${data.name}>Консолидации`);
|
setComGroupName(data.name);
|
||||||
setDesc(`${data.legal_entities_count} организаций`);
|
setLocationPath(`Отчетность>${data.name}>Консолидации`);
|
||||||
dispatch(getConsolidationsListThunk(id));
|
setDesc(`${data.legal_entities_count} организаций`);
|
||||||
|
await dispatch(getConsolidationsListThunk(id));
|
||||||
|
if (versionParam) dispatch(setSelectedVersion(versionParam));
|
||||||
|
} catch (e: unknown) {
|
||||||
|
message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить группу компаний');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getAllInfo(group_id);
|
getAllInfo(group_id);
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(setSelectedVersion(null));
|
dispatch(setSelectedVersion(null));
|
||||||
};
|
};
|
||||||
}, [dispatch, group_id]);
|
}, [dispatch, group_id, versionParam]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
44
src/widgets/ConsoleLegalEntities/normalize.test.ts
Normal file
44
src/widgets/ConsoleLegalEntities/normalize.test.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import type { IVersionCard } from '../../types/consolidations/version_members';
|
||||||
|
import { normalizeData } from './normalize';
|
||||||
|
|
||||||
|
const card = {
|
||||||
|
id: 'v1',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'le1',
|
||||||
|
name: 'АО "Русский бисквит"',
|
||||||
|
inn: '3528052718',
|
||||||
|
is_head_entity: true,
|
||||||
|
entity_type: 'ЮЛ',
|
||||||
|
osv_accounts: ['62', '60'],
|
||||||
|
checklist: [
|
||||||
|
{ document_type: 'BALANCE', account: null, status: 'NEEDS_CHECK', missing_quarters: [], document_ids: ['d1'] },
|
||||||
|
{ document_type: 'INCOME', account: null, status: 'MISSING', missing_quarters: [], document_ids: [] },
|
||||||
|
{ document_type: 'OSV', account: '62', status: 'CONFIRMED', missing_quarters: [], document_ids: ['d2'] },
|
||||||
|
{ document_type: 'OSV', account: '60', status: 'PROCESSING', missing_quarters: [], document_ids: [] },
|
||||||
|
{ document_type: 'OSV', account: '58', status: 'MISSING', missing_quarters: ['Q1'], document_ids: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as unknown as IVersionCard;
|
||||||
|
|
||||||
|
describe('normalizeData', () => {
|
||||||
|
it('строка ЮЛ со статусами ББ/ОФР и загруженными ОСВ', () => {
|
||||||
|
expect(normalizeData(card)).toEqual([
|
||||||
|
{
|
||||||
|
id: 'le1',
|
||||||
|
name: 'АО "Русский бисквит"',
|
||||||
|
inn: '3528052718',
|
||||||
|
version_id: 'v1',
|
||||||
|
entity_type: 'ЮЛ',
|
||||||
|
bb_status: 'Требуется проверка',
|
||||||
|
ofr_status: 'Отсутствует',
|
||||||
|
uploaded_osvs: '60, 62',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('null — пусто', () => {
|
||||||
|
expect(normalizeData(null)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { ArrowRightOutlined, DownOutlined, IdcardOutlined, MoreOutlined, RightOutlined } from '@ant-design/icons';
|
import { ArrowRightOutlined, DownOutlined, MoreOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
import { Button, Dropdown, Space } from 'antd';
|
import { Button, Dropdown, Space, message } from 'antd';
|
||||||
import type { ColumnsType, TableProps } from 'antd/es/table';
|
import type { ColumnsType, TableProps } from 'antd/es/table';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { ColumnSettings, DataTable, type TableConfig, isGroup, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
|
import { ColumnSettings, DataTable, type TableConfig, isGroup, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
|
||||||
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
|
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
|
||||||
import { setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
|
import { setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
|
||||||
@ -44,6 +44,7 @@ const RowActions = memo(function RowActions({
|
|||||||
});
|
});
|
||||||
export function ConsoleTableWidget() {
|
export function ConsoleTableWidget() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { group_id } = useParams();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
|
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
|
||||||
const loading = useAppSelector((s) => s.consolidations.loading);
|
const loading = useAppSelector((s) => s.consolidations.loading);
|
||||||
@ -109,7 +110,7 @@ export function ConsoleTableWidget() {
|
|||||||
if (key === 'history') {
|
if (key === 'history') {
|
||||||
navigate('/logging');
|
navigate('/logging');
|
||||||
} else {
|
} else {
|
||||||
alert('Не реализовано');
|
message.info('Будет позже');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigate],
|
[navigate],
|
||||||
@ -122,10 +123,7 @@ export function ConsoleTableWidget() {
|
|||||||
width: 120,
|
width: 120,
|
||||||
render: (_: unknown, row: ConsRow) => (
|
render: (_: unknown, row: ConsRow) => (
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<Button type='default' icon={<IdcardOutlined />} onClick={() => alert('Не реализован')}>
|
<Button type='default' onClick={() => navigate(`/groups/${group_id}/versions/${row.id}/osv`)}>
|
||||||
Взять в работу
|
|
||||||
</Button>
|
|
||||||
<Button type='default' onClick={() => navigate(`/cons/${row.con_id}/versions/${row.id}`)}>
|
|
||||||
К отчетности <ArrowRightOutlined />
|
К отчетности <ArrowRightOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<RowActions row={row} onAction={handleDropdownAction} />
|
<RowActions row={row} onAction={handleDropdownAction} />
|
||||||
@ -133,7 +131,7 @@ export function ConsoleTableWidget() {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[navigate],
|
[navigate, group_id, handleDropdownAction],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
||||||
|
|||||||
@ -1,27 +1,21 @@
|
|||||||
import { Checkbox, Col, Empty, Form, Input, Modal, Row, Select, Spin } from 'antd';
|
import { Checkbox, Col, Empty, Form, Input, Modal, Row, Select, Spin, message } from 'antd';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { fetchAllLegalEntities, fetchRelatedGroups } from '../../services/api/directories/company_groups';
|
import { ApiError } from '../../api/client';
|
||||||
|
import { fetchAllLegalEntities, fetchRelatedGroup, fetchRelatedGroups } from '../../services/api/directories/company_groups';
|
||||||
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
|
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
|
||||||
import { useAppDispatch, useAppSelector } from '../../store/store';
|
import { useAppDispatch, useAppSelector } from '../../store/store';
|
||||||
import type { LegalEntityBrief, RelatedGroupOut } from '../../types/api';
|
import type { ConsolidationCreate, LegalEntityBrief, Quarter, RelatedGroupOut } from '../../types/api';
|
||||||
|
import { preselectMembers } from './preselect';
|
||||||
type FormValues = {
|
|
||||||
company_group_id: string;
|
|
||||||
related_group_id: string;
|
|
||||||
year: number;
|
|
||||||
quarter: string; // example of 2nd static field
|
|
||||||
legal_entity_ids: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onOk: (values: FormValues) => void | Promise<void>;
|
onOk: (values: ConsolidationCreate) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||||
const [form] = Form.useForm<FormValues>();
|
const [form] = Form.useForm<ConsolidationCreate>();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { raw_rows, loading } = useAppSelector((state) => state.company_groups);
|
const { raw_rows, loading } = useAppSelector((state) => state.company_groups);
|
||||||
@ -33,6 +27,8 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
const [legEntities, setLegEntities] = useState<LegalEntityBrief[]>([]);
|
const [legEntities, setLegEntities] = useState<LegalEntityBrief[]>([]);
|
||||||
const [loadingEntities, setLoadingEntities] = useState(false);
|
const [loadingEntities, setLoadingEntities] = useState(false);
|
||||||
const legEntReqRef = useRef(0);
|
const legEntReqRef = useRef(0);
|
||||||
|
const relatedReqRef = useRef(0);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const [legEntSearch, setLegEntSearch] = useState('');
|
const [legEntSearch, setLegEntSearch] = useState('');
|
||||||
|
|
||||||
@ -42,7 +38,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
const semester = Form.useWatch('quarter', form);
|
const semester = Form.useWatch('quarter', form);
|
||||||
const legEntIds = Form.useWatch('legal_entity_ids', form);
|
const legEntIds = Form.useWatch('legal_entity_ids', form);
|
||||||
|
|
||||||
const quarterOptions = [
|
const quarterOptions: { value: Quarter; label: string }[] = [
|
||||||
{ value: 'Q1', label: '1 квартал' },
|
{ value: 'Q1', label: '1 квартал' },
|
||||||
{ value: 'Q2', label: '2 квартал' },
|
{ value: 'Q2', label: '2 квартал' },
|
||||||
{ value: 'Q3', label: '3 квартал' },
|
{ value: 'Q3', label: '3 квартал' },
|
||||||
@ -71,37 +67,6 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
legEntReqRef.current++;
|
legEntReqRef.current++;
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleGroupChange = async (value?: string) => {
|
|
||||||
// form.setFieldsValue({
|
|
||||||
// related_group_id: undefined,
|
|
||||||
// legal_entity_ids: [],
|
|
||||||
// });
|
|
||||||
// setSubgroups([]);
|
|
||||||
// clearLegEnts();
|
|
||||||
// if (!value) return;
|
|
||||||
// const reqId = ++subgroupReqRef.current;
|
|
||||||
// setLoadingSubgroups(true);
|
|
||||||
// try {
|
|
||||||
// const data = await fetchGSKByComGroup(value);
|
|
||||||
// if (reqId === subgroupReqRef.current) setSubgroups(data);
|
|
||||||
// } finally {
|
|
||||||
// if (reqId === subgroupReqRef.current) setLoadingSubgroups(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleSubGroupChange = async (value?: string) => {
|
|
||||||
// form.setFieldsValue({ legal_entity_ids: [] });
|
|
||||||
// clearLegEnts();
|
|
||||||
// if (!value) return;
|
|
||||||
// const reqId = ++legEntReqRef.current;
|
|
||||||
// setLoadingEntities(true);
|
|
||||||
// try {
|
|
||||||
// const data = await fetchGroupMembers(value);
|
|
||||||
// if (reqId === legEntReqRef.current) setLegEntities(data);
|
|
||||||
// } finally {
|
|
||||||
// if (reqId === legEntReqRef.current) setLoadingEntities(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
const handleGroupChange = async (value?: string) => {
|
const handleGroupChange = async (value?: string) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
related_group_id: undefined,
|
related_group_id: undefined,
|
||||||
@ -115,17 +80,38 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
setLoadingSubgroups(true);
|
setLoadingSubgroups(true);
|
||||||
setLoadingEntities(true);
|
setLoadingEntities(true);
|
||||||
try {
|
try {
|
||||||
const dataSubgroups = await fetchRelatedGroups(value);
|
const [dataSubgroups, dataLegEnts] = await Promise.all([fetchRelatedGroups(value), fetchAllLegalEntities(value)]);
|
||||||
const dataLegEnts = await fetchAllLegalEntities(value);
|
if (reqId === subgroupReqRef.current) setSubgroups(dataSubgroups.filter((g) => g.kind === 'gsk'));
|
||||||
if (reqId === subgroupReqRef.current) setSubgroups(dataSubgroups);
|
|
||||||
if (leReqId === legEntReqRef.current) setLegEntities(dataLegEnts);
|
if (leReqId === legEntReqRef.current) setLegEntities(dataLegEnts);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить данные ГК');
|
||||||
} finally {
|
} finally {
|
||||||
if (reqId === subgroupReqRef.current) setLoadingSubgroups(false);
|
if (reqId === subgroupReqRef.current) setLoadingSubgroups(false);
|
||||||
if (leReqId === legEntReqRef.current) setLoadingEntities(false);
|
if (leReqId === legEntReqRef.current) setLoadingEntities(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRelatedGroupChange = async (value?: string) => {
|
||||||
|
form.setFieldsValue({ legal_entity_ids: [] });
|
||||||
|
if (!value) return;
|
||||||
|
const reqId = ++relatedReqRef.current;
|
||||||
|
try {
|
||||||
|
const detail = await fetchRelatedGroup(value);
|
||||||
|
if (reqId === relatedReqRef.current) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
legal_entity_ids: preselectMembers(
|
||||||
|
detail.members.map((m) => m.legal_entity_id),
|
||||||
|
legEntities,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить состав ГСК');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const resetLocal = () => {
|
const resetLocal = () => {
|
||||||
|
relatedReqRef.current++;
|
||||||
setSubgroups([]);
|
setSubgroups([]);
|
||||||
setLoadingSubgroups(false);
|
setLoadingSubgroups(false);
|
||||||
subgroupReqRef.current++;
|
subgroupReqRef.current++;
|
||||||
@ -134,13 +120,21 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
|
let values: ConsolidationCreate;
|
||||||
|
try {
|
||||||
|
values = await form.validateFields();
|
||||||
|
} catch {
|
||||||
|
return; // ошибки полей antd показывает сам
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
|
||||||
await onOk(values);
|
await onOk(values);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
resetLocal();
|
resetLocal();
|
||||||
} catch {
|
} catch (e: unknown) {
|
||||||
// ошибка валидации или запроса: форма остаётся открытой, пользователь исправит данные
|
message.error(e instanceof ApiError ? e.message : 'Не удалось создать консолидацию');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,10 +157,12 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
okButtonProps={{ disabled: isSubmitDisabled }}
|
okButtonProps={{ disabled: isSubmitDisabled }}
|
||||||
okText='Submit'
|
okText='Создать'
|
||||||
|
cancelText='Отмена'
|
||||||
|
confirmLoading={submitting}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
width={640}>
|
width={640}>
|
||||||
<Form form={form} layout='vertical' preserve={false} initialValues={{ legEntIds: [] }}>
|
<Form form={form} layout='vertical' preserve={false} initialValues={{ legal_entity_ids: [] }}>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name='company_group_id' label='Группа компаний' rules={[{ required: true, message: 'Укажите ГК' }]}>
|
<Form.Item name='company_group_id' label='Группа компаний' rules={[{ required: true, message: 'Укажите ГК' }]}>
|
||||||
@ -191,7 +187,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
allowClear
|
allowClear
|
||||||
filterOption={(input, option) => (option?.label as string)?.toLowerCase().includes(input.toLowerCase())}
|
filterOption={(input, option) => (option?.label as string)?.toLowerCase().includes(input.toLowerCase())}
|
||||||
options={subgroups.map((s) => ({ value: s.id, label: s.name }))}
|
options={subgroups.map((s) => ({ value: s.id, label: s.name }))}
|
||||||
// onChange={handleSubGroupChange}
|
onChange={handleRelatedGroupChange}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
@ -235,7 +231,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
|||||||
{!loadingEntities && filteredLegEnts.length === 0 && (
|
{!loadingEntities && filteredLegEnts.length === 0 && (
|
||||||
<Empty
|
<Empty
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
description={legEntities.length === 0 ? 'Нет организаций' : 'Ничего не найдено'}
|
description={legEntities.length === 0 ? 'Нет ЮЛ в ГК' : 'Ничего не найдено'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loadingEntities && filteredLegEnts.length > 0 && (
|
{!loadingEntities && filteredLegEnts.length > 0 && (
|
||||||
|
|||||||
16
src/widgets/NewConsoleModal/preselect.test.ts
Normal file
16
src/widgets/NewConsoleModal/preselect.test.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { preselectMembers } from './preselect';
|
||||||
|
|
||||||
|
const entities = [
|
||||||
|
{ id: 'a', name: 'А', inn: '1' },
|
||||||
|
{ id: 'b', name: 'Б', inn: '2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('preselectMembers', () => {
|
||||||
|
it('отмечает только тех членов ГСК, что есть среди ЮЛ ГК', () => {
|
||||||
|
expect(preselectMembers(['b', 'x'], entities)).toEqual(['b']);
|
||||||
|
});
|
||||||
|
it('пустая ГСК — ничего', () => {
|
||||||
|
expect(preselectMembers([], entities)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
7
src/widgets/NewConsoleModal/preselect.ts
Normal file
7
src/widgets/NewConsoleModal/preselect.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { LegalEntityBrief } from '../../types/api';
|
||||||
|
|
||||||
|
/** Члены ГСК, присутствующие среди ЮЛ ГК, — начальные отметки периметра. */
|
||||||
|
export function preselectMembers(memberIds: string[], entities: LegalEntityBrief[]): string[] {
|
||||||
|
const present = new Set(entities.map((e) => e.id));
|
||||||
|
return memberIds.filter((id) => present.has(id));
|
||||||
|
}
|
||||||
@ -9,12 +9,12 @@ import {
|
|||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Divider, Layout, Tooltip } from 'antd';
|
import { Button, Divider, Layout, Tooltip, message } from 'antd';
|
||||||
import { type ReactNode, memo, useState } from 'react';
|
import { type ReactNode, memo, useState } from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink, useNavigate } from 'react-router-dom';
|
||||||
import { USERS_ADMIN } from '../../auth/permissions';
|
import { USERS_ADMIN } from '../../auth/permissions';
|
||||||
import { createConsolidation } from '../../services/api/consolidations';
|
import { createConsolidation } from '../../services/api/consolidations';
|
||||||
import { MyModal } from '../NewConsoleModal/NewConsoleModal';
|
import { NewConsolidationModal } from '../NewConsoleModal/NewConsoleModal';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
type CollapsedIconProps = {
|
type CollapsedIconProps = {
|
||||||
@ -162,6 +162,7 @@ const Section = memo(function Section({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
|
||||||
@ -207,17 +208,16 @@ export function Sidebar() {
|
|||||||
<Section key={g.key} group={g} isCollapsed={isCollapsed} />
|
<Section key={g.key} group={g} isCollapsed={isCollapsed} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MyModal
|
<NewConsolidationModal
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
onOk={async (values) => {
|
onOk={async (values) => {
|
||||||
await createConsolidation({
|
const card = await createConsolidation(values);
|
||||||
...values,
|
|
||||||
quarter: values.quarter as 'Q1' | 'Q2' | 'Q3' | 'Q4',
|
|
||||||
});
|
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
message.success(`Создана консолидация ${values.year} ${values.quarter}`);
|
||||||
|
navigate(`/groups/${values.company_group_id}?version=${card.id}`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Layout.Sider>
|
</Layout.Sider>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user