Обе задачи влиты в test 01.11 (PR #239/#242, #244/#274), архив пропущен. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
14 KiB
Markdown
114 lines
14 KiB
Markdown
# 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 % от числа отправленных правок
|