arm-core/api/src/models/access.py
Andrey Epifancev 4cc926c033 КПП юридического лица: legal_entity.kpp, миграция 0005, API, импорт (DBML v5.17, #45)
- модели LegalEntity.kpp / LegalEntityHistory.kpp (последней колонкой), миграция 0005_dbml_v5_17 (upgrade/downgrade);
- domain/kpp.py: 9 символов (4 цифры, 2 цифры/латинские A-Z, 3 цифры), у ИП запрещён, иначе 422 INVALID_KPP;
- схемы Create/Update/Out/Detail/HistoryOut, PATCH пишет снимок history и аудит before/after с kpp, поиск q по КПП;
- импорт справочника: необязательная колонка КПП (синоним kpp), код INVALID_KPP, update обновляет непустой КПП;
- dev-seed: необязательный ключ kpp (значений в данных нет, не выдумываем);
- сверка с DBML, Makefile, README, CLAUDE.md, openspec/config.yaml — на v5.17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
2026-10-07 18:51:50 +04:00

76 lines
3.9 KiB
Python
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.

"""Часть 02 DBML v5.17 «Пользователи и доступ»: пользователи, роли, права как данные (`effective_permissions`)."""
import datetime
import uuid
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, String, Text, text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base, UUIDPrimaryMixin
from .enums import BusinessSegment, PermissionAction, PermissionObject, pg_enum
class AppUser(UUIDPrimaryMixin, Base):
"""Пользователь; аутентификация внешняя (JWT РАИСА, email → `app_user`)."""
__tablename__ = "app_user"
external_id: Mapped[str | None] = mapped_column(String, unique=True)
email: Mapped[str | None] = mapped_column(String, unique=True)
full_name: Mapped[str] = mapped_column(String, nullable=False)
employee_no: Mapped[str | None] = mapped_column(String)
regional_office_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("regional_office.id"), nullable=False
)
business_segment: Mapped[BusinessSegment | None] = mapped_column(pg_enum("business_segment"))
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("true"))
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
class Role(UUIDPrimaryMixin, Base):
"""Роль; полномочия — через `role_permission`, код роли в бизнес-логике не проверяется."""
__tablename__ = "role"
code: Mapped[str] = mapped_column(String, nullable=False, unique=True)
name: Mapped[str] = mapped_column(String, nullable=False)
description: Mapped[str | None] = mapped_column(Text)
is_system: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("true"))
class Permission(UUIDPrimaryMixin, Base):
"""Справочник прав «объект × действие» (seed, привязан к коду)."""
__tablename__ = "permission"
__table_args__ = (Index("uq_permission_object_type_action", "object_type", "action", unique=True),)
code: Mapped[str] = mapped_column(String, nullable=False, unique=True)
object_type: Mapped[PermissionObject] = mapped_column(pg_enum("permission_object"), nullable=False)
action: Mapped[PermissionAction] = mapped_column(pg_enum("permission_action"), nullable=False)
name: Mapped[str] = mapped_column(String, nullable=False)
description: Mapped[str | None] = mapped_column(Text)
class RolePermission(Base):
"""Матрица доступов как данные; составной PK `(role_id, permission_id)`, суррогатного `id` нет."""
__tablename__ = "role_permission"
role_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("role.id"), primary_key=True)
permission_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("permission.id"), primary_key=True)
granted_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
granted_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
class UserRole(UUIDPrimaryMixin, Base):
"""Назначение ролей пользователю (M:N, current-state); история — `audit_event(USER_ROLE_CHANGE)`."""
__tablename__ = "user_role"
__table_args__ = (Index("uq_user_role_user_id_role_id", "user_id", "role_id", unique=True),)
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"), nullable=False)
role_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("role.id"), nullable=False)