from __future__ import annotations from datetime import datetime import sqlalchemy as sa from sqlalchemy.dialects import postgresql from sqlalchemy.orm import Mapped, mapped_column from app.core.config import settings # BIGINT в Postgres; в SQLite автоинкремент работает только у колонки ровно # "INTEGER PRIMARY KEY" (алиас rowid), поэтому для локальной БД — Integer. BigIntPK = sa.BigInteger().with_variant(sa.Integer(), "sqlite") # JSONB в Postgres (индексируемый, компактный), обычный JSON в SQLite. JsonType = sa.JSON().with_variant(postgresql.JSONB(), "postgresql") # Деньги, цены, курсы — только Numeric. Точность с запасом: курсы ЦБ бывают # с 8 знаками на единицу (VND), цены MOEX — до 6 знаков. Price = sa.Numeric(18, 6) Rate = sa.Numeric(18, 8) Amount = sa.Numeric(20, 6) Percent = sa.Numeric(12, 6) Quantity = sa.Numeric(20, 4) SYSTEM_USER = "system" def qualified(table_column: str) -> str: """'currency.id' -> 'schema.currency.id', если задана DATABASE_SCHEMA. Нужен для ForeignKey: при заданной схеме таблицы в metadata регистрируются как 'schema.table', и FK без схемы не разрешится. """ if settings.DATABASE_SCHEMA: return f"{settings.DATABASE_SCHEMA}.{table_column}" return table_column class IdMixin: id: Mapped[int] = mapped_column(BigIntPK, sa.Identity(), primary_key=True) class AuditMixin: """created_*/updated_* на всех таблицах. Пользователь — email из профиля Keycloak, для джобов — SYSTEM_USER. updated_at при upsert (INSERT ... ON CONFLICT) выставляется явно в репозиториях: onupdate срабатывает только для ORM-UPDATE. """ created_at: Mapped[datetime] = mapped_column( sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False ) created_by: Mapped[str] = mapped_column(sa.String(255), nullable=False) updated_at: Mapped[datetime] = mapped_column( sa.DateTime(timezone=True), server_default=sa.func.now(), onupdate=sa.func.now(), nullable=False, ) updated_by: Mapped[str] = mapped_column(sa.String(255), nullable=False)