From 3aad67051011991f80369370129e0acef3089e0d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 27 Oct 2026 00:00:47 +0400 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B8=20=E2=80=94=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=93=D0=9A,=20?= =?UTF-8?q?=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA=20=D0=AE=D0=9B,=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D1=83=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20(=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20#179,=20?= =?UTF-8?q?AURORA-1671)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Страница ГК: снова выбирается первая ГК списка; список ЮЛ без выбранной ГК не запрашивается (иначе уходил запрос по всем ГК), «ЮЛ вне ГК» грузится без ГК. - Выбранная ГК обновляется после перезагрузки списка ГК — счётчик ЮЛ в шапке и на вкладке после создания/переноса/вывода ЮЛ. - Загрузка ГСК/ГСЗ: try/finally — ошибка не оставляет вечный спиннер. - Форма ЮЛ: INVALID_INN/INVALID_KPP/INN_EXISTS/CRM_ID_EXISTS — у поля; КПП приводится к верхнему регистру при вводе. - crm_id ГСК/ГСЗ — string | null; колонка «ID в CRM»; LE_PAGE вместо литерала; возвращён комментарий в фильтре шахматки. Co-Authored-By: Claude Opus 5.5 --- web/src/App.css | 1 + web/src/layouts/CatalogueGroups.tsx | 31 ++++++++++++++---- .../services/api/directories/catalogue_jur.ts | 3 +- .../catalogueJurSlice/catalogueJurSlice.ts | 7 ++++ web/src/types/api.ts | 3 +- web/src/types/relatedGroup/relatedGroup.ts | 2 +- .../CatalogueJurForm/CatalogueJurForm.tsx | 32 +++++++++---------- .../CatalogueJurTable/CatalogueJurTable.tsx | 5 ++- .../ConsolidationChessTableFilter.tsx | 1 + .../RelatedGroupForm/RelatedGroupForm.tsx | 2 +- web/src/widgets/RelatedGroupTable/config.tsx | 2 +- 11 files changed, 60 insertions(+), 29 deletions(-) diff --git a/web/src/App.css b/web/src/App.css index 4f7a3bb..606dd57 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,3 +1,4 @@ +/* Теги выбранных значений в селектах: фильтр шахматки, синонимы ЮЛ */ .custom-select-selection-item .ant-select-selection-item { background-color: #ecfdf3 !important; color: #1f5f35 !important; diff --git a/web/src/layouts/CatalogueGroups.tsx b/web/src/layouts/CatalogueGroups.tsx index 897efd3..6d712ea 100644 --- a/web/src/layouts/CatalogueGroups.tsx +++ b/web/src/layouts/CatalogueGroups.tsx @@ -1,10 +1,12 @@ import { PlusOutlined } from '@ant-design/icons'; import { Button, Space } from 'antd'; import { useEffect, useState } from 'react'; +import { ApiError } from '../api/client'; import PageTitle from '../components/PageTitle'; import { createCatalogueJur } from '../services/api/directories/catalogue_jur'; import { createCompanyGroup, fetchRelatedGroups } from '../services/api/directories/company_groups'; import { createRelatedGroup } from '../services/api/directories/related-groups'; +import { setActiveCompanyGroup } from '../store/slices/catalogueJurSlice/catalogueJurSlice'; import { getCatalogueJurThunk } from '../store/slices/catalogueJurSlice/thunks'; import { getCompanyGroupsThunk } from '../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../store/store'; @@ -12,6 +14,7 @@ import type { RelatedGroupOut } from '../types/api'; import type { CatalogueJurFormProps } from '../types/catalogueJur/catalogueJur'; import type { CreateCompanyGroupProps } from '../types/companyGroups/companyGroups'; import type { CreateEntityKind, RelatedGroupFormType } from '../types/relatedGroup/relatedGroup'; +import { useNotificationApi } from '../utils/useNotification'; import { CataloguGroupTabs, type CatalogueGroupTabKey } from '../widgets/CataloguGroupTabs/CataloguGroupTabs'; import { CompanyGroupHeader } from '../widgets/CatalogueGroupHeader/CatalogueGroupHeader'; import { CatalogueJurForm } from '../widgets/CatalogueJurForm/CatalogueJurForm'; @@ -24,6 +27,7 @@ import { RelatedGroupTable } from '../widgets/RelatedGroupTable/RelatedGroupTabl export function CatalogueGroup() { const dispatch = useAppDispatch(); + const notification = useNotificationApi(); const isGettingArchive = useAppSelector((state) => state.catalogue_jur.isGettingArchive); @@ -35,6 +39,7 @@ export function CatalogueGroup() { const [gszData, setGszData] = useState([]); const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); const relatedGroupFilter = useAppSelector((state) => state.catalogue_jur.relatedGroupFilter); + const rawRows = useAppSelector((state) => state.company_groups.raw_rows); async function createJurHandler(values: CatalogueJurFormProps) { await createCatalogueJur(values); @@ -68,12 +73,19 @@ export function CatalogueGroup() { async function getRelatedGroupsHandler() { if (activeCompanyGroup?.id) { setRelatedGroupLoading(true); - const res = await fetchRelatedGroups(activeCompanyGroup?.id); - const gsks = res.filter((elem) => elem.kind === 'gsk'); - const gszs = res.filter((elem) => elem.kind === 'gsz'); - setGskData(gsks); - setGszData(gszs); - setRelatedGroupLoading(false); + try { + const res = await fetchRelatedGroups(activeCompanyGroup?.id); + const gsks = res.filter((elem) => elem.kind === 'gsk'); + const gszs = res.filter((elem) => elem.kind === 'gsz'); + setGskData(gsks); + setGszData(gszs); + } catch (e) { + if (e instanceof ApiError) { + notification.error({ description: e.message }); + } + } finally { + setRelatedGroupLoading(false); + } } } @@ -81,6 +93,13 @@ export function CatalogueGroup() { getRelatedGroupsHandler(); }, [activeCompanyGroup]); + // Без выбранной ГК таблица ЮЛ не грузится — выбираем первую ГК списка + useEffect(() => { + if (!activeCompanyGroup && rawRows?.length) { + dispatch(setActiveCompanyGroup(rawRows[0])); + } + }, [rawRows, activeCompanyGroup, dispatch]); + useEffect(() => { setActiveTab('legal_entities'); }, [relatedGroupFilter]); diff --git a/web/src/services/api/directories/catalogue_jur.ts b/web/src/services/api/directories/catalogue_jur.ts index 33d3d8f..9b1d30a 100644 --- a/web/src/services/api/directories/catalogue_jur.ts +++ b/web/src/services/api/directories/catalogue_jur.ts @@ -7,9 +7,10 @@ import type { MoveCatalogueJurProps, WithdrawCatalogueJurProps, } from '../../../types/catalogueJur/catalogueJur'; +import { LE_PAGE } from './company_groups'; export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) { - const res = await api.get>('/directories/legal-entities', { limit: 200, offset: 0, ...params }); + const res = await api.get>('/directories/legal-entities', { limit: LE_PAGE, offset: 0, ...params }); return res; } diff --git a/web/src/store/slices/catalogueJurSlice/catalogueJurSlice.ts b/web/src/store/slices/catalogueJurSlice/catalogueJurSlice.ts index 8662ee1..7abf90b 100644 --- a/web/src/store/slices/catalogueJurSlice/catalogueJurSlice.ts +++ b/web/src/store/slices/catalogueJurSlice/catalogueJurSlice.ts @@ -1,6 +1,7 @@ import { type PayloadAction, createSlice } from '@reduxjs/toolkit'; import type { RelatedGroupOut } from '../../../types/api'; import type { ICompanyGroup } from '../../../types/companyGroups/companyGroups'; +import { getCompanyGroupsThunk } from '../companyGroupsSlice/thunks'; import { getCatalogueJurThunk } from './thunks'; import type { CatalogueJurSliceType } from './types'; @@ -43,6 +44,12 @@ const catalogueJurSlice = createSlice({ .addCase(getCatalogueJurThunk.rejected, (state, action) => { state.error = action.payload ?? 'Неизвестная ошибка'; state.loading = false; + }) + // Выбранная ГК — копия строки списка ГК: после перезагрузки списка берём свежую (счётчик ЮЛ в шапке и вкладке) + .addCase(getCompanyGroupsThunk.fulfilled, (state, action) => { + if (state.activeCompanyGroup?.id) { + state.activeCompanyGroup = action.payload.find((elem) => elem.id === state.activeCompanyGroup?.id) ?? null; + } }); }, }); diff --git a/web/src/types/api.ts b/web/src/types/api.ts index d304177..7e36dd4 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -375,7 +375,7 @@ export interface ConsolidationCreate { export interface RelatedGroupOut { id: string; code: string; - crm_id: string; + crm_id: string | null; company_group_id: string; kind: 'gsk' | 'gsz'; name: string; @@ -390,7 +390,6 @@ export interface RelatedGroupMemberOut { } export interface RelatedGroupDetail extends RelatedGroupOut { members: RelatedGroupMemberOut[]; - crm_id: string; } export interface LegalEntityBrief { id: string; diff --git a/web/src/types/relatedGroup/relatedGroup.ts b/web/src/types/relatedGroup/relatedGroup.ts index 7472809..82cfb1c 100644 --- a/web/src/types/relatedGroup/relatedGroup.ts +++ b/web/src/types/relatedGroup/relatedGroup.ts @@ -1,7 +1,7 @@ export type RelatedGroupFormType = { name: string; created_date: string | null; - crm_id: string; + crm_id: string | null; kind?: 'gsk' | 'gsz'; company_group_id?: string; }; diff --git a/web/src/widgets/CatalogueJurForm/CatalogueJurForm.tsx b/web/src/widgets/CatalogueJurForm/CatalogueJurForm.tsx index a54cad8..2a78176 100644 --- a/web/src/widgets/CatalogueJurForm/CatalogueJurForm.tsx +++ b/web/src/widgets/CatalogueJurForm/CatalogueJurForm.tsx @@ -17,6 +17,14 @@ type Props = { jurId?: string; }; +// Ошибки сервера, относящиеся к одному полю, показываем у этого поля +const FIELD_BY_ERROR_CODE: Record = { + INN_EXISTS: 'inn', + INVALID_INN: 'inn', + INVALID_KPP: 'kpp', + CRM_ID_EXISTS: 'crm_id', +}; + export const CatalogueJurForm: React.FC = ({ open, onCancel, onOk, jurId }) => { const [form] = Form.useForm(); const notification = useNotificationApi(); @@ -116,21 +124,9 @@ export const CatalogueJurForm: React.FC = ({ open, onCancel, onOk, jurId }); } catch (e) { if (e instanceof ApiError) { - if (e.code === 'INN_EXISTS') { - form.setFields([ - { - name: 'inn', - errors: [e.message], - }, - ]); - } - if (e.code === 'CRM_ID_EXISTS') { - form.setFields([ - { - name: 'crm_id', - errors: [e.message], - }, - ]); + const field = e.code ? FIELD_BY_ERROR_CODE[e.code] : undefined; + if (field) { + form.setFields([{ name: field, errors: [e.message] }]); } notification.error({ description: e.message }); } @@ -194,7 +190,11 @@ export const CatalogueJurForm: React.FC = ({ open, onCancel, onOk, jurId - + value?.toUpperCase()} + rules={[{ pattern: /^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$/, message: 'Некорректный формат' }]}> diff --git a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx index 9dfe714..94c3112 100644 --- a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx +++ b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx @@ -128,7 +128,10 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur } useEffect(() => { - getCatalogueJur(); + // «ЮЛ вне ГК» грузится без выбранной ГК; список ГК — только по выбранной (без неё запрос ушёл бы по всем ГК) + if (outside_group || activeCompanyGroup?.id) { + getCatalogueJur(); + } }, [isGettingArchive, activeCompanyGroup, outside_group]); const extraColumns: ColumnsType = useMemo( diff --git a/web/src/widgets/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx b/web/src/widgets/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx index d6eebba..d9ab17b 100644 --- a/web/src/widgets/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx +++ b/web/src/widgets/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx @@ -15,6 +15,7 @@ interface ConsolidationChessTableFilterI { credit: string[]; onCreditChange: (val: string[]) => void; allCompanies: MatrixCompany[]; + /** Год и квартал версии — только для показа: версия задаётся адресом. */ year?: number; quarter?: Quarter; } diff --git a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx index 84deb75..d4ed42e 100644 --- a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx +++ b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx @@ -42,7 +42,7 @@ export const RelatedGroupForm: React.FC = ({ open, onCancel, onOk, relate form.setFieldsValue({ crm_id: data?.crm_id, name: data?.name, - created_date: data?.created_date ?? (relatedGroupId ? null : dayjs().format('YYYY-MM-DD')), + created_date: data?.created_date ?? null, company_group_id: data?.company_group_id, }); } catch (e) { diff --git a/web/src/widgets/RelatedGroupTable/config.tsx b/web/src/widgets/RelatedGroupTable/config.tsx index c4b72af..fe4f8cb 100644 --- a/web/src/widgets/RelatedGroupTable/config.tsx +++ b/web/src/widgets/RelatedGroupTable/config.tsx @@ -15,7 +15,7 @@ export const relateGroupConfig: TableConfig = { }, { key: 'crm_id', - title: 'CRM ID', + title: 'ID в CRM', }, { key: 'name',