From a1054f08c9d06a10a0ea212ef0ff71366045d11d Mon Sep 17 00:00:00 2001 From: Grog Date: Thu, 5 Nov 2026 13:57:54 +0300 Subject: [PATCH] =?UTF-8?q?aurora-1544=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BD=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D1=8B=20=D0=B3=D1=83=D0=B4=D0=B2=D0=B8=D0=BB=20=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=B0=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D1=82=D1=80=20=D0=B5=D0=B4=D0=B8=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=20=D0=B8=20=D0=B7=D0=BD=D0=B0=D0=BA=D0=BE=D0=B2=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=B7=D0=B0=D0=BF=D1=8F=D1=82=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConsolidationFormatTableFilter.tsx} | 4 +- web/src/layouts/ConsolidationReportPage.tsx | 2 +- web/src/services/{ => api}/goodwill.ts | 4 +- web/src/types/goodwill/goodwill.ts | 14 ++- .../ConsolidatedChessContent.tsx | 6 +- .../ConsolidationGoodwillContent.tsx | 93 ++++++++++++++----- .../ConsolidationGoodwillTable.css | 5 + .../ConsolidationGoodwillTable.tsx | 73 ++++++++++++--- .../ConsolidationGoodwillTable/config.tsx | 9 +- 9 files changed, 161 insertions(+), 49 deletions(-) rename web/src/{widgets/ConsolidationChess/ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter.tsx => components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter.tsx} (90%) rename web/src/services/{ => api}/goodwill.ts (88%) diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter.tsx b/web/src/components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter.tsx similarity index 90% rename from web/src/widgets/ConsolidationChess/ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter.tsx rename to web/src/components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter.tsx index 5c2b5d9..79ee8f5 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter.tsx +++ b/web/src/components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter.tsx @@ -1,14 +1,14 @@ import { Select, Space } from 'antd'; import type React from 'react'; -interface ConsolidationChessFormatTableFilterI { +interface ConsolidationFormatTableFilterI { decimals: number; onDecimalsChange: (val: number) => void; units: string; onUnitsChange: (val: string) => void; } -export const ConsolidationChessFormatTableFilter: React.FC = ({ +export const ConsolidationFormatTableFilter: React.FC = ({ decimals, onDecimalsChange, units, diff --git a/web/src/layouts/ConsolidationReportPage.tsx b/web/src/layouts/ConsolidationReportPage.tsx index 5823352..bf19cd7 100644 --- a/web/src/layouts/ConsolidationReportPage.tsx +++ b/web/src/layouts/ConsolidationReportPage.tsx @@ -32,7 +32,7 @@ function ReportContent() { osv: , chess: , - goodwill: , + goodwill: , }; const ActiveTab = TAB_COMPONENTS[tab as string] ?? null; diff --git a/web/src/services/goodwill.ts b/web/src/services/api/goodwill.ts similarity index 88% rename from web/src/services/goodwill.ts rename to web/src/services/api/goodwill.ts index 294d801..90a97e6 100644 --- a/web/src/services/goodwill.ts +++ b/web/src/services/api/goodwill.ts @@ -1,5 +1,5 @@ -import type { GoodwillTableResponse, GoodwillRow, GoodwillRowPayload, GoodwillSettingsPayload } from '../types/goodwill/goodwill'; -import { api, apiFetchBlob } from '../api/client'; +import type { GoodwillTableResponse, GoodwillRow, GoodwillRowPayload, GoodwillSettingsPayload } from '../../types/goodwill/goodwill'; +import { api, apiFetchBlob } from '../../api/client'; export function fetchGoodwillTable(versionId: string): Promise { return api.get(`/consolidations/versions/${versionId}/goodwill`); diff --git a/web/src/types/goodwill/goodwill.ts b/web/src/types/goodwill/goodwill.ts index 4616126..993dce9 100644 --- a/web/src/types/goodwill/goodwill.ts +++ b/web/src/types/goodwill/goodwill.ts @@ -34,10 +34,20 @@ export interface GoodwillTableResponse { totals: GoodwillTotals; } -// Для дерева Ant Design (с добавлением группировки) export type GoodwillTreeRow = GoodwillRow & { key: string; children?: GoodwillTreeRow[]; isGroup?: boolean; isTotal?: boolean; -}; \ No newline at end of file + isGroupTotal?:boolean +}; + +export interface GoodwillRowPayload { + ownership_share: string; + include_in_kfo: boolean; + comment: string | null; +} + +export interface GoodwillSettingsPayload { + goodwill_enabled: boolean; +} \ No newline at end of file diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx index 3af164a..7009d92 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx @@ -16,7 +16,7 @@ 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'; +import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; import { ConsolidationChessResTable } from '../ConsolidationChessResTable/ConsolidationChessResTable'; import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable'; import { ConsolidationChessTableFilter } from '../ConsolidationChessTableFilter/ConsolidationChessTableFilter'; @@ -245,7 +245,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { backgroundColor: '#e5e7eb', }} /> - { gap: 16, }}>
- { +export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { const { version_id } = useParams<{ version_id: string }>(); const notification = useNotificationApi(); @@ -113,9 +115,11 @@ export const GoodwillContent = () => { const goodwillTableRef = useRef(null); const [isGoodwillFullscreen, setIsGoodwillFullscreen] = useState(false); const [goodwillFilter, setGoodwillFilter] = useState('allValue'); + const [exportLoading, setExportLoading] = useState(false); + const versionInfo = card ? { groupName: card.company_group.name, year: card.year, quarter: card.quarter } : null; const handleRowEdit = useCallback( - (rowKey: string, field: keyof GoodwillRow, value: any) => { + (rowKey: string, field: keyof GoodwillRow, value: boolean | string) => { const isParent = rowKey.startsWith('head-') && !rowKey.includes('-sub-'); if (isParent && field === 'include_in_kfo') { @@ -126,7 +130,7 @@ export const GoodwillContent = () => { const next = { ...prev }; children.forEach((child) => { const childKey = `${headId}-${child.subsidiary_legal_entity.id}`; - next[childKey] = { ...next[childKey], include_in_kfo: value }; + next[childKey] = { ...next[childKey], include_in_kfo: value as boolean }; }); return next; }); @@ -140,12 +144,13 @@ export const GoodwillContent = () => { }, [data?.rows], ); + const columns = useMemo(() => { return getGoodwillColumns({ units, decimals, isEditMode, editedRows, onRowEdit: handleRowEdit }); }, [units, decimals, isEditMode, editedRows, handleRowEdit]); const groupsConfig = useMemo(() => { - return { rowKey: 'key' as any, columns: columns as any }; + return { rowKey: 'key' as const, columns: columns as ColumnDef[] }; }, [columns]); const { visibleKeys, setVisibleKeys } = useVisibleColumns(groupsConfig); @@ -164,18 +169,19 @@ export const GoodwillContent = () => { notification.error({ description: e instanceof Error ? e.message : 'Ошибка' }); } }; + const dropFiltersHandler = () => { setGoodwillFilter('allValue'); }; - const loadData = useCallback(async () => { + const loadData = useCallback(async (vId: string) => { setLoading(true); try { - await new Promise((resolve) => setTimeout(resolve, 500)); - setData(MOCK_DATA); + const res = await fetchGoodwillTable(vId); + setData(res); const initialEdited: Record> = {}; - MOCK_DATA.rows.forEach((row) => { + res.rows.forEach((row) => { if (row.include_in_kfo) { const key = `${row.head_legal_entity.id}-${row.subsidiary_legal_entity.id}`; initialEdited[key] = { @@ -196,13 +202,35 @@ export const GoodwillContent = () => { }, [notification]); const handleSave = async () => { + if (!version_id || !data) return; setSaving(true); try { - await new Promise((resolve) => setTimeout(resolve, 800)); - notification.success({ description: 'Изменения сохранены (тестовый режим)' }); + const promises = Object.keys(editedRows).map((rowKey) => { + const changes = editedRows[rowKey]; + const originalRow = data.rows.find( + (r) => `${r.head_legal_entity.id}-${r.subsidiary_legal_entity.id}` === rowKey + ); + if (!originalRow) return; + + const payload: GoodwillRowPayload = { + ownership_share: String(changes.ownership_share ?? originalRow.ownership_share ?? ''), + include_in_kfo: changes.include_in_kfo ?? originalRow.include_in_kfo, + comment: changes.comment !== undefined ? changes.comment : (originalRow.comment ?? null), + }; + + return saveGoodwillRow( + version_id, + originalRow.head_legal_entity.id, + originalRow.subsidiary_legal_entity.id, + payload + ); + }); + + await Promise.all(promises); + notification.success({ description: 'Изменения сохранены' }); setEditedRows({}); setIsEditMode(false); - loadData(); + loadData(version_id); } catch (e) { if (e instanceof ApiError) { notification.error({ description: e.message }); @@ -211,6 +239,24 @@ export const GoodwillContent = () => { setSaving(false); } }; + + const download = async () => { + if (!version_id) { + return; + } + setExportLoading(true); + try { + const { blob, filename } = await exportGoodwillTable(version_id); + saveBlob(blob, filename ?? xlsxFilename('Гудвил', versionInfo)); + } catch (e) { + if (e instanceof ApiError) { + notification.error({ description: e.message }); + } + } finally { + setExportLoading(false); + } + }; + const filteredData = useMemo(() => { if (!data) return null; if (goodwillFilter === 'allValue') return data; @@ -245,8 +291,10 @@ export const GoodwillContent = () => { }, [editedRows, isEditMode]); useEffect(() => { - loadData(); - }, [loadData]); + if (version_id) { + loadData(version_id); + } + }, [version_id, loadData]); return ( (document.fullscreenElement as HTMLElement) || document.body}> @@ -330,7 +378,7 @@ export const GoodwillContent = () => { backgroundColor: '#e5e7eb', }} /> - { disabled={isEditMode || !data?.editable}> Редактировать +
); -}; +}; \ No newline at end of file diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css index a7f5cac..56addcd 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css @@ -16,3 +16,8 @@ .goodwillTable .resRow { background-color: #e6f4ea !important ; } + +.goodwillTable .group-total-row td { + background-color: #ecfdf3 !important; + font-weight: 600; +} diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx index 47a6a5d..5020f2b 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx @@ -18,7 +18,7 @@ interface ConsolidationGoodwillTableI { isFullscreen?: boolean; isEditMode: boolean; editedRows: Record>; - onRowEdit: (rowId: string, field: keyof GoodwillRow, value: any) => void; + onRowEdit: (rowId: string, field: keyof GoodwillRow, value: boolean | string) => void; visibleKeys: string[]; } @@ -42,10 +42,10 @@ export const ConsolidationGoodwillTable: React.FC = data.rows.forEach((row) => { const headId = row.head_legal_entity.id; - + if (!heads.has(headId)) { const groupNode: GoodwillTreeRow = { - ...row, + ...row, key: `head-${headId}`, isGroup: true, head_legal_entity: row.head_legal_entity, @@ -73,12 +73,44 @@ export const ConsolidationGoodwillTable: React.FC = }); }); - tree.forEach(parent => { + tree.forEach((parent) => { if (parent.children && parent.children.length > 0) { - const allChecked = parent.children.every(child => - editedRows[child.key]?.include_in_kfo ?? child.include_in_kfo - ); + const allChecked = parent.children.every((child) => editedRows[child.key]?.include_in_kfo ?? child.include_in_kfo); parent.include_in_kfo = allChecked; + + let totalFinInv = 0; + let totalParticipation = 0; + let totalCharter = 0; + let totalGoodwill = 0; + + parent.children.forEach((child) => { + totalFinInv += Number(child.financial_investment_amount || 0); + totalParticipation += Number(child.participation_amount || 0); + totalCharter += Number(child.charter_capital_amount || 0); + totalGoodwill += Number(child.goodwill_amount || 0); + }); + + const groupTotalRow: GoodwillTreeRow = { + key: `total-${parent.key}`, + head_legal_entity: parent.head_legal_entity, + subsidiary_legal_entity: { id: '', name: 'Всего' }, + isGroupTotal: true, + isGroup: false, + + financial_investment_amount: String(totalFinInv), + participation_amount: String(totalParticipation), + charter_capital_amount: String(totalCharter), + goodwill_amount: String(totalGoodwill), + + ownership_share: null, + additional_capital_amount: '0', + include_in_kfo: false, + comment: null, + updated_at: null, + updated_by: null, + }; + + parent.children.push(groupTotalRow); } }); @@ -90,7 +122,7 @@ export const ConsolidationGoodwillTable: React.FC = }, [units, decimals, isEditMode, editedRows, onRowEdit]); const visibleColumns = useMemo(() => { - return columns.filter(col => visibleKeys.includes(col.key as string)); + return columns.filter((col) => visibleKeys.includes(col.key as string)); }, [columns, visibleKeys]); const scrollY = isFullscreen ? 'calc(100vh - 200px)' : 'auto'; @@ -98,7 +130,7 @@ export const ConsolidationGoodwillTable: React.FC = const renderSummary = () => { if (!data?.totals) return null; - const ownershipIndex = visibleColumns.findIndex(c => c.key === 'ownership_share'); + const ownershipIndex = visibleColumns.findIndex((c) => c.key === 'ownership_share'); return ( @@ -106,7 +138,7 @@ export const ConsolidationGoodwillTable: React.FC = {visibleColumns.map((col, index) => { if (index === 0) { return ( - 0 ? ownershipIndex : 1} align='left'> + 0 ? ownershipIndex : 1} align='left'>
Итого по листу
@@ -117,7 +149,11 @@ export const ConsolidationGoodwillTable: React.FC = if (index < ownershipIndex) return null; if (col.key === 'ownership_share') { - return —; + return ( + + — + + ); } if (col.key === 'financial_investment_amount') { @@ -141,7 +177,11 @@ export const ConsolidationGoodwillTable: React.FC = } if (col.key === 'charter_capital_amount') { - return —; + return ( + + — + + ); } if (col.key === 'goodwill_amount') { @@ -155,7 +195,11 @@ export const ConsolidationGoodwillTable: React.FC = } if (col.key === 'comment') { - return —; + return ( + + — + + ); } return null; @@ -179,6 +223,7 @@ export const ConsolidationGoodwillTable: React.FC = title={() => toolbar} expandable={{ defaultExpandAllRows: true }} summary={renderSummary} + rowClassName={(record) => (record.isGroupTotal ? 'group-total-row' : '')} /> ); -}; \ No newline at end of file +}; diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/config.tsx b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/config.tsx index c0c99aa..adfc6c5 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/config.tsx +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/config.tsx @@ -10,7 +10,7 @@ interface GetColumnsParams { decimals: number; isEditMode: boolean; editedRows: Record>; - onRowEdit: (rowId: string, field: keyof GoodwillRow, value: any) => void; + onRowEdit: (rowId: string, field: keyof GoodwillRow, value: boolean | string) => void; } export const getGoodwillColumns = ({ @@ -32,6 +32,8 @@ export const getGoodwillColumns = ({ width: 120, align: 'center', render: (_, row) => { + if (row.isGroupTotal) return null; + const isChecked = row.isGroup ? row.include_in_kfo : (editedRows[row.key]?.include_in_kfo ?? row.include_in_kfo); return ( @@ -63,7 +65,7 @@ export const getGoodwillColumns = ({ return { style: { - backgroundColor: isInvalid ? '#fffbe6' : '#f6ffed', + backgroundColor: isInvalid ? '#fffbe6' : '#f6ffed', }, }; }, @@ -80,7 +82,7 @@ export const getGoodwillColumns = ({ min={0} max={100} step={1} - style={{ width: '100%' }} + style={{ width: '100%' }} value={val ? Math.floor(Number(val)) : ''} onChange={(e) => { const intValue = e.target.value.replace(/\D/g, ''); @@ -131,7 +133,6 @@ export const getGoodwillColumns = ({ const formatted = formatMoney(val); if (formatted === '—') return —; - const isNegative = String(val).trim().startsWith('-'); return (