"""Phase 5.6 — Sales accounting integration domain models.""" from __future__ import annotations import uuid from datetime import date from decimal import Decimal from sqlalchemy import ( Boolean, Date, Index, Integer, Numeric, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin from app.models.types import GUID, RevenueRecognitionMethod, SalesDocumentType class SalesPostingProfile(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "sales_posting_profiles" name: Mapped[str] = mapped_column(String(255), nullable=False) document_type: Mapped[SalesDocumentType] = mapped_column(nullable=False) revenue_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) receivable_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) cash_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) bank_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) discount_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) tax_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) deferred_revenue_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) rounding_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "name", name="uq_sales_profile_tenant_name"), ) class AccountingProfile(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "accounting_profiles" name: Mapped[str] = mapped_column(String(255), nullable=False) profile_type: Mapped[str] = mapped_column(String(50), nullable=False) entity_type: Mapped[str] = mapped_column(String(50), nullable=False) entity_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) sales_posting_profile_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) config_data: Mapped[str | None] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( Index("ix_accounting_profile_entity", "tenant_id", "entity_type", "entity_id"), ) class PostingRule(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "posting_rules" name: Mapped[str] = mapped_column(String(255), nullable=False) document_type: Mapped[str] = mapped_column(String(50), nullable=False) source_module: Mapped[str] = mapped_column(String(50), nullable=False) debit_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) credit_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) condition_expression: Mapped[str | None] = mapped_column(Text, nullable=True) priority: Mapped[int] = mapped_column(Integer, default=0, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( Index("ix_posting_rules_tenant_module", "tenant_id", "source_module"), ) class SalesAccountingConfiguration(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "sales_accounting_configurations" default_profile_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) auto_post_invoices: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) auto_post_returns: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) default_revenue_recognition: Mapped[RevenueRecognitionMethod] = mapped_column( default=RevenueRecognitionMethod.IMMEDIATE, nullable=False ) __table_args__ = ( UniqueConstraint("tenant_id", name="uq_sales_accounting_config_tenant"), ) class RevenueRecognition(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "revenue_recognitions" source_document_type: Mapped[str] = mapped_column(String(50), nullable=False) source_document_id: Mapped[str] = mapped_column(String(100), nullable=False) method: Mapped[RevenueRecognitionMethod] = mapped_column(nullable=False) total_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) recognized_amount: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) deferred_amount: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) recognition_date: Mapped[date | None] = mapped_column(Date, nullable=True) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) __table_args__ = ( Index("ix_revenue_recognition_source", "tenant_id", "source_document_type", "source_document_id"), ) class RevenueSchedule(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "revenue_schedules" revenue_recognition_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) schedule_date: Mapped[date] = mapped_column(Date, nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) is_recognized: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) class AccountingPreview(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "accounting_previews" source_module: Mapped[str] = mapped_column(String(50), nullable=False) source_document_type: Mapped[str] = mapped_column(String(50), nullable=False) source_document_id: Mapped[str] = mapped_column(String(100), nullable=False) preview_data: Mapped[str] = mapped_column(Text, nullable=False) is_balanced: Mapped[bool] = mapped_column(Boolean, nullable=False) total_debit: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) total_credit: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) class PostingSimulation(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "posting_simulations" source_module: Mapped[str] = mapped_column(String(50), nullable=False) simulation_data: Mapped[str] = mapped_column(Text, nullable=False) result_data: Mapped[str | None] = mapped_column(Text, nullable=True) is_successful: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) error_message: Mapped[str | None] = mapped_column(Text, nullable=True)