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:
Andrey Epifancev 2026-10-03 19:32:48 +04:00
parent 842bb584ee
commit 48525731ff
4 changed files with 515 additions and 1 deletions

View File

@ -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
View 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)

View 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
View 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))