refactor(schemas): одна модель филиала в schemas/directories.py (R-13)

RegionalOfficeOut больше не дублируется в schemas/me.py: в schemas/directories.py
определены RegionalOfficeBrief (id, name, is_head_office — форма /me Среза 0) и
RegionalOfficeOut(RegionalOfficeBrief) с is_active. /me импортирует RegionalOfficeBrief,
тело ответа /me не меняется; в OpenAPI нет двух одноимённых компонентов.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-04 23:54:59 +04:00
parent 83746e694e
commit 4dd966b0b7
5 changed files with 18 additions and 18 deletions

View File

@ -9,7 +9,8 @@ from typing import Annotated
from fastapi import APIRouter, Depends
from src.api.dependencies.auth import Principal, current_user
from src.schemas.me import MeResponse, RegionalOfficeOut
from src.schemas.directories import RegionalOfficeBrief
from src.schemas.me import MeResponse
router = APIRouter(tags=["me"])
@ -24,7 +25,7 @@ async def get_me(principal: Annotated[Principal, Depends(current_user)]) -> MeRe
# пользователь найден по email, поэтому он заполнен; пустая строка — лишь для типов (колонка nullable)
email=user.email or "",
full_name=user.full_name,
regional_office=RegionalOfficeOut(id=office.id, name=office.name, is_head_office=office.is_head_office),
regional_office=RegionalOfficeBrief.model_validate(office),
business_segment=user.business_segment,
roles=sorted(principal.roles),
permissions=sorted(principal.permissions),

View File

@ -19,14 +19,19 @@ def _blank_to_none(value: str | None) -> str | None:
return stripped or None
class RegionalOfficeOut(BaseModel):
"""Филиал (`GET /directories/regional-offices`)."""
class RegionalOfficeBrief(BaseModel):
"""Филиал кратко: профиль `GET /me` (форма ответа Среза 0 — без `is_active`)."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
name: str
is_head_office: bool
class RegionalOfficeOut(RegionalOfficeBrief):
"""Филиал целиком (`GET /directories/regional-offices`, пользователи администратора)."""
is_active: bool

View File

@ -5,14 +5,7 @@ import uuid
from pydantic import BaseModel, ConfigDict
from src.models.enums import BusinessSegment
class RegionalOfficeOut(BaseModel):
"""Филиал пользователя."""
id: uuid.UUID
name: str
is_head_office: bool
from src.schemas.directories import RegionalOfficeBrief
class MeResponse(BaseModel):
@ -23,7 +16,7 @@ class MeResponse(BaseModel):
id: uuid.UUID
email: str
full_name: str
regional_office: RegionalOfficeOut
regional_office: RegionalOfficeBrief
business_segment: BusinessSegment | None
roles: list[str]
permissions: list[str]

View File

@ -34,7 +34,8 @@ from src.models.access import AppUser, Role, UserRole
from src.models.directories import RegionalOffice
from src.models.enums import BusinessSegment
from src.repositories.users import AmbiguousUserError, load_principal_data
from src.schemas.me import MeResponse, RegionalOfficeOut
from src.schemas.directories import RegionalOfficeBrief
from src.schemas.me import MeResponse
SECRET = "test-secret-for-auth-deps-0123456789abcdef"
OTHER_SECRET = "foreign-secret-for-auth-deps-0123456789ab"
@ -487,7 +488,7 @@ async def test_repository_raises_on_ambiguity(seeded: AsyncSession) -> None:
def test_me_response_schema_serializes_enum_and_null_segment() -> None:
office = RegionalOfficeOut(id=uuid.uuid4(), name="ГО", is_head_office=True)
office = RegionalOfficeBrief(id=uuid.uuid4(), name="ГО", is_head_office=True)
fields: dict[str, Any] = {
"id": uuid.uuid4(),
"email": "a@b.c",

View File

@ -36,7 +36,7 @@ from src.db.db import get_async_session
from src.models.directories import RegionalOffice
from src.models.enums import AuditAction, AuditSection
from src.schemas.common import ErrorDetail, Page, PageParams
from src.schemas.me import RegionalOfficeOut
from src.schemas.directories import RegionalOfficeBrief
from src.services.audit import AuditService
from tests.pg_helpers import alembic_async
@ -141,8 +141,8 @@ def test_domain_error_extra_cannot_shadow_code_or_message() -> None:
def test_page_serializes() -> None:
office = RegionalOfficeOut(id=CONFLICT_ID, name="ГО", is_head_office=True)
page = Page[RegionalOfficeOut](items=[office], total=7, limit=1, offset=3)
office = RegionalOfficeBrief(id=CONFLICT_ID, name="ГО", is_head_office=True)
page = Page[RegionalOfficeBrief](items=[office], total=7, limit=1, offset=3)
assert page.model_dump(mode="json") == {
"items": [{"id": str(CONFLICT_ID), "name": "ГО", "is_head_office": True}],
"total": 7,