DFiP_Budget_planing/ARCHITECTURE.md
Andrey Epifancev 4a5e15e970 docs: ARCHITECTURE — фокусная сборка ответа на правку (AURORA-1850)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-29 00:19:04 +04:00

43 lines
6.5 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.

# Архитектура
Монорепозиторий: `api/` (FastAPI) отдаёт REST, WebSocket и собранный фронт; `web/` — SPA на React. Основная бизнес-логика — PL/pgSQL-функции схемы `v3` в Postgres. Python-слой проверяет роли, валидирует запрос, вызывает функцию и маппит её ошибки.
Поток редактирования ячейки: `RealtimeTable` → WebSocket `/api/v1/ws/form/{id}/sheet/{sheet}` (`cell_edit_start` — блокировка, `cell_updated`) → `SheetService` → `v3.upd_form_cell(s)`. Функция проверяет роль и окно этапа и пишет аудит `v3.log_event`, после чего изменение рассылается остальным подключённым. Ответ `upd_form_cells` (HTTP и `cell_updated`) — полные строки (`data` как в GET листа, с явными `null`): изменённые INPUT и все их предки-агрегаты, в том числе неизменившиеся; при `sections` ключи исключённых разделов убираются. Выборку делает `v3._form_affected_rows` (миграции 0050, 0051). Для FORM_1 и FORM_2 она не собирает лист целиком: построители (`v_form1_view`/`v_form2_view` → `*_sheet_jsonb` → `*_sheet_sections`) вызываются с фокусом `p_focus_lines` (NULL — весь лист, `'{}'` — ничего) и считают только изменённые строки и узлы их путей. `sort_order` в обоих режимах берётся из скелета листа (`_sort_rank`), поэтому совпадает с GET. FORM_4 до AURORA-1846 собирается полностью. Фоновая дельта АХР после правки сателлита (`get_form2_ahr_satellite_delta`) идёт тем же путём.
## Модули
| Модуль | Ответственность | Зависит от |
|---|---|---|
| `api/src/api/v1/` | Роутеры: auth, users, admin, audit, forms, projects, form_phases (`/stages`), phase_template, export, websocket, org_unit, vsp (`/dict/info`), expense_item, summary (`/svod`) | services, deps |
| `api/src/api/v1/deps.py` | Текущий пользователь из JWT, гарды ролей | repository.user_repository, core.security |
| `api/src/api/v1/websocket.py` | ConnectionManager, блокировки ячеек в памяти, события форм и FORM_4/проектов, синхронизация сателлитов AHR | services |
| `api/src/services/` | Сервисы по сущностям, контроль доступа (`access_control_service.py`) | repository |
| `api/src/services/export_service/` | Фоновые выгрузки в Excel: ProcessPool, таблица `export_job`, файлы в `tmp/exports`, маппинг колонок `export_mappings.py` | repository, openpyxl/XlsxWriter |
| `api/src/repository/` | SQLAlchemy-запросы и вызовы SQL-функций `v3.*`, `SET LOCAL app.user_id` | db |
| `api/src/db/` | engine, `Base`, сессия, ORM-модели (36) | core.config |
| `api/src/domain/` | pydantic-схемы API, enums | — |
| `api/src/core/` | Настройки, JWT/argon2, исключения и маппинг ошибок Postgres в HTTP | — |
| `api/alembic/` | Миграции (около 49 ревизий); `versions/sql/*.sql` — DDL, функции, сиды, около 43 тыс. строк | Postgres |
| `web/src/app/` | Роуты, AuthProvider (токены в localStorage) | pages |
| `web/src/pages/` | Задачи (формы), карточка формы или проекта, проекты и их своды, свод 1 и 2, админка, справочники | components, api |
| `web/src/components/RealtimeTable/` | Редактируемая таблица листа: WebSocket, блокировки, конфиги колонок `constants/FORM_*` | api |
| `web/src/api/` | axios-клиент `/api/v1`, модули по сущностям, WebSocket выгрузок | — |
## Границы
- Роутеры не ходят в БД напрямую, только через сервисы и репозитории. Сейчас нарушают `admin.py`, `main.py` (engine) и `websocket.py` (бизнес-логика в слое API).
- Проверка прав на ячейки и окна этапов — в SQL. Python её не дублирует, только маппит ошибки.
- Изменения схемы и функций — только новой миграцией alembic; старые миграции не правятся.
- Фронт обращается только к `/api/v1` через `web/src/api/`.
## Открытые вопросы
- Prod-авторизация через Keycloak (`raisa_fastapi_protected_api`) закомментирована в `main.py`, `deps.py`, `auth.py`; сейчас всегда работает локальный JWT. Как будет устроен prod?
- `websocket.py:333`, `:357`: условие `form_key != form_key` всегда ложно, поэтому блокировки сравниваются по `line_id` между всеми формами; `release_locks_for_row` ничего не возвращает.
- Раскладка колонок дублируется: `web/.../constants/FORM_1|2|4/*.js` (около 4 тыс. строк каждый) и `api/.../export_mappings.py`. Источник правды не определён.
- Названия форм в БД и UI расходятся (FORM_2, FORM_4).
- Мёртвый код фронта: `pages/TablePage.jsx`, `FormsPage.jsx`, `TablesTest.jsx`, `TableTempPage/`, `api/tables.js`, `api/client_form.js`, `api/ws.js`, `constants/FORM_1/*_NO_LOCAL.js`.
- В `package.json` не объявлены `@mui/x-date-pickers`, `@mui/icons-material`; tailwind/daisyui не используются.
- `Settings.CORS_ORIGINS` не применяется (в `main.py` `allow_origins=["*"]`), `APP_ENV` не читается, `JWT_SECRET` по умолчанию небезопасный.
- `/` монтируется дважды в `api/src/main.py:289` и `:291`.
- Интеграционные тесты зависят от данных в общей БД и не изолированы; `downgrade()` в миграциях пустые.
- Python-линтеров и проверок CI в репозитории нет.