80 lines
10 KiB
Markdown
80 lines
10 KiB
Markdown
# cell-formula Specification
|
||
|
||
## Purpose
|
||
Формулы ячеек листов бюджетных форм (FORM_1, FORM_2, FORM_4) и отчётов проектов (FORM_3): как формула сохраняется вместе со значением, кто и как её видит, когда она перестаёт показываться и когда удаляется.
|
||
|
||
Термины: **ячейка** — (владелец, лист, `line_id`, `column`), где владелец — бюджетная форма (`budget_form.id`) или отчёт проекта (`rf_project_report.id`); `line_id` и `column` — те же, что в `cell_updated` и `PATCH …/cell`. **Формула** — выражение без ведущего «=», например `100+50`.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Формула сохраняется вместе со значением ячейки
|
||
Запись ячейки с полем `formula` (WS `cell_updated` — `data.formula`; REST `PATCH /form/{id}/sheet/{sheet}/cell`, `…/cells`, `PATCH /projects/{id}/report/{year}/{type}/cell`, `…/cells` — поле `formula` элемента) SHALL сохранять формулу этой ячейки в той же транзакции, что и значение: выражение, значение (`value` записи), автора (текущий пользователь) и время записи. Предыдущая формула ячейки SHALL заменяться. Формула SHALL сохраняться только если запись значения прошла (те же проверки прав и принадлежности строки, что у значения); при отказе в записи значения формула SHALL NOT сохраняться.
|
||
|
||
#### Scenario: Сохранение формулы
|
||
- **WHEN** администратор пишет в ячейку FORM_2/AHR `plan.q1` значение 150 с формулой `100+50`
|
||
- **THEN** чтение формул листа возвращает для этой ячейки выражение `100+50`, значение 150, автора — этого администратора и время записи
|
||
|
||
#### Scenario: Отказ в праве на ячейку
|
||
- **WHEN** исполнитель без права на колонку (окно закрыто или чужая оргединица) пишет значение с формулой
|
||
- **THEN** запрос отклоняется так же, как без формулы (403)
|
||
- **AND** формула ячейки не появляется и прежняя формула не меняется
|
||
|
||
### Requirement: Ввод без формулы удаляет формулу ячейки
|
||
Запись значения ячейки без поля `formula` (или с `formula: null`) через любой из путей записи выше SHALL удалять формулу этой ячейки, если она была. Это касается и массовой записи `…/cells`: для каждого элемента без формулы формула его ячейки удаляется.
|
||
|
||
#### Scenario: Перезапись числом
|
||
- **WHEN** у ячейки есть формула `100+50`, и пользователь записывает в неё значение 200 без формулы
|
||
- **THEN** чтение формул листа не возвращает формулы этой ячейки
|
||
|
||
### Requirement: Проверка выражения
|
||
Сервер SHALL принимать выражение длиной от 1 до 500 символов, состоящее только из цифр, пробелов и символов `+ - * / % ( ) , .`; ведущий «=» и пробелы по краям SHALL отбрасываться при нормализации. Сервер SHALL NOT вычислять выражение. Формула с непустым выражением SHALL требовать числовое `value`. При нарушении сервер SHALL отвечать 422 с кодом ошибки по префиксу `invalid_formula:`, и значение ячейки в этом запросе SHALL NOT записываться.
|
||
|
||
#### Scenario: Недопустимое выражение
|
||
- **WHEN** пользователь пишет значение 150 с формулой `drop table`
|
||
- **THEN** ответ — 422 `invalid_formula`
|
||
- **AND** значение ячейки осталось прежним, формула не сохранилась
|
||
|
||
#### Scenario: Нормализация
|
||
- **WHEN** пользователь пишет формулу ` =100+50 `
|
||
- **THEN** сохраняется выражение `100+50`
|
||
|
||
### Requirement: Чтение формул листа
|
||
Сервер SHALL отдавать все формулы листа: `GET /api/v1/form/{form_id}/sheet/{sheet}/formulas` и `GET /api/v1/projects/{project_id}/report/{year}/{report_type}/formulas`. Каждая запись SHALL содержать `line_id`, `column`, `expression`, `value`, `updated_by` (`id`, `full_name`) и `updated_at`. Доступ SHALL проверяться так же, как у GET листа (форма) или отчёта (проект): нет доступа к форме или формы нет — 404 «Форма не найдена»; отчёт проекта не найден или нет доступа — та же ошибка, что у PATCH этого отчёта (400 «Отчёт не найден»).
|
||
|
||
#### Scenario: Формулы видны другому пользователю
|
||
- **WHEN** пользователь А сохранил формулу, а пользователь Б с доступом к форме запрашивает формулы листа
|
||
- **THEN** Б получает формулу А с её автором и временем
|
||
|
||
### Requirement: Рассылка формулы по WebSocket
|
||
Рассылка `cell_updated` всем подключённым к листу SHALL содержать в `data` поле `formula` — нормализованное сохранённое выражение или `null`, если формулы у ячейки после записи нет, — и при наличии формулы поле `formula_meta` с `updated_by` (`id`, `full_name`) и `updated_at`. Значение `data.formula` в рассылке SHALL задавать сервер, а не пересылать входящее значение клиента.
|
||
|
||
#### Scenario: Второй пользователь видит формулу без перезагрузки
|
||
- **WHEN** пользователь А вводит в ячейку `=100+50`
|
||
- **THEN** пользователь Б, открывший тот же лист, получает `cell_updated` с `data.formula = "100+50"` и автором А
|
||
- **AND** в ячейке у Б отображается 150 с маркером формулы
|
||
|
||
#### Scenario: Перезапись снимает маркер у всех
|
||
- **WHEN** пользователь Б записывает в ту же ячейку число 200
|
||
- **THEN** все подключённые клиенты получают `cell_updated` с `data.formula = null`, и маркер формулы пропадает
|
||
|
||
### Requirement: Отображение формулы
|
||
Фронт SHALL показывать у ячейки маркер формулы `fx` и подсказку с выражением, ФИО автора и датой-временем, только если для ячейки есть формула и её `value` совпадает с текущим значением ячейки с точностью 1e-9. Редактор ячейки SHALL открываться с текстом `=` + выражение при том же условии совпадения. Если значения не совпадают (значение изменилось не через ввод, например синхронизацией), маркер SHALL NOT показываться, и редактор открывается со значением.
|
||
|
||
#### Scenario: Формула устарела после пересчёта
|
||
- **WHEN** у ячейки есть формула со значением 150, а текущее значение ячейки стало 170
|
||
- **THEN** маркер формулы не показывается, редактор открывается со значением 170
|
||
|
||
### Requirement: Формулы не хранятся в браузере
|
||
Фронт SHALL NOT читать и писать формулы в localStorage. При первом запуске новой версии ключи `formula_*` SHALL удаляться из localStorage.
|
||
|
||
#### Scenario: Формула с другого компьютера
|
||
- **WHEN** автор сохранил формулу и открыл лист на другом компьютере
|
||
- **THEN** он видит маркер формулы и открывает редактор с `=выражением`
|
||
|
||
### Requirement: Формулы удаляются вместе с владельцем и строкой
|
||
Формулы SHALL удаляться при удалении строки, к которой относится ячейка (строка бюджета, запись сателлита аренды/коммунальных/охраны, запись отчёта ОС OTCH9F, строка отчёта проекта), при окончательном удалении бюджетной формы и при удалении отчёта проекта — независимо от того, какая функция удаляет строку.
|
||
|
||
#### Scenario: Удаление строки
|
||
- **WHEN** у строки листа есть формулы, и строку удаляют
|
||
- **THEN** чтение формул листа не возвращает формул этой строки
|