Andrey Epifancev 458392e94d feat(model): DBML v5.14 — related_group_member.kind, audit_action +2, миграция 0002, последовательности кодов
- RelatedGroupMember.kind (related_group_kind, NOT NULL) между legal_entity_id и valid_from, как в DBML;
  частичный уникальный индекс uq_related_group_member_entity_kind_current (legal_entity_id, kind) WHERE is_current —
  ЮЛ одновременно в одной ГСК и одной ГСЗ.
- AuditAction: LEGAL_ENTITY_CHANGE (после GROUP_CHANGE), USER_CHANGE (после USER_ROLE_CHANGE).
- Последовательности company_group_code_seq / related_group_code_seq в metadata (design D-5).
- Миграция 0002_dbml_v5_14: related_group_member пересоздаётся с переносом строк (kind из группы), чтобы порядок
  колонок совпал с DBML и create_all; ALTER TYPE ADD VALUE ×2; downgrade пересоздаёт audit_action v5.13.
- DBML_VERSION = 5.14, пути к data-model-v5.14.dbml (Makefile, dbml_parity), тесты: откат на шаг, перенос строк,
  уникальность gsk+gsz, офлайн-SQL до head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:49:32 +04:00

60 lines
3.4 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.

"""Часть 05 DBML v5.14 «ОСВ»: строка оборотно-сальдовой ведомости (ТЗ 6.2, 6.5).
Первичка не мутируется: сырые значения хранятся как пришли, правка аналитика — рядом
(`is_modified`, `original_values`, `modified_by/at` + audit_event(RECOGNIZED_DATA_EDIT)).
"""
import datetime
import decimal
import uuid
from typing import Any
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, Numeric, SmallInteger, String, text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base, UUIDPrimaryMixin, nullable_jsonb
from .enums import OsvDataType, OsvGroupType, pg_enum
class OsvEntry(UUIDPrimaryMixin, Base):
"""Строка ОСВ; суммы — рубли, `numeric(18,2)`."""
__tablename__ = "osv_entry"
__table_args__ = (
Index("uq_osv_entry_document_id_row_number", "document_id", "row_number", unique=True),
Index("ix_osv_entry_owner_account_counterparty", "owner_legal_entity_id", "account_id", "counterparty_id"),
)
recognition_job_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("recognition_job.id"), nullable=False
)
document_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("document.id"), nullable=False)
owner_legal_entity_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
)
parent_entry_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("osv_entry.id"))
group_type: Mapped[OsvGroupType | None] = mapped_column(pg_enum("osv_group_type"))
group_name: Mapped[str | None] = mapped_column(String)
row_number: Mapped[int | None] = mapped_column(Integer)
level: Mapped[int | None] = mapped_column(SmallInteger)
row_value: Mapped[str | None] = mapped_column(String)
data_type: Mapped[OsvDataType | None] = mapped_column(pg_enum("osv_data_type"))
account_code: Mapped[str | None] = mapped_column(String)
account_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("chart_of_account.id"))
account_root: Mapped[str | None] = mapped_column(String)
counterparty_name: Mapped[str | None] = mapped_column(String)
counterparty_inn: Mapped[str | None] = mapped_column(String)
counterparty_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("legal_entity.id"))
opening_debit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
opening_credit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
turnover_debit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
turnover_credit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
closing_debit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
closing_credit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
is_modified: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
original_values: Mapped[Any | None] = mapped_column(nullable_jsonb())
modified_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
modified_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
is_excisable: Mapped[bool | None] = mapped_column(Boolean)