feat: ORM-модели частей 07–09 DBML v5.13 (консолидация, КФО, аудит/ЕХ, required_document_rule)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
842bb584ee
commit
48525731ff
@ -3,7 +3,7 @@
|
|||||||
Импорт всех модулей моделей обязателен: он регистрирует таблицы в `Base.metadata` (create_all, alembic).
|
Импорт всех модулей моделей обязателен: он регистрирует таблицы в `Base.metadata` (create_all, alembic).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import access, accounting, directories, files, osv, statements
|
from . import access, accounting, audit_ex, consolidation, directories, files, kfo, osv, statements
|
||||||
from .base import Base, UUIDPrimaryMixin
|
from .base import Base, UUIDPrimaryMixin
|
||||||
|
|
||||||
#: Версия DBML, которой соответствуют модели (сверяется с шапкой DBML в check_dbml_vs_orm).
|
#: Версия DBML, которой соответствуют модели (сверяется с шапкой DBML в check_dbml_vs_orm).
|
||||||
@ -15,8 +15,11 @@ __all__ = [
|
|||||||
"UUIDPrimaryMixin",
|
"UUIDPrimaryMixin",
|
||||||
"access",
|
"access",
|
||||||
"accounting",
|
"accounting",
|
||||||
|
"audit_ex",
|
||||||
|
"consolidation",
|
||||||
"directories",
|
"directories",
|
||||||
"files",
|
"files",
|
||||||
|
"kfo",
|
||||||
"osv",
|
"osv",
|
||||||
"statements",
|
"statements",
|
||||||
]
|
]
|
||||||
|
|||||||
156
api/src/models/audit_ex.py
Normal file
156
api/src/models/audit_ex.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
"""Часть 09 DBML v5.13 «Комментарии, аудит, ЕХФД, прочее»: комментарии, журнал аудита, срезы ЕХ, чек-лист документов.
|
||||||
|
|
||||||
|
`audit_event` — append-only, высокообъёмная (before/after — jsonb). Срезы ЕХ самодостаточны (v5.12): FK на
|
||||||
|
консолидацию и её результаты отсутствуют (их удаляют по сроку, срез хранится «вечно»): `ex_snapshot.source_version_id`
|
||||||
|
— uuid без FK. `required_document_rule` (секция DBML «ПРОЧЕЕ») лежит здесь по рулингу R-4.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Index, Integer, SmallInteger, String, Text
|
||||||
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from .base import Base, UUIDPrimaryMixin
|
||||||
|
from .enums import (
|
||||||
|
AuditAction,
|
||||||
|
AuditSection,
|
||||||
|
ConsolidationApproach,
|
||||||
|
DocumentType,
|
||||||
|
ExSnapshotStatus,
|
||||||
|
PeriodQuarter,
|
||||||
|
pg_enum,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(UUIDPrimaryMixin, Base):
|
||||||
|
"""Комментарий к объекту (документ, версия, строка КФО и т.п.; ТЗ 6.3/6.4); связь с объектом — полиморфная."""
|
||||||
|
|
||||||
|
__tablename__ = "comment"
|
||||||
|
__table_args__ = (Index("ix_comment_object_type_object_id", "object_type", "object_id"),)
|
||||||
|
|
||||||
|
object_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
object_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
|
||||||
|
text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
author_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"), nullable=False)
|
||||||
|
created_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class AuditEvent(UUIDPrimaryMixin, Base):
|
||||||
|
"""Журнал изменений (ТЗ 8.2). Append-only; `occurred_at` проставляет приложение (default в DBML нет)."""
|
||||||
|
|
||||||
|
__tablename__ = "audit_event"
|
||||||
|
__table_args__ = (
|
||||||
|
Index("ix_audit_event_version_occurred_at", "consolidation_version_id", "occurred_at"),
|
||||||
|
Index("ix_audit_event_object_type_object_id", "object_type", "object_id"),
|
||||||
|
Index("ix_audit_event_user_occurred_at", "user_id", "occurred_at"),
|
||||||
|
)
|
||||||
|
|
||||||
|
occurred_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
user_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
action: Mapped[AuditAction] = mapped_column(pg_enum("audit_action"), nullable=False)
|
||||||
|
section: Mapped[AuditSection] = mapped_column(pg_enum("audit_section"), nullable=False)
|
||||||
|
object_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
object_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("consolidation_version.id")
|
||||||
|
)
|
||||||
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
before_value: Mapped[Any | None] = mapped_column(JSONB)
|
||||||
|
after_value: Mapped[Any | None] = mapped_column(JSONB)
|
||||||
|
comment: Mapped[str | None] = mapped_column(Text)
|
||||||
|
result: Mapped[str | None] = mapped_column(String)
|
||||||
|
changed_field: Mapped[str | None] = mapped_column(String)
|
||||||
|
ip_address: Mapped[str | None] = mapped_column(String)
|
||||||
|
user_agent: Mapped[str | None] = mapped_column(String)
|
||||||
|
|
||||||
|
|
||||||
|
class ExSnapshot(UUIDPrimaryMixin, Base):
|
||||||
|
"""Срез ЕХ: одна ГСК на одну отчётную дату (ЕХ 6.2). Неизменяем и самодостаточен — без FK на консолидацию."""
|
||||||
|
|
||||||
|
__tablename__ = "ex_snapshot"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_ex_snapshot_group_year_quarter_version",
|
||||||
|
"related_group_id",
|
||||||
|
"year",
|
||||||
|
"quarter",
|
||||||
|
"version_no",
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
Index("ix_ex_snapshot_group_year_quarter_status", "related_group_id", "year", "quarter", "status"),
|
||||||
|
)
|
||||||
|
|
||||||
|
related_group_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("related_group.id"), nullable=False
|
||||||
|
)
|
||||||
|
company_group_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("company_group.id"), nullable=False
|
||||||
|
)
|
||||||
|
reporting_date: Mapped[datetime.date] = mapped_column(Date, nullable=False)
|
||||||
|
year: Mapped[int] = mapped_column(SmallInteger, nullable=False)
|
||||||
|
quarter: Mapped[PeriodQuarter] = mapped_column(pg_enum("period_quarter"), nullable=False)
|
||||||
|
version_no: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||||
|
status: Mapped[ExSnapshotStatus] = mapped_column(pg_enum("ex_snapshot_status"), nullable=False)
|
||||||
|
# id consolidation_version-источника БЕЗ FK (v5.12): источник удаляется по сроку, срез — нет
|
||||||
|
source_version_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False, unique=True)
|
||||||
|
source_version_no: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||||
|
superseded_by_snapshot_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("ex_snapshot.id")
|
||||||
|
)
|
||||||
|
verified_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
written_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
written_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
archived_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
data_lake_ref: Mapped[str | None] = mapped_column(String)
|
||||||
|
|
||||||
|
|
||||||
|
class ExSnapshotMember(Base):
|
||||||
|
"""Периметр среза на дату (ЕХ 6.3); `inn`/`name` денормализованы; составной PK (ex_snapshot_id, legal_entity_id)."""
|
||||||
|
|
||||||
|
__tablename__ = "ex_snapshot_member"
|
||||||
|
|
||||||
|
ex_snapshot_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("ex_snapshot.id"), primary_key=True
|
||||||
|
)
|
||||||
|
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("legal_entity.id"), primary_key=True
|
||||||
|
)
|
||||||
|
inn: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ExSnapshotItem(UUIDPrimaryMixin, Base):
|
||||||
|
"""Состав среза: `primary_document` -> `document` (исходники хранятся весь срок), всё остальное -> `payload`."""
|
||||||
|
|
||||||
|
__tablename__ = "ex_snapshot_item"
|
||||||
|
__table_args__ = (Index("ix_ex_snapshot_item_snapshot_item_type", "ex_snapshot_id", "item_type"),)
|
||||||
|
|
||||||
|
ex_snapshot_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("ex_snapshot.id"), nullable=False)
|
||||||
|
item_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
legal_entity_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("legal_entity.id"))
|
||||||
|
document_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("document.id"))
|
||||||
|
payload: Mapped[Any | None] = mapped_column(JSONB)
|
||||||
|
payload_schema_version: Mapped[str | None] = mapped_column(String)
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredDocumentRule(UUIDPrimaryMixin, Base):
|
||||||
|
"""Состав обязательных документов по подходу — основа чек-листа (ТЗ 5.1/6.2, сноска 12)."""
|
||||||
|
|
||||||
|
__tablename__ = "required_document_rule"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_required_document_rule_approach_type_account",
|
||||||
|
"approach",
|
||||||
|
"document_type",
|
||||||
|
"account_id",
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
approach: Mapped[ConsolidationApproach] = mapped_column(pg_enum("consolidation_approach"), nullable=False)
|
||||||
|
document_type: Mapped[DocumentType] = mapped_column(pg_enum("document_type"), nullable=False)
|
||||||
|
account_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("chart_of_account.id"))
|
||||||
|
is_mandatory: Mapped[bool] = mapped_column(Boolean, nullable=False)
|
||||||
158
api/src/models/consolidation.py
Normal file
158
api/src/models/consolidation.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""Часть 07 DBML v5.13 «Консолидация»: консолидация ГК за период, версии, маршрут, блокировка, периметр, документы.
|
||||||
|
|
||||||
|
Производные консолидации (сводная ОСВ, пары ВГО, шахматка) НЕ хранятся (DECISION-5): считаются запросом из
|
||||||
|
`osv_entry` × `consolidation_member` × `elimination_rule`. Версия после VERIFIED — read-only (ТЗ 6.10).
|
||||||
|
Длинные имена FK на `consolidation_version` заданы явно (по конвенции R-9 > 63 символов PostgreSQL).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Index, Integer, SmallInteger, String, Text, text
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from .base import Base, UUIDPrimaryMixin
|
||||||
|
from .enums import (
|
||||||
|
ConsolidationApproach,
|
||||||
|
ConsolidationScenario,
|
||||||
|
ConsolidationStatus,
|
||||||
|
PeriodQuarter,
|
||||||
|
pg_enum,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Consolidation(UUIDPrimaryMixin, Base):
|
||||||
|
"""Логическая консолидация ГК за период (ТЗ 6.10); версии различаются периметром `consolidation_member`."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation"
|
||||||
|
__table_args__ = (Index("uq_consolidation_group_year_quarter", "company_group_id", "year", "quarter", unique=True),)
|
||||||
|
|
||||||
|
company_group_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("company_group.id"), nullable=False
|
||||||
|
)
|
||||||
|
related_group_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("related_group.id"))
|
||||||
|
year: Mapped[int] = mapped_column(SmallInteger, nullable=False)
|
||||||
|
quarter: Mapped[PeriodQuarter] = mapped_column(pg_enum("period_quarter"), nullable=False)
|
||||||
|
reporting_date: Mapped[datetime.date] = mapped_column(Date, nullable=False)
|
||||||
|
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolidationVersion(UUIDPrimaryMixin, Base):
|
||||||
|
"""Версия консолидации: сценарий, подход, статус, маршрут согласования, решение УОБ (ТЗ 5.1, 6.10)."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation_version"
|
||||||
|
__table_args__ = (
|
||||||
|
Index("uq_consolidation_version_consolidation_id_version_no", "consolidation_id", "version_no", unique=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("consolidation.id"), nullable=False
|
||||||
|
)
|
||||||
|
version_no: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||||
|
status: Mapped[ConsolidationStatus] = mapped_column(pg_enum("consolidation_status"), nullable=False)
|
||||||
|
scenario: Mapped[ConsolidationScenario] = mapped_column(pg_enum("consolidation_scenario"), nullable=False)
|
||||||
|
approach: Mapped[ConsolidationApproach] = mapped_column(
|
||||||
|
pg_enum("consolidation_approach"), nullable=False, server_default=text("'BASIC'")
|
||||||
|
)
|
||||||
|
goodwill_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
||||||
|
initiator_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"), nullable=False)
|
||||||
|
initiator_office_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("regional_office.id"), nullable=False
|
||||||
|
)
|
||||||
|
approved_in_rf_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
approved_in_rf_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
verified_in_go_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
verified_in_go_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
uob_decision_no: Mapped[str | None] = mapped_column(String)
|
||||||
|
uob_decision_date: Mapped[datetime.date | None] = mapped_column(Date)
|
||||||
|
methodology_deviations: Mapped[str | None] = mapped_column(Text)
|
||||||
|
elimination_rules_version: Mapped[int | None] = mapped_column(Integer)
|
||||||
|
is_in_work: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("true"))
|
||||||
|
previous_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_consolidation_version_previous_version_id"),
|
||||||
|
)
|
||||||
|
cancelled_reason: Mapped[str | None] = mapped_column(Text)
|
||||||
|
created_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
updated_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolidationStatusHistory(UUIDPrimaryMixin, Base):
|
||||||
|
"""Маршрут согласования: журнал смен статуса версии (ТЗ 6.10, 8.2)."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation_status_history"
|
||||||
|
|
||||||
|
version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_consolidation_status_history_version_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
from_status: Mapped[ConsolidationStatus | None] = mapped_column(pg_enum("consolidation_status"))
|
||||||
|
to_status: Mapped[ConsolidationStatus] = mapped_column(pg_enum("consolidation_status"), nullable=False)
|
||||||
|
changed_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"), nullable=False)
|
||||||
|
changed_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
comment: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolidationLock(UUIDPrimaryMixin, Base):
|
||||||
|
"""Взятие версии в работу: ответственный сейчас = активная блокировка (`released_at IS NULL`)."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation_lock"
|
||||||
|
__table_args__ = (
|
||||||
|
# не более одной активной блокировки на версию
|
||||||
|
Index(
|
||||||
|
"uq_consolidation_lock_version_active",
|
||||||
|
"consolidation_version_id",
|
||||||
|
unique=True,
|
||||||
|
postgresql_where=text("released_at IS NULL"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_consolidation_lock_consolidation_version_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
locked_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"), nullable=False)
|
||||||
|
locked_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
released_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
taken_over_from: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
takeover_reason: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolidationMember(UUIDPrimaryMixin, Base):
|
||||||
|
"""Зафиксированный периметр версии: изменение `related_group_member` существующие версии не меняет."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation_member"
|
||||||
|
__table_args__ = (
|
||||||
|
Index("uq_consolidation_member_version_entity", "consolidation_version_id", "legal_entity_id", unique=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_consolidation_member_consolidation_version_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||||||
|
)
|
||||||
|
is_head_entity: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolidationDocument(UUIDPrimaryMixin, Base):
|
||||||
|
"""Явная привязка логических документов к версии (Прил. 2 ТЗ); чек-лист = required_document_rule × периметр."""
|
||||||
|
|
||||||
|
__tablename__ = "consolidation_document"
|
||||||
|
__table_args__ = (
|
||||||
|
Index("uq_consolidation_document_version_document", "consolidation_version_id", "document_id", unique=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_consolidation_document_consolidation_version_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
document_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("document.id"), nullable=False)
|
||||||
197
api/src/models/kfo.py
Normal file
197
api/src/models/kfo.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
"""Часть 08 DBML v5.13 «КФО»: корректировки, гудвилл, результат КФО (шапка, строки, разрез по компаниям), КФО клиента.
|
||||||
|
|
||||||
|
Корректировки и гудвилл — ручные решения аналитика, хранятся поверх первички (overlay). Результат КФО
|
||||||
|
замораживается при VERIFIED. Деньги — `numeric(18,2)`, проценты — `numeric(7,4)` / `numeric(9,4)`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, Numeric, String, Text, text
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from .base import Base, UUIDPrimaryMixin
|
||||||
|
from .enums import CorrectionStatus, StatementType, pg_enum
|
||||||
|
|
||||||
|
|
||||||
|
class Correction(UUIDPrimaryMixin, Base):
|
||||||
|
"""Корректировка ВГО: итог = свод первички − Σ применённых корректировок; пара — по (rule_id, компании)."""
|
||||||
|
|
||||||
|
__tablename__ = "correction"
|
||||||
|
__table_args__ = (
|
||||||
|
Index("ix_correction_consolidation_version_id", "consolidation_version_id"),
|
||||||
|
Index(
|
||||||
|
"ix_correction_version_rule_entities",
|
||||||
|
"consolidation_version_id",
|
||||||
|
"rule_id",
|
||||||
|
"legal_entity_id",
|
||||||
|
"counterparty_id",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("consolidation_version.id"), nullable=False
|
||||||
|
)
|
||||||
|
statement_type: Mapped[StatementType] = mapped_column(pg_enum("statement_type"), nullable=False)
|
||||||
|
rule_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("elimination_rule.id"))
|
||||||
|
parent_correction_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("correction.id"))
|
||||||
|
legal_entity_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("legal_entity.id"))
|
||||||
|
counterparty_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("legal_entity.id"))
|
||||||
|
debit_line_code: Mapped[str | None] = mapped_column(String)
|
||||||
|
debit_account_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("chart_of_account.id"))
|
||||||
|
debit_adjustment: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
credit_line_code: Mapped[str | None] = mapped_column(String)
|
||||||
|
credit_account_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("chart_of_account.id"))
|
||||||
|
credit_adjustment: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
deviation_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
deviation_percent: Mapped[decimal.Decimal | None] = mapped_column(Numeric(9, 4))
|
||||||
|
reason_code: Mapped[str | None] = mapped_column(String)
|
||||||
|
comment: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
status: Mapped[CorrectionStatus] = mapped_column(
|
||||||
|
pg_enum("correction_status"), nullable=False, server_default=text("'DRAFT'")
|
||||||
|
)
|
||||||
|
version_no: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("1"))
|
||||||
|
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))
|
||||||
|
updated_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
|
||||||
|
|
||||||
|
class Goodwill(UUIDPrimaryMixin, Base):
|
||||||
|
"""Гудвилл по паре «головная организация — дочерняя» (ТЗ 6.7); доля участия вводится вручную, %."""
|
||||||
|
|
||||||
|
__tablename__ = "goodwill"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_goodwill_version_head_subsidiary",
|
||||||
|
"consolidation_version_id",
|
||||||
|
"head_legal_entity_id",
|
||||||
|
"subsidiary_legal_entity_id",
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("consolidation_version.id"), nullable=False
|
||||||
|
)
|
||||||
|
head_legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||||||
|
)
|
||||||
|
subsidiary_legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("legal_entity.id"), nullable=False
|
||||||
|
)
|
||||||
|
ownership_share: Mapped[decimal.Decimal | None] = mapped_column(Numeric(7, 4))
|
||||||
|
financial_investment_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
charter_capital_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
additional_capital_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
participation_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
goodwill_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
include_in_kfo: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
||||||
|
comment: Mapped[str | None] = mapped_column(Text)
|
||||||
|
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))
|
||||||
|
updated_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
updated_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
|
||||||
|
|
||||||
|
class KfoResult(UUIDPrimaryMixin, Base):
|
||||||
|
"""Шапка результата КФО версии + контрольные пороги (ТЗ 6.8); `is_frozen = true` после VERIFIED."""
|
||||||
|
|
||||||
|
__tablename__ = "kfo_result"
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("consolidation_version.id"), nullable=False, unique=True
|
||||||
|
)
|
||||||
|
computed_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
is_frozen: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
||||||
|
net_assets: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
balance_total: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
revenue: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
sales_profit: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
total_deviation_bb: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
total_deviation_ofr: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
threshold_bb_exceeded: Mapped[bool | None] = mapped_column(Boolean)
|
||||||
|
threshold_ofr_exceeded: Mapped[bool | None] = mapped_column(Boolean)
|
||||||
|
threshold_justification: Mapped[str | None] = mapped_column(Text)
|
||||||
|
goodwill_total: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
has_deviations: Mapped[bool | None] = mapped_column(Boolean)
|
||||||
|
|
||||||
|
|
||||||
|
class KfoResultLine(UUIDPrimaryMixin, Base):
|
||||||
|
"""Строка КФО (ФСБУ 4/2023): сумма по компаниям -> авто-элиминация -> корректировки -> итог."""
|
||||||
|
|
||||||
|
__tablename__ = "kfo_result_line"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_kfo_result_line_result_type_code",
|
||||||
|
"kfo_result_id",
|
||||||
|
"statement_type",
|
||||||
|
"line_code",
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
kfo_result_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("kfo_result.id"), nullable=False)
|
||||||
|
statement_type: Mapped[StatementType] = mapped_column(pg_enum("statement_type"), nullable=False)
|
||||||
|
line_code: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
line_name: Mapped[str | None] = mapped_column(String)
|
||||||
|
sum_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
eliminated_auto: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
eliminated_manual: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
goodwill_adjust: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
consolidated_amount: Mapped[decimal.Decimal] = mapped_column(Numeric(18, 2), nullable=False)
|
||||||
|
vgo_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
source_kind: Mapped[str | None] = mapped_column(String)
|
||||||
|
deviation_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
comment: Mapped[str | None] = mapped_column(Text)
|
||||||
|
ordinal: Mapped[int | None] = mapped_column(Integer)
|
||||||
|
|
||||||
|
|
||||||
|
class KfoResultLineCompany(Base):
|
||||||
|
"""Разрез статьи КФО по компаниям (ЕХ 6.6); составной PK (kfo_result_line_id, legal_entity_id)."""
|
||||||
|
|
||||||
|
__tablename__ = "kfo_result_line_company"
|
||||||
|
|
||||||
|
kfo_result_line_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("kfo_result_line.id"), primary_key=True
|
||||||
|
)
|
||||||
|
legal_entity_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("legal_entity.id"), primary_key=True
|
||||||
|
)
|
||||||
|
amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
share_percent: Mapped[decimal.Decimal | None] = mapped_column(Numeric(7, 4))
|
||||||
|
|
||||||
|
|
||||||
|
class ClientKfoLine(UUIDPrimaryMixin, Base):
|
||||||
|
"""Сравнение с КФО клиента: ручной ввод сумм клиента и расхождение относительно наименьшего (Методика, сн. 14)."""
|
||||||
|
|
||||||
|
__tablename__ = "client_kfo_line"
|
||||||
|
__table_args__ = (
|
||||||
|
Index(
|
||||||
|
"uq_client_kfo_line_version_type_code",
|
||||||
|
"consolidation_version_id",
|
||||||
|
"statement_type",
|
||||||
|
"line_code",
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
consolidation_version_id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("consolidation_version.id", name="fk_client_kfo_line_consolidation_version_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
statement_type: Mapped[StatementType] = mapped_column(pg_enum("statement_type"), nullable=False)
|
||||||
|
line_code: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
client_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
client_vgo_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
bank_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
bank_vgo_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
deviation_amount: Mapped[decimal.Decimal | None] = mapped_column(Numeric(18, 2))
|
||||||
|
deviation_percent: Mapped[decimal.Decimal | None] = mapped_column(Numeric(9, 4))
|
||||||
|
deviation_reason: Mapped[str | None] = mapped_column(Text)
|
||||||
|
comment: Mapped[str | None] = mapped_column(Text)
|
||||||
|
entered_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("app_user.id"))
|
||||||
|
entered_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
Loading…
x
Reference in New Issue
Block a user