# 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** чтение формул листа не возвращает формул этой строки