Andrey Epifancev b1c61ae769 openspec: архив aurora-1883-closed-vsp и aurora-1896-cell-formulas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 18:49:51 +04:00

80 lines
10 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.

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