"""Phase 5.1 — Accounting foundation domain models.""" from __future__ import annotations import uuid from datetime import date, datetime from decimal import Decimal from sqlalchemy import ( Boolean, Date, DateTime, ForeignKey, Index, Integer, Numeric, String, Text, UniqueConstraint, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin from app.models.types import ( AccountCategory, AccountStatus, AccountType, FiscalPeriodStatus, GUID, ) class ChartOfAccounts(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "chart_of_accounts" name: Mapped[str] = mapped_column(String(255), nullable=False) code: Mapped[str] = mapped_column(String(50), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) accounts: Mapped[list["Account"]] = relationship(back_populates="chart") __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_coa_tenant_code"), ) class Account(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "accounts" chart_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("chart_of_accounts.id", ondelete="CASCADE"), nullable=False ) parent_id: Mapped[uuid.UUID | None] = mapped_column( GUID(), ForeignKey("accounts.id", ondelete="SET NULL"), nullable=True ) code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) account_type: Mapped[AccountType] = mapped_column(nullable=False) account_category: Mapped[AccountCategory] = mapped_column(nullable=False) level: Mapped[int] = mapped_column(Integer, default=1, nullable=False) is_group: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_postable: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) status: Mapped[AccountStatus] = mapped_column( default=AccountStatus.ACTIVE, nullable=False ) currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) chart: Mapped["ChartOfAccounts"] = relationship(back_populates="accounts") parent: Mapped["Account | None"] = relationship(remote_side="Account.id") __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_account_tenant_code"), Index("ix_accounts_chart_id", "chart_id"), ) class Currency(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "currencies" code: Mapped[str] = mapped_column(String(3), nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=False) symbol: Mapped[str | None] = mapped_column(String(10), nullable=True) decimal_places: Mapped[int] = mapped_column(Integer, default=2, nullable=False) is_base: 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", "code", name="uq_currency_tenant_code"), ) class ExchangeRate(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "exchange_rates" from_currency_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) to_currency_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) rate: Mapped[Decimal] = mapped_column(Numeric(18, 8), nullable=False) effective_date: Mapped[date] = mapped_column(Date, nullable=False) __table_args__ = ( Index("ix_exchange_rates_tenant_date", "tenant_id", "effective_date"), ) class FiscalYear(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "fiscal_years" name: Mapped[str] = mapped_column(String(100), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) is_current: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_closed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) periods: Mapped[list["FiscalPeriod"]] = relationship(back_populates="fiscal_year") __table_args__ = ( UniqueConstraint("tenant_id", "name", name="uq_fiscal_year_tenant_name"), ) class FiscalPeriod(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "fiscal_periods" fiscal_year_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("fiscal_years.id", ondelete="CASCADE"), nullable=False ) name: Mapped[str] = mapped_column(String(100), nullable=False) period_number: Mapped[int] = mapped_column(Integer, nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) status: Mapped[FiscalPeriodStatus] = mapped_column( default=FiscalPeriodStatus.OPEN, nullable=False ) is_current: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) fiscal_year: Mapped["FiscalYear"] = relationship(back_populates="periods") __table_args__ = ( UniqueConstraint( "tenant_id", "fiscal_year_id", "period_number", name="uq_fiscal_period_tenant_year_num", ), Index("ix_fiscal_periods_tenant_status", "tenant_id", "status"), ) class CostCenter(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "cost_centers" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) parent_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_cost_center_tenant_code"), ) class Project(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "projects" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) start_date: Mapped[date | None] = mapped_column(Date, nullable=True) end_date: Mapped[date | None] = mapped_column(Date, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_project_tenant_code"), ) class Dimension(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "dimensions" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) dimension_type: Mapped[str] = mapped_column(String(50), nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_dimension_tenant_code"), ) class AccountingSettings(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "accounting_settings" key: Mapped[str] = mapped_column(String(100), nullable=False) value: Mapped[str | None] = mapped_column(Text, nullable=True) value_type: Mapped[str] = mapped_column(String(20), default="string", nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "key", name="uq_accounting_settings_tenant_key"), ) class DocumentNumberSequence(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "document_number_sequences" document_type: Mapped[str] = mapped_column(String(50), nullable=False) prefix: Mapped[str | None] = mapped_column(String(20), nullable=True) next_number: Mapped[int] = mapped_column(Integer, default=1, nullable=False) padding: Mapped[int] = mapped_column(Integer, default=6, nullable=False) __table_args__ = ( UniqueConstraint( "tenant_id", "document_type", name="uq_doc_seq_tenant_type" ), ) class TenantAccountingConfiguration(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "tenant_accounting_configurations" base_currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) default_chart_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) fiscal_year_start_month: Mapped[int] = mapped_column(Integer, default=1, nullable=False) allow_multi_currency: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) require_cost_center: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) require_project: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", name="uq_tenant_accounting_config_tenant"), )