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

6.5 KiB
Raw Blame History

Архитектура

Монорепозиторий: 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 в репозитории нет.