AURORA-1888: исполнитель видит только свои этапы, 404 вместо 500 на чужую форму/проект #261
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/250-aurora-1888-stages-role-filter"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #250.
Проблема
GET /stages/form/{form_id} и GET /stages/project/{project_id} возвращали этапы всех ролей (DFIP, EXECUTOR_RF, DURRS) любому авторизованному пользователю, без фильтрации по роли. Доступ к форме/проекту проверялся только после загрузки этапов и только если они нашлись — для чужой формы/проекта (или формы без этапов)
bf_service.get/project_service.get_instanceвозвращалиNone, обращение к.org_unit_idпадалоAttributeError→ 500 вместо 404.На фронте подпись роли в таблице этапов показывалась всем, хотя у исполнителя теперь всегда будет своя единственная роль.
Что сделано
api/src/services/form_phase_service.py— добавлена функцияrole_code_for_user(user)(и словарьROLE_CODE_BY_USER_ROLE): единственное место сопоставления роли пользователя (UserRoleEnum.EXECUTOR_DFIP/EXECUTOR_RF/EXECUTOR_DURRS) коду роли этапа (v3.role.code:DFIP/EXECUTOR_RF/DURRS). Для ADMIN возвращаетNone— без фильтра.FormPhaseService.get_listпередаётrole=role_code_for_user(user)в репозиторий.api/src/services/form3_phase_service.py—Form3PhaseService.get_listполучил параметрuser, импортирует и использует ту жеrole_code_for_user.api/src/repository/form_phase_repository.py,api/src/repository/form3_phase_repository.py—get_listполучил необязательный параметрrole; если задан —.where(<Model>.role == role).api/src/api/v1/form_phases.py:get_form_phases— сначалаbf_service.get(user=current_user, budget_form_id=form_id), приNone→HTTPException(404, "Форма не найдена"), только потом загрузка этапов (с ролевым фильтром). Убрана веткаif phases: ... else: result = [];count = len(result).get_form3_phases— аналогично: сначалаproject_service.get_instance(user=current_user, project_id=project_id), приNone→ 404, потом отчёты и этапы с ролевым фильтром. Сопоставлениеphases[i] → reportsпоrf_project_report_idсохранено (фильтр стоит в самом SQL-запросе).web/src/components/Stages/StagesTable/StagesTable.jsx— подпись роли этапа (STAGE_ROLE_RUSSIAN_NAME) показывается только админу (isAdmin &&). Кнопка «Этапы» не тронута.Тесты
api/tests/unit/test_form_phase_service.py(новый) —FormPhaseService.get_listс моком репозитория:EXECUTOR_DFIP→ репозиторий вызван сrole='DFIP';ADMIN→role=None.api/tests/integration/test_form_phases_api_smoke.py(дополнен):test_form_phases_role_filter_for_executor— под isp1 (role_id=2=DFIP) на своей форме (budget_form 4) все этапы сrole == 'DFIP'; в той же транзакции админ добавляет этапEXECUTOR_RFна этой же форме — isp1 его не видит (список не меняется по составу ролей), админ видит оба этапа (список не урезан).test_form_phases_foreign_org_404— isp1 →GET /stages/form/1(форма чужого org_unit) → 404 (было 500).test_form3_phases_foreign_org_404— isp1 →GET /stages/project/2(проект чужого org_unit) → 404 (было 500).Прогон (venv
/Users/andrey.epifancev/projects/fomichev_claude/.venvs/dfip-api, Postgres 16perf-pg:55432, БДdfip_1888,pytest --init_dbодин раз для накатки миграций+сида, затем обычный прогон):Все 3 падения — подтверждённо пред-существующие на
testи не связаны с этой веткой (проверено прогоном тех же тестов на коде до правки,git checkout <tip test> -- api/src api/tests, тот же результат):test_db_functions_integration.py::test_backend_calls_form3_add_project_and_add_line_functions,test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets,test_export_service_query_params.py::test_export_form_payload_forwards_ignore_flag. Все тесты, затронутые этой веткой (form_phases/form3_phases), зелёные.Rulings (решения без обсуждения)
role_code_for_user) разместил вform_phase_service.pyи импортировал вform3_phase_service.py— тикет предлагал это как один из двух равноценных вариантов («либо функция в form_phase_service, либо читать Role.code по user.role_id»). Выбрал хардкод-словарь вместо похода в БД заRole.code:AppUserне хранит загруженной связи наRole(толькоrole_id), а коды ролей зафиксированы в сидах миграций (0003_initial_data.sql,0025_new_role.py) и уже захардкожены в этом же файле какLiteral["DFIP", "EXECUTOR_RF", "ADMIN", "DURRS"](FormPhaseRoleвdomain/schemas.py) — дополнительный асинхронный запрос не добавляет корректности, но добавляет round-trip.get_form3_phases, по аналогии сget_form_phases(п.4 тикета), убрал веткуif phases: ... else: result = []целиком, а не только переставил проверку проекта наверх — тикет явно просил это только дляget_form_phases(п.4), дляget_form3_phases(п.5) формулировка менее явная («сначала project_service.get_instance..., потом загрузить отчёты и этапы»). Применил ту же упрощённую структуру для единообразия: раз проект проверен в начале и гарантированно неNone, отдельная ветка не нужна.get_form3_phases— тикет не задавал точную формулировку. Использовал"Проект не найден"(по аналогии с остальными русскоязычными 404 в этом файле), а не"Project not found", как вprojects.py:238— последнее выглядит точечной опечаткой/несогласованностью в существующем коде, а не стилем, который стоит повторять.client/admin_tokens/isp_tokens— per-test, общая БД-сессия только внутри одного теста) — это ровно то, что требует сценарий тикета («в той же транзакции под админом создать этап... и убедиться, что isp1 его не видит»).Form3PhaseService.get_listотдельный unit-тест с моками не писал — тикет явно требовал unit-тест только дляFormPhaseService.get_list; ролевая фильтрация для Form3 покрыта интеграционным путём (черезGET /stages/project/{id}и косвенно через отсутствие побочных эффектов на существующих form3-тестах).🤖 Generated with Claude Code
- FormPhaseRepository.get_list и Form3PhaseRepository.get_list: новый необязательный параметр role для фильтрации по v3.role.code. - FormPhaseService.get_list и Form3PhaseService.get_list: для не-админа передают в репозиторий role по коду роли пользователя (единая функция role_code_for_user в form_phase_service.py), для ADMIN фильтр не нужен. - GET /stages/form/{form_id} и GET /stages/project/{project_id}: сначала проверяют доступ к форме/проекту (BudgetFormService.get / ProjectService.get_instance), при отсутствии доступа — 404 вместо AttributeError → 500. - StagesTable.jsx: подпись роли этапа показывается только админу. Closes #250. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>В ветку влит origin/test (merge, без rebase и force-push), PR снова сливается без конфликтов. Единственный конфликт — api/tests/integration/test_form_phases_api_smoke.py: оставлены оба набора тестов (role filter и 404 из этого PR плюс тест aware-дат из test). Код и сигнатуры сервисов/репозиториев/роутера этапов после merge согласованы, миграций в PR нет.
Тесты (Postgres 16, alembic с нуля, полный backend-набор):
Мерж не выполнялся.