fix(web): справочники — выбранная ГК, счётчик ЮЛ, ошибки у полей (ревью #179, AURORA-1671)
- Страница ГК: снова выбирается первая ГК списка; список ЮЛ без выбранной ГК не запрашивается (иначе уходил запрос по всем ГК), «ЮЛ вне ГК» грузится без ГК. - Выбранная ГК обновляется после перезагрузки списка ГК — счётчик ЮЛ в шапке и на вкладке после создания/переноса/вывода ЮЛ. - Загрузка ГСК/ГСЗ: 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 <noreply@anthropic.com>
This commit is contained in:
parent
630a616bf1
commit
3aad670510
@ -1,3 +1,4 @@
|
|||||||
|
/* Теги выбранных значений в селектах: фильтр шахматки, синонимы ЮЛ */
|
||||||
.custom-select-selection-item .ant-select-selection-item {
|
.custom-select-selection-item .ant-select-selection-item {
|
||||||
background-color: #ecfdf3 !important;
|
background-color: #ecfdf3 !important;
|
||||||
color: #1f5f35 !important;
|
color: #1f5f35 !important;
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Space } from 'antd';
|
import { Button, Space } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ApiError } from '../api/client';
|
||||||
import PageTitle from '../components/PageTitle';
|
import PageTitle from '../components/PageTitle';
|
||||||
import { createCatalogueJur } from '../services/api/directories/catalogue_jur';
|
import { createCatalogueJur } from '../services/api/directories/catalogue_jur';
|
||||||
import { createCompanyGroup, fetchRelatedGroups } from '../services/api/directories/company_groups';
|
import { createCompanyGroup, fetchRelatedGroups } from '../services/api/directories/company_groups';
|
||||||
import { createRelatedGroup } from '../services/api/directories/related-groups';
|
import { createRelatedGroup } from '../services/api/directories/related-groups';
|
||||||
|
import { setActiveCompanyGroup } from '../store/slices/catalogueJurSlice/catalogueJurSlice';
|
||||||
import { getCatalogueJurThunk } from '../store/slices/catalogueJurSlice/thunks';
|
import { getCatalogueJurThunk } from '../store/slices/catalogueJurSlice/thunks';
|
||||||
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';
|
||||||
@ -12,6 +14,7 @@ import type { RelatedGroupOut } from '../types/api';
|
|||||||
import type { CatalogueJurFormProps } from '../types/catalogueJur/catalogueJur';
|
import type { CatalogueJurFormProps } from '../types/catalogueJur/catalogueJur';
|
||||||
import type { CreateCompanyGroupProps } from '../types/companyGroups/companyGroups';
|
import type { CreateCompanyGroupProps } from '../types/companyGroups/companyGroups';
|
||||||
import type { CreateEntityKind, RelatedGroupFormType } from '../types/relatedGroup/relatedGroup';
|
import type { CreateEntityKind, RelatedGroupFormType } from '../types/relatedGroup/relatedGroup';
|
||||||
|
import { useNotificationApi } from '../utils/useNotification';
|
||||||
import { CataloguGroupTabs, type CatalogueGroupTabKey } from '../widgets/CataloguGroupTabs/CataloguGroupTabs';
|
import { CataloguGroupTabs, type CatalogueGroupTabKey } from '../widgets/CataloguGroupTabs/CataloguGroupTabs';
|
||||||
import { CompanyGroupHeader } from '../widgets/CatalogueGroupHeader/CatalogueGroupHeader';
|
import { CompanyGroupHeader } from '../widgets/CatalogueGroupHeader/CatalogueGroupHeader';
|
||||||
import { CatalogueJurForm } from '../widgets/CatalogueJurForm/CatalogueJurForm';
|
import { CatalogueJurForm } from '../widgets/CatalogueJurForm/CatalogueJurForm';
|
||||||
@ -24,6 +27,7 @@ import { RelatedGroupTable } from '../widgets/RelatedGroupTable/RelatedGroupTabl
|
|||||||
|
|
||||||
export function CatalogueGroup() {
|
export function CatalogueGroup() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const notification = useNotificationApi();
|
||||||
|
|
||||||
const isGettingArchive = useAppSelector((state) => state.catalogue_jur.isGettingArchive);
|
const isGettingArchive = useAppSelector((state) => state.catalogue_jur.isGettingArchive);
|
||||||
|
|
||||||
@ -35,6 +39,7 @@ export function CatalogueGroup() {
|
|||||||
const [gszData, setGszData] = useState<RelatedGroupOut[]>([]);
|
const [gszData, setGszData] = useState<RelatedGroupOut[]>([]);
|
||||||
const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup);
|
const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup);
|
||||||
const relatedGroupFilter = useAppSelector((state) => state.catalogue_jur.relatedGroupFilter);
|
const relatedGroupFilter = useAppSelector((state) => state.catalogue_jur.relatedGroupFilter);
|
||||||
|
const rawRows = useAppSelector((state) => state.company_groups.raw_rows);
|
||||||
|
|
||||||
async function createJurHandler(values: CatalogueJurFormProps) {
|
async function createJurHandler(values: CatalogueJurFormProps) {
|
||||||
await createCatalogueJur(values);
|
await createCatalogueJur(values);
|
||||||
@ -68,19 +73,33 @@ export function CatalogueGroup() {
|
|||||||
async function getRelatedGroupsHandler() {
|
async function getRelatedGroupsHandler() {
|
||||||
if (activeCompanyGroup?.id) {
|
if (activeCompanyGroup?.id) {
|
||||||
setRelatedGroupLoading(true);
|
setRelatedGroupLoading(true);
|
||||||
|
try {
|
||||||
const res = await fetchRelatedGroups(activeCompanyGroup?.id);
|
const res = await fetchRelatedGroups(activeCompanyGroup?.id);
|
||||||
const gsks = res.filter((elem) => elem.kind === 'gsk');
|
const gsks = res.filter((elem) => elem.kind === 'gsk');
|
||||||
const gszs = res.filter((elem) => elem.kind === 'gsz');
|
const gszs = res.filter((elem) => elem.kind === 'gsz');
|
||||||
setGskData(gsks);
|
setGskData(gsks);
|
||||||
setGszData(gszs);
|
setGszData(gszs);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiError) {
|
||||||
|
notification.error({ description: e.message });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
setRelatedGroupLoading(false);
|
setRelatedGroupLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRelatedGroupsHandler();
|
getRelatedGroupsHandler();
|
||||||
}, [activeCompanyGroup]);
|
}, [activeCompanyGroup]);
|
||||||
|
|
||||||
|
// Без выбранной ГК таблица ЮЛ не грузится — выбираем первую ГК списка
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activeCompanyGroup && rawRows?.length) {
|
||||||
|
dispatch(setActiveCompanyGroup(rawRows[0]));
|
||||||
|
}
|
||||||
|
}, [rawRows, activeCompanyGroup, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveTab('legal_entities');
|
setActiveTab('legal_entities');
|
||||||
}, [relatedGroupFilter]);
|
}, [relatedGroupFilter]);
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import type {
|
|||||||
MoveCatalogueJurProps,
|
MoveCatalogueJurProps,
|
||||||
WithdrawCatalogueJurProps,
|
WithdrawCatalogueJurProps,
|
||||||
} from '../../../types/catalogueJur/catalogueJur';
|
} from '../../../types/catalogueJur/catalogueJur';
|
||||||
|
import { LE_PAGE } from './company_groups';
|
||||||
|
|
||||||
export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) {
|
export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) {
|
||||||
const res = await api.get<Page<ICatalogueJur>>('/directories/legal-entities', { limit: 200, offset: 0, ...params });
|
const res = await api.get<Page<ICatalogueJur>>('/directories/legal-entities', { limit: LE_PAGE, offset: 0, ...params });
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { type PayloadAction, createSlice } from '@reduxjs/toolkit';
|
import { type PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import type { RelatedGroupOut } from '../../../types/api';
|
import type { RelatedGroupOut } from '../../../types/api';
|
||||||
import type { ICompanyGroup } from '../../../types/companyGroups/companyGroups';
|
import type { ICompanyGroup } from '../../../types/companyGroups/companyGroups';
|
||||||
|
import { getCompanyGroupsThunk } from '../companyGroupsSlice/thunks';
|
||||||
import { getCatalogueJurThunk } from './thunks';
|
import { getCatalogueJurThunk } from './thunks';
|
||||||
import type { CatalogueJurSliceType } from './types';
|
import type { CatalogueJurSliceType } from './types';
|
||||||
|
|
||||||
@ -43,6 +44,12 @@ const catalogueJurSlice = createSlice({
|
|||||||
.addCase(getCatalogueJurThunk.rejected, (state, action) => {
|
.addCase(getCatalogueJurThunk.rejected, (state, action) => {
|
||||||
state.error = action.payload ?? 'Неизвестная ошибка';
|
state.error = action.payload ?? 'Неизвестная ошибка';
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
})
|
||||||
|
// Выбранная ГК — копия строки списка ГК: после перезагрузки списка берём свежую (счётчик ЮЛ в шапке и вкладке)
|
||||||
|
.addCase(getCompanyGroupsThunk.fulfilled, (state, action) => {
|
||||||
|
if (state.activeCompanyGroup?.id) {
|
||||||
|
state.activeCompanyGroup = action.payload.find((elem) => elem.id === state.activeCompanyGroup?.id) ?? null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -375,7 +375,7 @@ export interface ConsolidationCreate {
|
|||||||
export interface RelatedGroupOut {
|
export interface RelatedGroupOut {
|
||||||
id: string;
|
id: string;
|
||||||
code: string;
|
code: string;
|
||||||
crm_id: string;
|
crm_id: string | null;
|
||||||
company_group_id: string;
|
company_group_id: string;
|
||||||
kind: 'gsk' | 'gsz';
|
kind: 'gsk' | 'gsz';
|
||||||
name: string;
|
name: string;
|
||||||
@ -390,7 +390,6 @@ export interface RelatedGroupMemberOut {
|
|||||||
}
|
}
|
||||||
export interface RelatedGroupDetail extends RelatedGroupOut {
|
export interface RelatedGroupDetail extends RelatedGroupOut {
|
||||||
members: RelatedGroupMemberOut[];
|
members: RelatedGroupMemberOut[];
|
||||||
crm_id: string;
|
|
||||||
}
|
}
|
||||||
export interface LegalEntityBrief {
|
export interface LegalEntityBrief {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export type RelatedGroupFormType = {
|
export type RelatedGroupFormType = {
|
||||||
name: string;
|
name: string;
|
||||||
created_date: string | null;
|
created_date: string | null;
|
||||||
crm_id: string;
|
crm_id: string | null;
|
||||||
kind?: 'gsk' | 'gsz';
|
kind?: 'gsk' | 'gsz';
|
||||||
company_group_id?: string;
|
company_group_id?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,14 @@ type Props = {
|
|||||||
jurId?: string;
|
jurId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ошибки сервера, относящиеся к одному полю, показываем у этого поля
|
||||||
|
const FIELD_BY_ERROR_CODE: Record<string, keyof CatalogueJurFormProps> = {
|
||||||
|
INN_EXISTS: 'inn',
|
||||||
|
INVALID_INN: 'inn',
|
||||||
|
INVALID_KPP: 'kpp',
|
||||||
|
CRM_ID_EXISTS: 'crm_id',
|
||||||
|
};
|
||||||
|
|
||||||
export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId }) => {
|
export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId }) => {
|
||||||
const [form] = Form.useForm<CatalogueJurFormProps>();
|
const [form] = Form.useForm<CatalogueJurFormProps>();
|
||||||
const notification = useNotificationApi();
|
const notification = useNotificationApi();
|
||||||
@ -116,21 +124,9 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ApiError) {
|
if (e instanceof ApiError) {
|
||||||
if (e.code === 'INN_EXISTS') {
|
const field = e.code ? FIELD_BY_ERROR_CODE[e.code] : undefined;
|
||||||
form.setFields([
|
if (field) {
|
||||||
{
|
form.setFields([{ name: field, errors: [e.message] }]);
|
||||||
name: 'inn',
|
|
||||||
errors: [e.message],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (e.code === 'CRM_ID_EXISTS') {
|
|
||||||
form.setFields([
|
|
||||||
{
|
|
||||||
name: 'crm_id',
|
|
||||||
errors: [e.message],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
notification.error({ description: e.message });
|
notification.error({ description: e.message });
|
||||||
}
|
}
|
||||||
@ -194,7 +190,11 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Form.Item name='kpp' label='КПП' rules={[{ pattern: /^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$/, message: 'Некорректный формат' }]}>
|
<Form.Item
|
||||||
|
name='kpp'
|
||||||
|
label='КПП'
|
||||||
|
normalize={(value?: string) => value?.toUpperCase()}
|
||||||
|
rules={[{ pattern: /^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$/, message: 'Некорректный формат' }]}>
|
||||||
<Input placeholder='000000000' maxLength={9} disabled={type === 'ИП'} style={{ width: '100%' }} />
|
<Input placeholder='000000000' maxLength={9} disabled={type === 'ИП'} style={{ width: '100%' }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@ -128,7 +128,10 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// «ЮЛ вне ГК» грузится без выбранной ГК; список ГК — только по выбранной (без неё запрос ушёл бы по всем ГК)
|
||||||
|
if (outside_group || activeCompanyGroup?.id) {
|
||||||
getCatalogueJur();
|
getCatalogueJur();
|
||||||
|
}
|
||||||
}, [isGettingArchive, activeCompanyGroup, outside_group]);
|
}, [isGettingArchive, activeCompanyGroup, outside_group]);
|
||||||
|
|
||||||
const extraColumns: ColumnsType<ICatalogueJur> = useMemo(
|
const extraColumns: ColumnsType<ICatalogueJur> = useMemo(
|
||||||
|
|||||||
@ -15,6 +15,7 @@ interface ConsolidationChessTableFilterI {
|
|||||||
credit: string[];
|
credit: string[];
|
||||||
onCreditChange: (val: string[]) => void;
|
onCreditChange: (val: string[]) => void;
|
||||||
allCompanies: MatrixCompany[];
|
allCompanies: MatrixCompany[];
|
||||||
|
/** Год и квартал версии — только для показа: версия задаётся адресом. */
|
||||||
year?: number;
|
year?: number;
|
||||||
quarter?: Quarter;
|
quarter?: Quarter;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
|
|||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
crm_id: data?.crm_id,
|
crm_id: data?.crm_id,
|
||||||
name: data?.name,
|
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,
|
company_group_id: data?.company_group_id,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const relateGroupConfig: TableConfig<RelatedGroupOut> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'crm_id',
|
key: 'crm_id',
|
||||||
title: 'CRM ID',
|
title: 'ID в CRM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user