diff --git a/web/src/utils/chessMatrixMode.test.ts b/web/src/utils/chessMatrixMode.test.ts new file mode 100644 index 0000000..d065c4b --- /dev/null +++ b/web/src/utils/chessMatrixMode.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest'; +import { chessMatrixMode } from './chessMatrixMode'; + +describe('chessMatrixMode', () => { + it('фильтры пусты — полная матрица', () => { + expect(chessMatrixMode([], [])).toBe('all'); + }); + it('выбраны Дт и Кт — ручной расчёт', () => { + expect(chessMatrixMode(['62'], ['60'])).toBe('manual'); + }); + it('только Дт — одна сторона', () => { + expect(chessMatrixMode(['62'], [])).toBe('half'); + }); + it('только Кт — одна сторона', () => { + expect(chessMatrixMode([], ['60'])).toBe('half'); + }); +}); diff --git a/web/src/utils/chessMatrixMode.ts b/web/src/utils/chessMatrixMode.ts new file mode 100644 index 0000000..a5e08b6 --- /dev/null +++ b/web/src/utils/chessMatrixMode.ts @@ -0,0 +1,16 @@ +/** + * Режим запроса шахматки по фильтрам счетов: + * - `all` — фильтры пусты, берём полную матрицу; + * - `manual` — выбраны и Дт, и Кт, считаем по выбранным счетам; + * - `half` — выбрана только одна сторона: запрос не шлём, показываем подсказку. + */ +export type ChessMatrixMode = 'all' | 'manual' | 'half'; + +/** Текст для состояния «выбрана одна сторона» — в таблице и у фильтров один и тот же (openspec intercompany-matrix-ui). */ +export const CHESS_HALF_FILTER_HINT = 'Для расчёта по счетам выберите счета и в Дт, и в Кт'; + +export const chessMatrixMode = (debit: readonly string[], credit: readonly string[]): ChessMatrixMode => { + if (debit.length > 0 && credit.length > 0) return 'manual'; + if (debit.length === 0 && credit.length === 0) return 'all'; + return 'half'; +}; diff --git a/web/src/utils/consolidations.ts b/web/src/utils/consolidations.ts index 4a3c0f4..592e5ad 100644 --- a/web/src/utils/consolidations.ts +++ b/web/src/utils/consolidations.ts @@ -140,7 +140,7 @@ export const shouldWarnCounterPairs = ( pairs: readonly CounterPairOut[], storage: Pick | null, ): boolean => { - if (pairs?.length === 0) return false; + if (pairs.length === 0) return false; if (status && FINISHED_STATUSES.includes(status)) return false; try { return storage?.getItem(`${COUNTER_PAIRS_STORAGE_PREFIX}${versionId}`) !== counterPairsKey(pairs); diff --git a/web/src/widgets/CatalogueCompanyGroup/CatalogueCompanyGroupTabs/CatalogueCompanyGroupTabs.tsx b/web/src/widgets/CatalogueCompanyGroup/CatalogueCompanyGroupTabs/CatalogueCompanyGroupTabs.tsx index 4593d85..b6f770b 100644 --- a/web/src/widgets/CatalogueCompanyGroup/CatalogueCompanyGroupTabs/CatalogueCompanyGroupTabs.tsx +++ b/web/src/widgets/CatalogueCompanyGroup/CatalogueCompanyGroupTabs/CatalogueCompanyGroupTabs.tsx @@ -2,7 +2,7 @@ import { Tabs } from 'antd'; import { useAppSelector } from '../../../store/store'; import type { RelatedGroupOut } from '../../../types/api'; import './CatalogueCompanyGroupTabs.css'; -interface CataloguGroupTabsI { +interface CatalogueCompanyGroupTabsI { activeTab: CatalogueCompanyGroupTabKey; setActiveTab: (val: CatalogueCompanyGroupTabKey) => void; gskData: RelatedGroupOut[]; @@ -11,7 +11,7 @@ interface CataloguGroupTabsI { export type CatalogueCompanyGroupTabKey = 'legal_entities' | 'gsk' | 'gsz'; -export const CatalogueCompanyGroupTabs = ({ activeTab, setActiveTab, gskData, gszData }: CataloguGroupTabsI) => { +export const CatalogueCompanyGroupTabs = ({ activeTab, setActiveTab, gskData, gszData }: CatalogueCompanyGroupTabsI) => { const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); return ( diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx index 23b7244..a64b8c3 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx @@ -13,6 +13,7 @@ import { } from '../../../services/api/intercompanyMatrix'; import type { IntercompanyMatrixResponse, IntercompanyMatrixResultResponse } from '../../../types/api'; import type { VersionCard } from '../../../types/consolidations/version_members'; +import { chessMatrixMode } from '../../../utils/chessMatrixMode'; import { saveBlob, xlsxFilename } from '../../../utils/download'; import { useNotificationApi } from '../../../utils/useNotification'; import { ConsolidationChessFormatTableFilter } from '../ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter'; @@ -72,14 +73,15 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { return () => document.removeEventListener('fullscreenchange', handleFullscreenChange); }, []); - const isHalfFiltered = (debit.length > 0 && credit.length === 0) || (debit.length === 0 && credit.length > 0); + const matrixMode = chessMatrixMode(debit, credit); + const isHalfFiltered = matrixMode === 'half'; const loadData = useCallback( async (vId: string, dIds: string[], cIds: string[], isActual: () => boolean) => { setLoading(true); try { let res: IntercompanyMatrixResponse; - if (dIds?.length && cIds?.length) { + if (chessMatrixMode(dIds, cIds) === 'manual') { res = await fetchChessConsolidationsManual(vId, { debit_account_ids: dIds, credit_account_ids: cIds }); } else { res = await fetchChessConsolidations(vId); @@ -97,7 +99,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { } } }, - [notification, isHalfFiltered], + [notification], ); const loadResData = useCallback( async (vid: string) => { @@ -124,7 +126,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { } setMatrixExportLoading(true); try { - if (debit?.length && credit?.length) { + if (matrixMode === 'manual') { const { blob, filename } = await exportChessTableManual(version_id, { debit_account_ids: debit, credit_account_ids: credit }); saveBlob(blob, filename ?? xlsxFilename('Шахматка', versionInfo)); } else { @@ -167,7 +169,8 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { }, [data, companies]); useEffect(() => { - if (!version_id) return; + // выбрана только одна сторона Дт/Кт — запрос не шлём, таблица показывает подсказку + if (!version_id || isHalfFiltered) return; let isMounted = true; const isActual = () => isMounted; @@ -178,7 +181,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { isMounted = false; setLoading(false); }; - }, [version_id, debit, credit, loadData]); + }, [version_id, debit, credit, isHalfFiltered, loadData]); useEffect(() => { if (!version_id) return; @@ -202,7 +205,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { style={{ backgroundColor: isChessFullscreen ? '#fff' : 'transparent', padding: isChessFullscreen ? 6 : 0, - height: isChessFullscreen ? '100vh' : '100%', + height: isChessFullscreen ? '100vh' : 'auto', overflow: isChessFullscreen ? 'hidden' : 'visible', display: 'flex', flexDirection: 'column', diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidatedChessTable.css b/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidatedChessTable.css index bd5b3ec..71f3fb9 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidatedChessTable.css +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidatedChessTable.css @@ -17,15 +17,6 @@ background-color: #e6f4ea !important ; } -.chessTable .ant-table-container { - display: flex; - flex-direction: column; -} - .chessTable.customHeight .ant-table-container { - height: 550px !important; -} - -.chessTable .ant-table-summary { - margin-top: auto; + min-height: 550px; } diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidationChessTable.tsx b/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidationChessTable.tsx index 3850c73..b657f42 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidationChessTable.tsx +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessTable/ConsolidationChessTable.tsx @@ -5,6 +5,7 @@ import type React from 'react'; import { useMemo } from 'react'; import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../../types/api'; import './ConsolidatedChessTable.css'; +import { CHESS_HALF_FILTER_HINT } from '../../../utils/chessMatrixMode'; import { chessTableCellFormatValue } from '../../../utils/chessTableCellFormatValue'; import { sumAmounts } from '../../../utils/money'; @@ -111,6 +112,7 @@ export const ConsolidationChessTable: React.FC = ({ } }); + // сумму, которую не удалось сложить, не выдумываем нулём — ячейка покажет «—» totalRow[colCompany.id] = sumAmounts(amountsToSum) ?? ''; }); @@ -121,9 +123,9 @@ export const ConsolidationChessTable: React.FC = ({ return ( - className={!isFullscreen ? 'chessTable customHeight' : 'chessTable'} + className={isHalfFiltered && !isFullscreen ? 'chessTable customHeight' : 'chessTable'} dataSource={isHalfFiltered ? [] : data?.companies || []} - columns={isHalfFiltered ? [] : columns || []} + columns={isHalfFiltered ? [] : columns} rowKey='id' pagination={false} loading={loading} @@ -133,7 +135,7 @@ export const ConsolidationChessTable: React.FC = ({ title={() => toolbar} locale={{ emptyText: isHalfFiltered ? ( -
Для получения данных выберите счета по Дт и Кт
+
{CHESS_HALF_FILTER_HINT}
) : ( 'Нет данных' ), diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx b/web/src/widgets/ConsolidationChess/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx index fe3ff96..dc61696 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessTableFilter/ConsolidationChessTableFilter.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { ApiError, api } from '../../../api/client'; import { type OptionType, TreeFilterDropdown } from '../../../components/TreeFilterDropdown/TreeFilterDropdown'; import type { Account, MatrixCompany, Quarter } from '../../../types/api'; +import { CHESS_HALF_FILTER_HINT, chessMatrixMode } from '../../../utils/chessMatrixMode'; import { quarterShortLabel } from '../../../utils/labels'; import { useNotificationApi } from '../../../utils/useNotification'; @@ -109,7 +110,8 @@ export const ConsolidationChessTableFilter: React.FCВыберите счета по Дт и Кт для фильтрации; + const warningBlock = + chessMatrixMode(debit, credit) === 'half' ? {CHESS_HALF_FILTER_HINT} : null; return (