Andrey Epifancev ac686991a8 openspec: архив aurora-1848-sheet-json и aurora-1850-upd-form-cells-subtree
Обе задачи влиты в test 01.11 (PR #239/#242, #244/#274), архив пропущен.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 01:29:46 +04:00

114 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# sheet-cell-edit Specification
## Purpose
Ответ сервера на правку ячеек листа бюджетной формы (HTTP `PATCH /form/{id}/sheet/{sheet}/cell(s)` и WebSocket `cell_updated`): какие строки в нём приходят, какими они должны быть относительно полного GET листа и сколько времени правка может стоить на сервере.
## Requirements
### Requirement: Ответ на правку содержит затронутые строки целиком
Ответ на правку ячеек листов AHR/CAP/OPER форм FORM_1, FORM_2 и FORM_4 SHALL содержать каждую изменённую строку INPUT и все её строки-агрегаты, то есть всех предков по дереву статей до ROOT включительно. Для FORM_4 предками считаются только ROOT раздела, GROUP программы и ITEM проекта изменённой строки. Агрегат SHALL входить в ответ, даже если его значения не изменились. Ответ SHALL NOT содержать строку META и строки других ветвей дерева. Поле `data` каждой строки SHALL быть полной строкой в том же виде, что и в GET листа с теми же `direction` и `sections`: пустые значения передаются явным `null`. Если задан `sections`, ключи верхнего уровня у исключённых разделов SHALL отсутствовать в `data`.
#### Scenario: Правка числовой ячейки возвращает строку и её предков
- **WHEN** исполнитель меняет `plan.q1` у строки листа FORM_2/AHR
- **THEN** ответ содержит эту строку INPUT и по одной строке-агрегату на каждый уровень её пути в дереве статей
- **AND** `data` каждой строки ответа равно `data` строки того же `sort_order` из последующего GET листа
#### Scenario: Правка нечисловой ячейки всё равно возвращает предков
- **WHEN** исполнитель меняет `header.justification` у строки FORM_2/AHR
- **THEN** ответ содержит строку INPUT и всех её предков, хотя их суммы не изменились
#### Scenario: Очистка значения
- **WHEN** исполнитель записывает `null` в `plan.q1`
- **THEN** в строке INPUT ответа ключ `plan.q1` присутствует со значением `null` (или 0 — так же, как в GET)
- **AND** суммы предков в ответе уменьшены на прежнее значение ячейки
#### Scenario: Правка с фильтром разделов
- **WHEN** правка выполняется с `sections=plan`
- **THEN** ни одна строка ответа не содержит ключей разделов, отличных от `plan`, а также служебных ключей, которых нет в GET с тем же `sections`
### Requirement: sort_order в ответе совпадает с полным листом
Значения `row_type`, `depth` и `sort_order` каждой строки ответа на правку SHALL совпадать со значениями этой же строки в полном GET листа с теми же параметрами, выполненном сразу после правки. `sort_order` — сквозной номер строки во всём листе, а не номер внутри ответа. Это SHALL выполняться для первой и последней строки листа, после добавления и удаления строк в листе, а также при совпадении ключа сортировки у строки INPUT и узла дерева. Порядок строк в GET при совпадении ключа SHALL быть детерминированным и одинаковым в GET и в ответе на правку.
#### Scenario: Первая и последняя строка листа
- **WHEN** правится первая по порядку строка INPUT листа, а затем последняя
- **THEN** у каждой строки обоих ответов есть строка с тем же `(row_type, depth, sort_order)` в GET листа, и её `data` совпадает
#### Scenario: После добавления и удаления строк
- **WHEN** в лист добавлены строки (POST line), одна из строк в середине листа удалена (DELETE line), после чего правится строка, расположенная ниже удалённой
- **THEN** `sort_order` каждой строки ответа равен её `sort_order` в GET листа после этих операций
#### Scenario: Пакет правок по разным ветвям дерева
- **WHEN** через `PATCH /cells` одним запросом правятся строки двух разных ветвей дерева статей
- **THEN** ответ содержит обе строки INPUT и объединение их предков без дублей, и каждая строка совпадает со строкой GET того же `sort_order`
#### Scenario: Направление FORM_1
- **WHEN** правится строка FORM_1/AHR с `direction=Support`
- **THEN** `sort_order` строк ответа равен `sort_order` в GET FORM_1/AHR с `direction=Support`
### Requirement: Состав ответа на листах сателлитов, AHR_LIMIT и OTCH9F
На листах FORM_2 AHR_RENT, AHR_UTILITY и AHR_SECURITY ответ на правку SHALL содержать только изменённые строки. На AHR_LIMIT ответ SHALL содержать изменённые строки LEAF шаблона и их предков по шаблону (GROUP и SECTION). На OTCH9F ответ SHALL содержать все строки (все месяцы) того же сочетания статьи и оборудования, что и изменённая строка: остатки на начало месяца зависят от предыдущих месяцев. Для всех этих листов SHALL выполняться требование о совпадении `data` и `sort_order` с полным GET.
#### Scenario: Правка строки аренды
- **WHEN** исполнитель меняет `plan.q1` у строки AHR_RENT
- **THEN** ответ содержит ровно одну строку с `data.id` этой строки, и она совпадает со строкой GET AHR_RENT того же `sort_order`
#### Scenario: Правка количества в лимитах
- **WHEN** исполнитель меняет `qty_q1` у строки LEAF листа AHR_LIMIT
- **THEN** ответ содержит эту строку и строки её группы и раздела с пересчитанными `amount_q1`, и они совпадают с GET AHR_LIMIT
#### Scenario: Правка месяца в OTCH9F
- **WHEN** исполнитель меняет поступление за месяц у строки OTCH9F
- **THEN** ответ содержит строки всех месяцев того же оборудования той же статьи с пересчитанными остатками, и они совпадают с GET OTCH9F
### Requirement: Обновление АХР после правки сателлита
После правки или добавления строки на AHR_RENT, AHR_UTILITY, AHR_SECURITY или AHR_LIMIT формы FORM_2 сервер SHALL отправить подключённым к листу AHR событие (`cell_updated` или `row_added`). Событие SHALL содержать синхронизированные строки AHR и всех их предков, включая уровень SUB_SUB_ITEM. Каждая из этих строк SHALL совпадать со строкой того же `sort_order` полного GET листа AHR. Если синхронизированных строк нет, событие SHALL NOT содержать строк AHR, то есть ответ пуст, а не весь лист. Строки AHR_LIMIT SHALL сопоставляться и со статьями AHR глубины 4 по `num_group_id` (поведение миграции 0037).
#### Scenario: Правка аренды обновляет АХР
- **WHEN** исполнитель меняет `plan.q1` у строки AHR_RENT, связанной со строкой AHR
- **THEN** строки, которые сервер готовит для события на лист AHR, совпадают со строками GET AHR того же `sort_order`, и среди них есть синхронизированная строка INPUT с новой суммой
#### Scenario: Источник без синхронизированных строк
- **WHEN** запрашивается обновление АХР для строки сателлита, у которой нет связанной строки AHR
- **THEN** набор строк для события пуст
#### Scenario: Лимит на статье глубины 4
- **WHEN** правится строка AHR_LIMIT, код статьи которой совпадает с `num_group_id` статьи AHR глубины 4
- **THEN** набор строк для события содержит синхронизированную строку AHR этой статьи
### Requirement: Пакет правок атомарен
Если хотя бы одна правка в пакете отклонена (неизвестная колонка, чужая строка, нет прав, окно закрыто), SHALL NOT применяться ни одна правка пакета, и ответ SHALL быть ошибкой.
#### Scenario: Ошибочная колонка в пакете
- **WHEN** `PATCH /cells` содержит одну корректную правку и одну с несуществующей колонкой
- **THEN** ответ — ошибка 4xx, и GET листа после запроса совпадает с GET до него
### Requirement: GET листа не меняется
Содержимое GET листов FORM_1 и FORM_2 (строки, `data`, `sort_order`) после изменения SHALL совпадать с содержимым до него на тех же данных. Исключение — порядок строк с совпадающим ключом сортировки: до изменения он не был определён. Время GET FORM_2/AHR SHALL NOT вырасти больше чем на 10 %.
#### Scenario: Снимок GET до и после миграции
- **WHEN** на стенде профилирования снят GET всех листов FORM_1 и FORM_2 из манифеста до применения миграции и после неё
- **THEN** ответы попарно совпадают
#### Scenario: Время GET после смешанной нагрузки
- **WHEN** в одном соединении с БД чередуются правки ячеек и GET листа FORM_2/AHR
- **THEN** среднее время GET не больше базового + 10 %
### Requirement: Время правки ячейки на FORM_1 и FORM_2
Правка одной ячейки листа FORM_1 или FORM_2 SHALL NOT собирать лист целиком: на правку SHALL приходиться 0 вложенных полных сборок листа. Среднее серверное время `v3.upd_form_cell` на FORM_2/AHR SHALL быть не больше 50 мс как на листе в 1154 строки, так и на листе в 2092 строки. Требование не распространяется на FORM_4 до слияния AURORA-1846.
#### Scenario: Соавтор на листе масштаба 1
- **WHEN** на стенде профилирования (масштаб 1, без grow, FORM_2/AHR 1154 строки) выполняется шаг `coauthor`
- **THEN** в `sql/coauthor/sql_top.json` `mean_exec_time` вызова `v3.upd_form_cell` ≤ 50 мс
- **AND** вызовов `v3.v_form_view` не верхнего уровня (`toplevel = false`) нет, а число вызовов `v3.v_form2_view(p_form_id, p_sheet, p_sections)` равно числу GET листа и не зависит от числа правок
#### Scenario: Соавтор на выросшем листе
- **WHEN** после `perfkit.gen.grow` (FORM_2/AHR 2092 строки) шаг `coauthor` выполняется повторно
- **THEN** `mean_exec_time` вызова `v3.upd_form_cell` ≤ 50 мс
### Requirement: Ручной ввод без очереди
Сервер SHALL успевать подтверждать правки одного соединения с частотой ручного ввода (5 правок/с), не накапливая очередь.
#### Scenario: Соавтор с частотой 5 правок в секунду
- **WHEN** бот соавтора на FORM_2/AHR отправляет 5 правок/с в течение шага `coauthor`
- **THEN** число полученных ответов (эхо и ошибки) составляет не меньше 95 % от числа отправленных правок