"""Часть 02 DBML v5.15 «Пользователи и доступ»: пользователи, роли, права как данные (`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)