[AURORA-1678]: Чек-лист документов:
- добавлен фильтр по документам - в запрос на загрузку добавлен параметр documents - Labeled вынесен в components/, убраны дубли в шахматке, таблице корректировок
This commit is contained in:
parent
967fa01fb6
commit
fbc6645239
10
web/src/components/Labeled.tsx
Normal file
10
web/src/components/Labeled.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Space } from 'antd';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
/** Поле с серой подписью сверху — для фильтров и настроек над таблицами (селекты отчётности, чек-листа). */
|
||||||
|
export const Labeled = ({ label, children }: { label: string; children: ReactNode }) => (
|
||||||
|
<Space orientation='vertical' size={0}>
|
||||||
|
<span style={{ fontSize: 12, color: '#667085' }}>{label}</span>
|
||||||
|
{children}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
@ -9,7 +9,12 @@ export const getConsolidations = (groupId: string, in_work_only: boolean) =>
|
|||||||
api.get<IConsolidation[]>(`/company-groups/${groupId}/consolidations`, { in_work_only });
|
api.get<IConsolidation[]>(`/company-groups/${groupId}/consolidations`, { in_work_only });
|
||||||
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
||||||
export const createConsolidation = (body: ConsolidationCreate) => api.post<VersionCard>('/consolidations', body);
|
export const createConsolidation = (body: ConsolidationCreate) => api.post<VersionCard>('/consolidations', body);
|
||||||
export const downloadChecklistCsv = (versionId: string) => apiFetchBlob(`/consolidations/versions/${versionId}/checklist?format=csv`);
|
/** CSV чек-листа; `documents` — отбор колонок (`BALANCE,INCOME,OSV:01`), не задан — вся матрица. */
|
||||||
|
export const downloadChecklistCsv = (versionId: string, documents?: string) => {
|
||||||
|
const params = new URLSearchParams({ format: 'csv' });
|
||||||
|
if (documents) params.set('documents', documents);
|
||||||
|
return apiFetchBlob(`/consolidations/versions/${versionId}/checklist?${params}`);
|
||||||
|
};
|
||||||
export const getChecklist = (versionId: string) => api.get<ChecklistBackendShape>(`/consolidations/versions/${versionId}/checklist`);
|
export const getChecklist = (versionId: string) => api.get<ChecklistBackendShape>(`/consolidations/versions/${versionId}/checklist`);
|
||||||
/** «Взять в работу»: 201 — взята, 200 — уже ваша; чужая — 409 `LOCKED_BY_OTHER` (перехват — с `takeover_reason`). */
|
/** «Взять в работу»: 201 — взята, 200 — уже ваша; чужая — 409 `LOCKED_BY_OTHER` (перехват — с `takeover_reason`). */
|
||||||
export const lockVersion = (versionId: string, reason?: string | null) =>
|
export const lockVersion = (versionId: string, reason?: string | null) =>
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
// Фильтр «Документы» окна чек-листа: селект как в отчётности (подпись сверху, underlined), в списке — чекбоксы.
|
||||||
|
import { Checkbox, Select } from 'antd';
|
||||||
|
import { Labeled } from '../../components/Labeled';
|
||||||
|
import { documentFilterLabel, toggleAllDocuments, toggleDocument } from './checklist_utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options: { key: string; title: string }[];
|
||||||
|
value: string[];
|
||||||
|
onChange: (next: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemStyle: React.CSSProperties = { display: 'flex', padding: '5px 12px', borderRadius: 4, cursor: 'pointer' };
|
||||||
|
|
||||||
|
export function ChecklistDocumentsFilter({ options, value, onChange }: Props) {
|
||||||
|
const allKeys = options.map((o) => o.key);
|
||||||
|
const isAll = value.length === allKeys.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Labeled label='Документы'>
|
||||||
|
<Select
|
||||||
|
variant='underlined'
|
||||||
|
style={{ minWidth: 180 }}
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
value='summary'
|
||||||
|
options={[{ value: 'summary', label: documentFilterLabel(value, allKeys.length) }]}
|
||||||
|
popupRender={() => (
|
||||||
|
<>
|
||||||
|
<Checkbox
|
||||||
|
style={itemStyle}
|
||||||
|
checked={isAll}
|
||||||
|
indeterminate={value.length > 0 && !isAll}
|
||||||
|
onChange={() => onChange(toggleAllDocuments(value, allKeys))}>
|
||||||
|
Все документы
|
||||||
|
</Checkbox>
|
||||||
|
{options.map((o) => (
|
||||||
|
<Checkbox
|
||||||
|
key={o.key}
|
||||||
|
style={itemStyle}
|
||||||
|
checked={value.includes(o.key)}
|
||||||
|
onChange={() => onChange(toggleDocument(value, o.key, allKeys))}>
|
||||||
|
{o.title}
|
||||||
|
</Checkbox>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Labeled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,25 +5,40 @@ import { ApiError } from '../../api/client';
|
|||||||
import { AppModal } from '../../components/AppModal/AppModal';
|
import { AppModal } from '../../components/AppModal/AppModal';
|
||||||
import { getChecklist } from '../../services/api/consolidations';
|
import { getChecklist } from '../../services/api/consolidations';
|
||||||
import type { ChecklistBackendShape, IVersionCard } from '../../types/consolidations/version_members';
|
import type { ChecklistBackendShape, IVersionCard } from '../../types/consolidations/version_members';
|
||||||
import { buildChecklistColumns, buildChecklistRows, getMarksItems } from './checklist_utils';
|
import { ChecklistDocumentsFilter } from './ChecklistDocumentsFilter';
|
||||||
|
import {
|
||||||
|
buildChecklistColumns,
|
||||||
|
buildChecklistRows,
|
||||||
|
buildDocumentFilterOptions,
|
||||||
|
documentsQueryParam,
|
||||||
|
getMarksItems,
|
||||||
|
} from './checklist_utils';
|
||||||
|
|
||||||
type ChecklistProps = {
|
type ChecklistProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onDownload: () => void;
|
onDownload: (documents?: string) => void;
|
||||||
consolidationData: IVersionCard;
|
consolidationData: IVersionCard;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ChecklistModal({ open, onDownload, consolidationData, onClose }: ChecklistProps) {
|
export default function ChecklistModal({ open, onDownload, consolidationData, onClose }: ChecklistProps) {
|
||||||
const [checklistData, setChecklistData] = useState<ChecklistBackendShape | null>(null);
|
const [checklistData, setChecklistData] = useState<ChecklistBackendShape | null>(null);
|
||||||
|
const [selectedDocuments, setSelectedDocuments] = useState<string[]>([]);
|
||||||
|
|
||||||
const rows = useMemo(() => (checklistData ? buildChecklistRows(checklistData) : []), [checklistData]);
|
const rows = useMemo(() => (checklistData ? buildChecklistRows(checklistData) : []), [checklistData]);
|
||||||
const columns = useMemo(() => (checklistData ? buildChecklistColumns(checklistData) : []), [checklistData]);
|
const columns = useMemo(
|
||||||
|
() => (checklistData ? buildChecklistColumns(checklistData, selectedDocuments) : []),
|
||||||
|
[checklistData, selectedDocuments],
|
||||||
|
);
|
||||||
|
const documentOptions = useMemo(() => (checklistData ? buildDocumentFilterOptions(checklistData) : []), [checklistData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!consolidationData.id || !open) return;
|
if (!consolidationData.id || !open) return;
|
||||||
getChecklist(consolidationData.id)
|
getChecklist(consolidationData.id)
|
||||||
.then(setChecklistData)
|
.then((data) => {
|
||||||
|
setChecklistData(data);
|
||||||
|
setSelectedDocuments(buildDocumentFilterOptions(data).map((o) => o.key));
|
||||||
|
})
|
||||||
.catch((e: unknown) => message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить чек-лист'));
|
.catch((e: unknown) => message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить чек-лист'));
|
||||||
}, [open, consolidationData.id]);
|
}, [open, consolidationData.id]);
|
||||||
|
|
||||||
@ -33,15 +48,21 @@ export default function ChecklistModal({ open, onDownload, consolidationData, on
|
|||||||
open={open}
|
open={open}
|
||||||
cancelText='Отмена'
|
cancelText='Отмена'
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
title='Чеклист'
|
title='Чек-лист загруженных документов'
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
onDownload();
|
onDownload(
|
||||||
|
documentsQueryParam(
|
||||||
|
selectedDocuments,
|
||||||
|
documentOptions.map((o) => o.key),
|
||||||
|
),
|
||||||
|
);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
okText='Скачать чек-лист'
|
okText='Скачать'
|
||||||
okButtonProps={{ icon: <DownloadOutlined /> }}>
|
okButtonProps={{ icon: <DownloadOutlined />, disabled: selectedDocuments.length === 0 }}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||||
|
<ChecklistDocumentsFilter options={documentOptions} value={selectedDocuments} onChange={setSelectedDocuments} />
|
||||||
{getMarksItems()}
|
{getMarksItems()}
|
||||||
<Table rowKey='key' columns={columns} dataSource={rows} pagination={false} />
|
<Table rowKey='key' columns={columns} dataSource={rows} pagination={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -39,10 +39,10 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
|||||||
}, [consolidation_id, reloadKey]);
|
}, [consolidation_id, reloadKey]);
|
||||||
const rawRows = useMemo(() => normalizeData(consolidationData), [consolidationData]);
|
const rawRows = useMemo(() => normalizeData(consolidationData), [consolidationData]);
|
||||||
|
|
||||||
const downloadChecklist = async () => {
|
const downloadChecklist = async (documents?: string) => {
|
||||||
if (!consolidationData) return;
|
if (!consolidationData) return;
|
||||||
try {
|
try {
|
||||||
const { blob, filename } = await downloadChecklistCsv(consolidationData.id);
|
const { blob, filename } = await downloadChecklistCsv(consolidationData.id, documents);
|
||||||
const card = consolidationData;
|
const card = consolidationData;
|
||||||
saveBlob(blob, filename ?? checklistFilename(card.company_group.name, card.year, card.quarter));
|
saveBlob(blob, filename ?? checklistFilename(card.company_group.name, card.year, card.quarter));
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { ChecklistBackendShape } from '../../types/consolidations/version_members';
|
import type { ChecklistBackendShape } from '../../types/consolidations/version_members';
|
||||||
import { buildChecklistRows } from './checklist_utils';
|
import {
|
||||||
|
buildChecklistColumns,
|
||||||
|
buildChecklistRows,
|
||||||
|
buildDocumentFilterOptions,
|
||||||
|
documentFilterLabel,
|
||||||
|
documentsQueryParam,
|
||||||
|
toggleAllDocuments,
|
||||||
|
toggleDocument,
|
||||||
|
} from './checklist_utils';
|
||||||
|
|
||||||
const checklist: ChecklistBackendShape = {
|
const checklist: ChecklistBackendShape = {
|
||||||
rules: [],
|
rules: [],
|
||||||
@ -37,3 +45,96 @@ describe('buildChecklistRows', () => {
|
|||||||
expect(rows[0]).toMatchObject({ balance: 'MISSING', ofr: 'MISSING', osv: {} });
|
expect(rows[0]).toMatchObject({ balance: 'MISSING', ofr: 'MISSING', osv: {} });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const twoOsv: ChecklistBackendShape = {
|
||||||
|
rules: [],
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
...checklist.rows[0],
|
||||||
|
cells: [
|
||||||
|
...checklist.rows[0].cells,
|
||||||
|
{ document_type: 'OSV', account: '01', status: 'CONFIRMED', missing_quarters: [], document_ids: ['d4'] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Ключи колонок верхнего уровня и подколонок ОСВ. */
|
||||||
|
const columnKeys = (selected?: string[]) =>
|
||||||
|
buildChecklistColumns(twoOsv, selected).map((c) => ('children' in c && c.children ? [c.key, c.children.map((ch) => ch.key)] : c.key));
|
||||||
|
|
||||||
|
describe('buildDocumentFilterOptions', () => {
|
||||||
|
it('ББ, ОФР и ОСВ по счетам в порядке колонок', () => {
|
||||||
|
expect(buildDocumentFilterOptions(twoOsv)).toEqual([
|
||||||
|
{ key: 'balance', title: 'Бух. баланс' },
|
||||||
|
{ key: 'ofr', title: 'ОФР' },
|
||||||
|
{ key: 'osv_60', title: 'ОСВ 60' },
|
||||||
|
{ key: 'osv_01', title: 'ОСВ 01' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildChecklistColumns', () => {
|
||||||
|
it('без выбора — все документы', () => {
|
||||||
|
expect(columnKeys()).toEqual(['name', 'balance', 'ofr', ['osv', ['osv_60', 'osv_01']]]);
|
||||||
|
expect(columnKeys(['balance', 'ofr', 'osv_60', 'osv_01'])).toEqual(columnKeys());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('только выбранные документы; организация — всегда', () => {
|
||||||
|
expect(columnKeys(['ofr', 'osv_01'])).toEqual(['name', 'ofr', ['osv', ['osv_01']]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ни одного счёта ОСВ не выбрано — группы ОСВ нет', () => {
|
||||||
|
expect(columnKeys(['balance'])).toEqual(['name', 'balance']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('документы не выбраны — только организация', () => {
|
||||||
|
expect(columnKeys([])).toEqual(['name']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleDocument', () => {
|
||||||
|
const all = ['balance', 'ofr', 'osv_01'];
|
||||||
|
|
||||||
|
it('снятие и отметка пункта; порядок — как у пунктов', () => {
|
||||||
|
expect(toggleDocument(all, 'ofr', all)).toEqual(['balance', 'osv_01']);
|
||||||
|
expect(toggleDocument(['osv_01'], 'balance', all)).toEqual(['balance', 'osv_01']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('сняли последний пункт — ничего не выбрано', () => {
|
||||||
|
expect(toggleDocument(['ofr'], 'ofr', all)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleAllDocuments', () => {
|
||||||
|
const all = ['balance', 'ofr', 'osv_01'];
|
||||||
|
|
||||||
|
it('при всех отмеченных снимает все, иначе отмечает все', () => {
|
||||||
|
expect(toggleAllDocuments(all, all)).toEqual([]);
|
||||||
|
expect(toggleAllDocuments(['ofr'], all)).toEqual(all);
|
||||||
|
expect(toggleAllDocuments([], all)).toEqual(all);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('documentFilterLabel', () => {
|
||||||
|
it('подпись по числу отмеченных', () => {
|
||||||
|
expect(documentFilterLabel(['a', 'b', 'c'], 3)).toBe('Все документы');
|
||||||
|
expect(documentFilterLabel([], 3)).toBe('Документы не выбраны');
|
||||||
|
expect(documentFilterLabel(['a'], 3)).toBe('1 документ');
|
||||||
|
expect(documentFilterLabel(['a', 'b'], 3)).toBe('2 документа');
|
||||||
|
expect(documentFilterLabel(['a', 'b', 'c', 'd', 'e'], 9)).toBe('5 документов');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('documentsQueryParam', () => {
|
||||||
|
const all = ['balance', 'ofr', 'osv_01', 'osv_60'];
|
||||||
|
|
||||||
|
it('отмечены все — без отбора', () => {
|
||||||
|
expect(documentsQueryParam(all, all)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ключи колонок → документы API через запятую', () => {
|
||||||
|
expect(documentsQueryParam(['balance', 'ofr', 'osv_01'], all)).toBe('BALANCE,INCOME,OSV:01');
|
||||||
|
expect(documentsQueryParam(['osv_60'], all)).toBe('OSV:60');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { Avatar, type TableColumnsType } from 'antd';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { type Preset, TAG_PRESETS } from '../../components/TableTag/presets';
|
import { type Preset, TAG_PRESETS } from '../../components/TableTag/presets';
|
||||||
import type { ChecklistBackendShape, FileStatus } from '../../types/consolidations/version_members';
|
import type { ChecklistBackendShape, FileStatus } from '../../types/consolidations/version_members';
|
||||||
|
import { pluralRu } from '../../utils/labels';
|
||||||
import { fileStatusMapping } from './consts';
|
import { fileStatusMapping } from './consts';
|
||||||
|
|
||||||
/** Строка таблицы чек-листа: ЮЛ и статусы ББ, ОФР и ОСВ по счетам. */
|
/** Строка таблицы чек-листа: ЮЛ и статусы ББ, ОФР и ОСВ по счетам. */
|
||||||
@ -93,7 +94,8 @@ export function buildChecklistRows(checklistData: ChecklistBackendShape): Checkl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildChecklistColumns(checklistData: ChecklistBackendShape): TableColumnsType<ChecklistTableRow> {
|
/** Счета ОСВ матрицы — в порядке первого появления в ячейках строк. */
|
||||||
|
function collectOsvCodes(checklistData: ChecklistBackendShape): string[] {
|
||||||
const osvCodes: string[] = [];
|
const osvCodes: string[] = [];
|
||||||
for (const row of checklistData.rows) {
|
for (const row of checklistData.rows) {
|
||||||
for (const cell of row.cells) {
|
for (const cell of row.cells) {
|
||||||
@ -102,7 +104,65 @@ export function buildChecklistColumns(checklistData: ChecklistBackendShape): Tab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return osvCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const osvKey = (code: string) => `osv_${code}`;
|
||||||
|
|
||||||
|
/** Пункты фильтра «Документы» — колонки матрицы: ББ, ОФР, ОСВ по счетам. Ключ пункта = ключ колонки. */
|
||||||
|
export function buildDocumentFilterOptions(checklistData: ChecklistBackendShape): { key: string; title: string }[] {
|
||||||
return [
|
return [
|
||||||
|
{ key: 'balance', title: 'Бух. баланс' },
|
||||||
|
{ key: 'ofr', title: 'ОФР' },
|
||||||
|
...collectOsvCodes(checklistData).map((code) => ({ key: osvKey(code), title: `ОСВ ${code}` })),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Клик по документу в фильтре «Документы»: отметить или снять. Порядок ключей — как в `allKeys`. */
|
||||||
|
export function toggleDocument(selected: string[], key: string, allKeys: string[]): string[] {
|
||||||
|
return allKeys.filter((k) => (k === key ? !selected.includes(k) : selected.includes(k)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Клик по «Все документы»: при всех отмеченных снимает все (чтобы оставить один документ), иначе отмечает все. */
|
||||||
|
export function toggleAllDocuments(selected: string[], allKeys: string[]): string[] {
|
||||||
|
return selected.length === allKeys.length ? [] : [...allKeys];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ключ колонки → документ в параметре `documents` выгрузки: `BALANCE`, `INCOME`, `OSV:<счёт>`. */
|
||||||
|
function toApiDocument(key: string): string {
|
||||||
|
if (key === 'balance') return 'BALANCE';
|
||||||
|
if (key === 'ofr') return 'INCOME';
|
||||||
|
return `OSV:${key.slice(osvKey('').length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Параметр `documents` выгрузки CSV по выбору фильтра: отмечены все — `undefined` (вся матрица, без отбора),
|
||||||
|
* иначе документы через запятую в порядке колонок: `BALANCE,INCOME,OSV:01`.
|
||||||
|
*/
|
||||||
|
export function documentsQueryParam(selected: string[], allKeys: string[]): string | undefined {
|
||||||
|
if (selected.length === allKeys.length) return undefined;
|
||||||
|
return selected.map(toApiDocument).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Подпись фильтра «Документы» в поле: «Все документы», «Документы не выбраны», «1 документ», «2 документа», «5 документов». */
|
||||||
|
export function documentFilterLabel(selected: string[], total: number): string {
|
||||||
|
if (selected.length === 0) return 'Документы не выбраны';
|
||||||
|
if (selected.length === total) return 'Все документы';
|
||||||
|
return `${selected.length} ${pluralRu(selected.length, ['документ', 'документа', 'документов'])}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Колонки таблицы чек-листа. `selectedDocuments` — ключи колонок, отмеченные в фильтре «Документы»
|
||||||
|
* (не передан — все колонки; пустой список — только «Организация»).
|
||||||
|
*/
|
||||||
|
export function buildChecklistColumns(
|
||||||
|
checklistData: ChecklistBackendShape,
|
||||||
|
selectedDocuments?: string[],
|
||||||
|
): TableColumnsType<ChecklistTableRow> {
|
||||||
|
const isShown = (key: string) => !selectedDocuments || selectedDocuments.includes(key);
|
||||||
|
const osvCodes = collectOsvCodes(checklistData).filter((code) => isShown(osvKey(code)));
|
||||||
|
|
||||||
|
const columns: TableColumnsType<ChecklistTableRow> = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
title: 'ОРГАНИЗАЦИЯ',
|
title: 'ОРГАНИЗАЦИЯ',
|
||||||
@ -112,16 +172,23 @@ export function buildChecklistColumns(checklistData: ChecklistBackendShape): Tab
|
|||||||
</h5>
|
</h5>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ key: 'balance', dataIndex: 'balance', title: 'БУХ. БАЛАНС', render: (v: FileStatus) => mapStatus(v) },
|
];
|
||||||
{ key: 'ofr', dataIndex: 'ofr', title: 'ОФР', render: (v: FileStatus) => mapStatus(v) },
|
if (isShown('balance')) {
|
||||||
{
|
columns.push({ key: 'balance', dataIndex: 'balance', title: 'БУХ. БАЛАНС', render: (v: FileStatus) => mapStatus(v) });
|
||||||
|
}
|
||||||
|
if (isShown('ofr')) {
|
||||||
|
columns.push({ key: 'ofr', dataIndex: 'ofr', title: 'ОФР', render: (v: FileStatus) => mapStatus(v) });
|
||||||
|
}
|
||||||
|
if (osvCodes.length > 0) {
|
||||||
|
columns.push({
|
||||||
key: 'osv',
|
key: 'osv',
|
||||||
title: 'ОСВ',
|
title: 'ОСВ',
|
||||||
children: osvCodes.map((code) => ({
|
children: osvCodes.map((code) => ({
|
||||||
key: `osv_${code}`,
|
key: osvKey(code),
|
||||||
title: code,
|
title: code,
|
||||||
render: (_: unknown, r: ChecklistTableRow) => mapStatus(r.osv[code] ?? 'MISSING'),
|
render: (_: unknown, r: ChecklistTableRow) => mapStatus(r.osv[code] ?? 'MISSING'),
|
||||||
})),
|
})),
|
||||||
},
|
});
|
||||||
];
|
}
|
||||||
|
return columns;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Select, Space } from 'antd';
|
import { Select, Space } from 'antd';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
import { Labeled } from '../../../components/Labeled';
|
||||||
|
|
||||||
interface ConsolidationChessFormatTableFilterI {
|
interface ConsolidationChessFormatTableFilterI {
|
||||||
decimals: number;
|
decimals: number;
|
||||||
@ -30,13 +31,11 @@ export const ConsolidationChessFormatTableFilter: React.FC<ConsolidationChessFor
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 16 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 16 }}>
|
||||||
<Space size='middle' wrap>
|
<Space size='middle' wrap>
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Единицы'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Единицы</span>
|
|
||||||
<Select style={{ minWidth: 120 }} options={UNIT_OPTIONS} value={units} onChange={onUnitsChange} variant='underlined' />
|
<Select style={{ minWidth: 120 }} options={UNIT_OPTIONS} value={units} onChange={onUnitsChange} variant='underlined' />
|
||||||
</Space>
|
</Labeled>
|
||||||
|
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Знаков после запятой'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Знаков после запятой</span>
|
|
||||||
<Select
|
<Select
|
||||||
style={{ minWidth: 150 }}
|
style={{ minWidth: 150 }}
|
||||||
value={decimals}
|
value={decimals}
|
||||||
@ -44,7 +43,7 @@ export const ConsolidationChessFormatTableFilter: React.FC<ConsolidationChessFor
|
|||||||
options={DECIMALS_OPTIONS}
|
options={DECIMALS_OPTIONS}
|
||||||
variant='underlined'
|
variant='underlined'
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Button, Select, Space } from 'antd';
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ApiError, api } from '../../../api/client';
|
import { ApiError, api } from '../../../api/client';
|
||||||
|
import { Labeled } from '../../../components/Labeled';
|
||||||
import { type OptionType, TreeFilterDropdown } from '../../../components/TreeFilterDropdown/TreeFilterDropdown';
|
import { type OptionType, TreeFilterDropdown } from '../../../components/TreeFilterDropdown/TreeFilterDropdown';
|
||||||
import type { Account, MatrixCompany, Quarter } from '../../../types/api';
|
import type { Account, MatrixCompany, Quarter } from '../../../types/api';
|
||||||
import { CHESS_HALF_FILTER_HINT, chessMatrixMode } from '../../../utils/chessMatrixMode';
|
import { CHESS_HALF_FILTER_HINT, chessMatrixMode } from '../../../utils/chessMatrixMode';
|
||||||
@ -115,8 +116,7 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space size='middle' wrap>
|
<Space size='middle' wrap>
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Год'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Год</span>
|
|
||||||
<Select
|
<Select
|
||||||
disabled
|
disabled
|
||||||
style={{ minWidth: 100 }}
|
style={{ minWidth: 100 }}
|
||||||
@ -124,9 +124,8 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
options={year ? [{ value: year, label: String(year) }] : []}
|
options={year ? [{ value: year, label: String(year) }] : []}
|
||||||
variant='underlined'
|
variant='underlined'
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Период'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Период</span>
|
|
||||||
<Select
|
<Select
|
||||||
disabled
|
disabled
|
||||||
style={{ minWidth: 100 }}
|
style={{ minWidth: 100 }}
|
||||||
@ -134,9 +133,8 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
options={quarter ? [{ value: quarter, label: quarterShortLabel(quarter) }] : []}
|
options={quarter ? [{ value: quarter, label: quarterShortLabel(quarter) }] : []}
|
||||||
variant='underlined'
|
variant='underlined'
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Компании'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Компании</span>
|
|
||||||
<Select
|
<Select
|
||||||
mode='multiple'
|
mode='multiple'
|
||||||
style={{ minWidth: 200 }}
|
style={{ minWidth: 200 }}
|
||||||
@ -162,10 +160,9 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
|
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Дт'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Дт</span>
|
|
||||||
<Select
|
<Select
|
||||||
mode='multiple'
|
mode='multiple'
|
||||||
style={{ minWidth: 150 }}
|
style={{ minWidth: 150 }}
|
||||||
@ -193,10 +190,9 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
)}
|
)}
|
||||||
loading={optionsLoading}
|
loading={optionsLoading}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
|
|
||||||
<Space orientation='vertical' size={0}>
|
<Labeled label='Кт'>
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>Кт</span>
|
|
||||||
<Select
|
<Select
|
||||||
mode='multiple'
|
mode='multiple'
|
||||||
style={{ minWidth: 150 }}
|
style={{ minWidth: 150 }}
|
||||||
@ -224,7 +220,7 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
)}
|
)}
|
||||||
loading={optionsLoading}
|
loading={optionsLoading}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Labeled>
|
||||||
{companies?.length || debit?.length || credit?.length ? <Button onClick={() => dropFiltersHandler()}>Сбросить фильтры</Button> : null}
|
{companies?.length || debit?.length || credit?.length ? <Button onClick={() => dropFiltersHandler()}>Сбросить фильтры</Button> : null}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
// Панель над таблицей: «Отображение», «Правило», «Тип записи», единицы и знаки, сброс фильтров, настройка, экспорт, весь экран.
|
// Панель над таблицей: «Отображение», «Правило», «Тип записи», единицы и знаки, сброс фильтров, настройка, экспорт, весь экран.
|
||||||
import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
||||||
import { Button, Select, Space } from 'antd';
|
import { Button, Select } from 'antd';
|
||||||
import { ColumnSettings } from '../../components/GenericTable/ColumnSettings';
|
import { ColumnSettings } from '../../components/GenericTable/ColumnSettings';
|
||||||
|
import { Labeled } from '../../components/Labeled';
|
||||||
import type { CorrectionRuleOut } from '../../types/corrections';
|
import type { CorrectionRuleOut } from '../../types/corrections';
|
||||||
import { PRECISIONS, UNITS, type Unit } from '../ConsolidatedOsv/useNumberDisplay';
|
import { PRECISIONS, UNITS, type Unit } from '../ConsolidatedOsv/useNumberDisplay';
|
||||||
import { COLUMN_DEFS, type ColKey, type ColShape } from './columns';
|
import { COLUMN_DEFS, type ColKey, type ColShape } from './columns';
|
||||||
@ -31,13 +32,6 @@ const RECORD_TYPES: { value: RecordType; label: string }[] = [
|
|||||||
{ value: 'unallocated', label: 'Нераспределенные записи' },
|
{ value: 'unallocated', label: 'Нераспределенные записи' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Labeled = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
|
||||||
<Space orientation='vertical' size={0}>
|
|
||||||
<span style={{ fontSize: 12, color: '#667085' }}>{label}</span>
|
|
||||||
{children}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
|
|
||||||
export function CorrectionToolbar(p: Props) {
|
export function CorrectionToolbar(p: Props) {
|
||||||
const { filters, rules } = p;
|
const { filters, rules } = p;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user