diff --git a/openspec/specs/document-review-ui/spec.md b/openspec/specs/document-review-ui/spec.md index 6c4a45e..43dde86 100644 --- a/openspec/specs/document-review-ui/spec.md +++ b/openspec/specs/document-review-ui/spec.md @@ -1,7 +1,7 @@ # document-review-ui Specification ## Purpose -Аналитик проверяет распознанный документ и подтверждает его: форма ББ/ОФР рядом со сканом PDF (правка значений и шапки, проверки и их акцепт) [41]/[41-02], ОСВ-документ — плоской таблицей строк только для чтения. +Аналитик проверяет распознанный документ и подтверждает его: форма ББ/ОФР рядом со сканом PDF (правка значений и шапки, проверки и их акцепт) [41]/[41-02], ОСВ-документ — таблицей строк в порядке файла (как в 1С) только для чтения. ## Requirements @@ -81,12 +81,19 @@ column_id, amount}`; ответ заменяет состояние экрана - **THEN** экран показывает ошибку со ссылкой на уже подтверждённую форму ### Requirement: ОСВ-документ -Для `OSV` экран SHALL показывать строки `GET /documents/{id}/entries` (все страницы, `limit` ≤ 2000) плоской -таблицей строк нижнего уровня (договоры отдельными строками не идут — их сумма в строке контрагента) с колонками: -субсчёт, подразделение (если в файле есть этот уровень), аналитика, ИНН (если есть контрагенты), сальдо на начало Дт/Кт, -обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с разделителями разрядов, рубли), только чтение. Кнопка «Подтвердить» — как для форм. -Заголовок колонки аналитики SHALL зависеть от вида строк-листьев (структурные строки субсчёта и подразделения не -учитываются, договоры строками не идут): только `COUNTERPARTY` — «Контрагент»; один другой вид — его `group_name` +Для `OSV` экран SHALL показывать строки `GET /documents/{id}/entries` (все страницы, `limit` ≤ 2000) таблицей в порядке +файла, как в 1С (AURORA-1702): строки счёта, субсчетов и подразделений, у которых есть вложенные строки, — строки-группы +(жирным, со своими сальдо и оборотами), вложенные строки — с отступом по уровню в колонке аналитики. Договоры отдельными +строками не идут — их сумма в строке контрагента; субсчёт или подразделение без вложенных строк и без сумм не выводится. +Строки-показатели колонки «Показатели» и строки «Кол.», «Вал.» (§1.3 правил предметной области: `group_type = OTHER`, +`data_type` не BU/SUM и не равен виду родителя — NU, QUANTITY, CURRENCY или пусто у «ПР»/«ВР») в рублёвую таблицу +SHALL NOT попадать и родителя группой не делают: строка БУ остаётся строкой с наименованием и суммами в рублях +(AURORA-1703). Колонки: субсчёт, подразделение (если в файле есть этот уровень), аналитика (у строки счёта — её текст), +ИНН (если есть контрагенты), сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с +разделителями разрядов, рубли), только чтение. Фильтр по счёту оставляет счёт и все его субсчета («62» — 62, 62.01, +62.02), при поиске по аналитике или ИНН строки-группы скрываются. Кнопка «Подтвердить» — как для форм. +Заголовок колонки аналитики SHALL зависеть от вида строк-листьев (структурные строки субсчёта и подразделения, +строки-группы и строки-показатели не учитываются, договоры строками не идут): только `COUNTERPARTY` — «Контрагент»; один другой вид — его `group_name` («Основные средства»); смесь видов или вид без `group_name` — «Аналитика», а перечень видов — во всплывающей подсказке заголовка. Колонка «ИНН» и подсказка поиска «Контрагент или ИНН» SHALL показываться только при наличии контрагентов (`COUNTERPARTY`); иначе колонки ИНН нет, а поиск подписан «Поиск по аналитике». @@ -95,13 +102,21 @@ column_id, amount}`; ответ заменяет состояние экрана - **WHEN** листья документа — аналитика с `group_name` «Основные средства», контрагентов нет - **THEN** колонка называется «Основные средства», колонки ИНН нет, поиск подписан «Поиск по аналитике» +#### Scenario: Строки счёта и субсчетов +- **WHEN** аналитик открывает ОСВ по счёту 62 с субсчетами 62.01 и 62.02 и контрагентами под ними +- **THEN** в таблице жирным видны строки 62, 62.01, 62.02 с их сальдо и оборотами, контрагенты — под своим субсчётом с отступом + +#### Scenario: ОСВ с колонкой «Показатели» +- **WHEN** у позиции номенклатуры счёта 41 в файле строки «БУ» и «Кол.» +- **THEN** в таблице строка позиции с наименованием и суммами в рублях, строки «Кол.» нет + #### Scenario: Подтверждение ОСВ - **WHEN** аналитик подтверждает ОСВ-документ `NEEDS_CHECK` - **THEN** документ становится «Загружен», версия, к которой он подходит, переходит в `DATA_CORRECTION` ### Requirement: Виртуализация таблицы ОСВ-документа Таблица строк ОСВ-документа SHALL быть виртуализирована: в DOM находятся только видимые строки, остальные не -отрисовываются. Строки по-прежнему загружаются целиком, поиск по аналитике (и ИНН) и фильтр по субсчёту — клиентские; +отрисовываются. Строки по-прежнему загружаются целиком, поиск по аналитике (и ИНН) и фильтр по счёту — клиентские; серверной пагинации нет. Высота области прокрутки считается числом по высоте окна и пересчитывается при его изменении. Строка «Итого по документу» закреплена внизу и выровнена по колонкам, двухъярусная шапка и колонка «Субсчёт» закреплены, горизонтальная прокрутка сохраняется. ОСВ любого размера (десятки тысяч строк) SHALL открываться без diff --git a/src/types/api.ts b/src/types/api.ts index 52e0e9a..c6e0850 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -127,6 +127,8 @@ export interface IssueCountsOut { } /** Уровень строки ОСВ (DBML `osv_group_type`). */ export type OsvGroupType = 'ACCOUNT' | 'DEPARTMENT' | 'COUNTERPARTY' | 'CONTRACT' | 'OTHER'; +/** Вид сумм строки ОСВ (`osv_data_type`): BU, SUM — рубли; NU, CURRENCY, QUANTITY — строки-показатели под строкой БУ. */ +export type OsvDataType = 'BU' | 'NU' | 'CURRENCY' | 'QUANTITY' | 'SUM'; export interface EntryOut { id: string; @@ -136,6 +138,7 @@ export interface EntryOut { row_value: string | null; group_type: OsvGroupType | null; group_name: string | null; + data_type: OsvDataType | null; account_code: string | null; counterparty_name: string | null; counterparty_inn: string | null; diff --git a/src/widgets/DocumentReview/OsvDocument.tsx b/src/widgets/DocumentReview/OsvDocument.tsx index e162784..2e0661c 100644 --- a/src/widgets/DocumentReview/OsvDocument.tsx +++ b/src/widgets/DocumentReview/OsvDocument.tsx @@ -10,11 +10,15 @@ import { IssuesPanel } from './IssuesPanel'; import { confirmButtonStyle } from './ReviewCard'; import { expectedActualText, failureKey } from './checkText'; import { confirmErrorInfo } from './confirmError'; -import { AMOUNT_KEYS, type AmountKey, type OsvRow, flattenEntries } from './osvRows'; +import { AMOUNT_KEYS, type AmountKey, type OsvRow, filterRows, flattenEntries } from './osvRows'; import { osvBodyHeight } from './osvTableSize'; /** Ширина колонки суммы: «17 780 468 796,56» помещается в одну строку, «Итого по документу» не переносится и не обрезается. */ const MONEY_WIDTH = 160; +/** Отступ одного уровня вложенности в колонке аналитики, px. */ +const INDENT = 16; +/** Строки-группы (счёт, субсчёт, подразделение с вложенными строками) — жирным, как промежуточные итоги в 1С. */ +const groupRowStyle = { fontWeight: 600 }; const money = (key: AmountKey, title: string) => ({ key, @@ -48,7 +52,8 @@ function buildColumns(options: ColumnsOptions): ColumnsType { title: analyticsHint ? {analyticsTitle} : analyticsTitle, dataIndex: 'analytics', width: 320, - render: (v: string | null) => v ?? '—', + // отступ по уровню вложенности — дерево строк как в 1С + render: (v: string | null, row: OsvRow) => {v ?? '—'}, }, ...(hasCounterparties ? [{ key: 'inn', title: 'ИНН', dataIndex: 'inn', width: 120 }] : []), group('Сальдо на начало периода', [money('opening_debit', 'Дебет'), money('opening_credit', 'Кредит')]), @@ -57,15 +62,6 @@ function buildColumns(options: ColumnsOptions): ColumnsType { ]; } -/** Поиск по аналитике (и по ИНН, если он есть) без учёта регистра и фильтр по субсчёту — на клиенте: строки документа уже загружены целиком. */ -function filterRows(rows: OsvRow[], query: string, account: string | undefined): OsvRow[] { - const q = query.trim().toLowerCase(); - return rows.filter( - (row) => - (!account || row.account === account) && (!q || (row.analytics ?? '').toLowerCase().includes(q) || (row.inn ?? '').includes(q)), - ); -} - interface ConfirmProblem { code: string | undefined; message: string; @@ -164,7 +160,7 @@ export function OsvDocument({ document, onChanged }: { document: DocumentOut; on />