"""Часть 08 DBML v5.18 «КФО»: корректировки, гудвилл, результат КФО (шапка, строки, разрез по компаниям), КФО клиента. Корректировки и гудвилл — ручные решения аналитика, хранятся поверх первички (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))