I-2: delta MODIFIED consolidations-api «Статусы Среза 4» (загрузка в консолидацию). T1.1: шапки моделей — DBML v5.18. T3.1: lock_versions_of_consolidation — двухшаговая блокировка (FOR UPDATE без членства, затем выборка членов); тесты гонки и выборки. M-1: тест миграции 0006 — порядок кварталов внутри года. M-2: тест 404 для ЮЛ чужого филиала на столе своей консолидации. T4.1: docstring supersede_previous — перенос строк. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
180 lines
9.4 KiB
Python
180 lines
9.4 KiB
Python
"""Часть 01 DBML v5.18 «Справочники»: филиалы, ГК и ЮЛ (SCD4), алиасы, ГСК/ГСЗ (SCD2)."""
|
||
|
||
import datetime
|
||
import uuid
|
||
|
||
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Index, Sequence, String, text
|
||
from sqlalchemy.dialects.postgresql import UUID
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from .base import SCHEMA, Base, UUIDPrimaryMixin
|
||
from .enums import EntityType, LeHistoryEvent, RelatedGroupKind, pg_enum
|
||
|
||
#: Последовательности кодов ГК и ГСК/ГСЗ (design D-5): `GK-000001`, `GSK-000001`/`GSZ-000001`. В DBML не описываются
|
||
#: (не таблицы); создаются миграцией 0002 и `Base.metadata.create_all` (привязаны к metadata).
|
||
COMPANY_GROUP_CODE_SEQ = Sequence("company_group_code_seq", schema=SCHEMA, metadata=Base.metadata)
|
||
RELATED_GROUP_CODE_SEQ = Sequence("related_group_code_seq", schema=SCHEMA, metadata=Base.metadata)
|
||
|
||
|
||
class RegionalOffice(UUIDPrimaryMixin, Base):
|
||
"""Филиал: ГО и региональные филиалы (DECISION-3: ГО — обычная запись с `is_head_office`)."""
|
||
|
||
__tablename__ = "regional_office"
|
||
|
||
name: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
||
is_head_office: 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 CompanyGroup(UUIDPrimaryMixin, Base):
|
||
"""ГК — текущее состояние (SCD4); история — `company_group_history`."""
|
||
|
||
__tablename__ = "company_group"
|
||
|
||
code: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
||
crm_id: Mapped[str | None] = mapped_column(String, unique=True)
|
||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||
base_regional_office_id: Mapped[uuid.UUID | None] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("regional_office.id")
|
||
)
|
||
is_archived: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
||
valid_from: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||
updated_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
|
||
|
||
class CompanyGroupHistory(UUIDPrimaryMixin, Base):
|
||
"""Прошлые снимки ГК (SCD4)."""
|
||
|
||
__tablename__ = "company_group_history"
|
||
__table_args__ = (
|
||
Index("uq_company_group_history_company_group_id_valid_from", "company_group_id", "valid_from", unique=True),
|
||
)
|
||
|
||
company_group_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("company_group.id"), nullable=False
|
||
)
|
||
crm_id: Mapped[str | None] = mapped_column(String)
|
||
name: Mapped[str | None] = mapped_column(String)
|
||
base_regional_office_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
|
||
valid_from: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
valid_to: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
changed_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||
|
||
|
||
class LegalEntity(UUIDPrimaryMixin, Base):
|
||
"""Организация (ЮЛ/ИП) — текущее состояние (SCD4); `company_group_id IS NULL` = монозаёмщик."""
|
||
|
||
__tablename__ = "legal_entity"
|
||
|
||
inn: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
||
crm_id: Mapped[str | None] = mapped_column(String, unique=True)
|
||
entity_type: Mapped[EntityType | None] = mapped_column(pg_enum("entity_type"))
|
||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||
company_group_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("company_group.id"))
|
||
regional_office_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("regional_office.id"))
|
||
close_date: Mapped[datetime.date | None] = mapped_column(Date)
|
||
valid_from: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||
updated_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
# v5.17, последней колонкой (как её добавляет миграция 0005): КПП, nullable; у ИП запрещён — проверяет ядро
|
||
kpp: Mapped[str | None] = mapped_column(String)
|
||
|
||
|
||
class LegalEntityHistory(UUIDPrimaryMixin, Base):
|
||
"""Прошлые снимки ЮЛ (SCD4) и события для архива ГК."""
|
||
|
||
__tablename__ = "legal_entity_history"
|
||
__table_args__ = (
|
||
Index("uq_legal_entity_history_legal_entity_id_valid_from", "legal_entity_id", "valid_from", unique=True),
|
||
)
|
||
|
||
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||
)
|
||
inn: Mapped[str | None] = mapped_column(String)
|
||
crm_id: Mapped[str | None] = mapped_column(String)
|
||
entity_type: Mapped[EntityType | None] = mapped_column(pg_enum("entity_type"))
|
||
name: Mapped[str | None] = mapped_column(String)
|
||
company_group_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
|
||
regional_office_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
|
||
close_date: Mapped[datetime.date | None] = mapped_column(Date)
|
||
event_type: Mapped[LeHistoryEvent | None] = mapped_column(pg_enum("le_history_event"))
|
||
effective_date: Mapped[datetime.date | None] = mapped_column(Date)
|
||
valid_from: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
valid_to: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
changed_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||
kpp: Mapped[str | None] = mapped_column(String) # v5.17, последней колонкой: снимок КПП
|
||
|
||
|
||
class LegalEntityAlias(UUIDPrimaryMixin, Base):
|
||
"""Словарь соответствий наименований контрагентов (ТЗ 6.5)."""
|
||
|
||
__tablename__ = "legal_entity_alias"
|
||
__table_args__ = (
|
||
Index(
|
||
"uq_legal_entity_alias_legal_entity_id_normalized_alias", "legal_entity_id", "normalized_alias", unique=True
|
||
),
|
||
Index("ix_legal_entity_alias_normalized_alias", "normalized_alias"),
|
||
)
|
||
|
||
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||
)
|
||
alias: Mapped[str] = mapped_column(String, nullable=False)
|
||
normalized_alias: Mapped[str] = mapped_column(String, nullable=False)
|
||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
|
||
|
||
class RelatedGroup(UUIDPrimaryMixin, Base):
|
||
"""ГСК/ГСЗ — текущее состояние; иерархия групп не поддерживается (ТЗ 6.1)."""
|
||
|
||
__tablename__ = "related_group"
|
||
|
||
code: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
||
company_group_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("company_group.id"), nullable=False
|
||
)
|
||
kind: Mapped[RelatedGroupKind] = mapped_column(pg_enum("related_group_kind"), nullable=False)
|
||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||
created_date: Mapped[datetime.date | None] = mapped_column(Date)
|
||
|
||
|
||
class RelatedGroupMember(UUIDPrimaryMixin, Base):
|
||
"""Состав ГСК/ГСЗ — SCD2; исключение = закрытие версии (`is_current=false`)."""
|
||
|
||
__tablename__ = "related_group_member"
|
||
__table_args__ = (
|
||
# (related_group_id, legal_entity_id): partial UNIQUE WHERE is_current
|
||
Index(
|
||
"uq_related_group_member_group_entity_current",
|
||
"related_group_id",
|
||
"legal_entity_id",
|
||
unique=True,
|
||
postgresql_where=text("is_current"),
|
||
),
|
||
# (legal_entity_id, kind): partial UNIQUE WHERE is_current — v5.14: ЮЛ в одной ГСК И одной ГСЗ одновременно
|
||
Index(
|
||
"uq_related_group_member_entity_kind_current",
|
||
"legal_entity_id",
|
||
"kind",
|
||
unique=True,
|
||
postgresql_where=text("is_current"),
|
||
),
|
||
)
|
||
|
||
related_group_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("related_group.id"), nullable=False
|
||
)
|
||
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||
)
|
||
kind: Mapped[RelatedGroupKind] = mapped_column(pg_enum("related_group_kind"), nullable=False)
|
||
valid_from: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||
valid_to: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||
is_current: Mapped[bool] = mapped_column(Boolean, nullable=False)
|
||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|