"""Phase 5.5 — Accounts Receivable & Payable domain models.""" from __future__ import annotations import uuid from datetime import date from decimal import Decimal from sqlalchemy import ( Boolean, Date, ForeignKey, Index, Integer, Numeric, String, Text, UniqueConstraint, ) 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 GUID, InvoiceStatus, SettlementStatus class CustomerAccount(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "customer_accounts" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) credit_limit: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True) currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) outstanding_balance: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) invoices: Mapped[list["ReceivableInvoice"]] = relationship(back_populates="customer") __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_customer_tenant_code"), ) class SupplierAccount(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "supplier_accounts" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) outstanding_balance: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) invoices: Mapped[list["PayableInvoice"]] = relationship(back_populates="supplier") __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_supplier_tenant_code"), ) class ReceivableInvoice(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "receivable_invoices" customer_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("customer_accounts.id", ondelete="RESTRICT"), nullable=False ) invoice_number: Mapped[str] = mapped_column(String(50), nullable=False) invoice_date: Mapped[date] = mapped_column(Date, nullable=False) due_date: Mapped[date | None] = mapped_column(Date, nullable=True) total_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) paid_amount: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) status: Mapped[InvoiceStatus] = mapped_column(nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) customer: Mapped["CustomerAccount"] = relationship(back_populates="invoices") __table_args__ = ( UniqueConstraint("tenant_id", "invoice_number", name="uq_receivable_inv_tenant_number"), Index("ix_receivable_inv_customer", "tenant_id", "customer_id"), ) class PayableInvoice(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payable_invoices" supplier_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("supplier_accounts.id", ondelete="RESTRICT"), nullable=False ) invoice_number: Mapped[str] = mapped_column(String(50), nullable=False) invoice_date: Mapped[date] = mapped_column(Date, nullable=False) due_date: Mapped[date | None] = mapped_column(Date, nullable=True) total_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) paid_amount: Mapped[Decimal] = mapped_column( Numeric(18, 4), default=Decimal("0"), nullable=False ) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) status: Mapped[InvoiceStatus] = mapped_column(nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) supplier: Mapped["SupplierAccount"] = relationship(back_populates="invoices") __table_args__ = ( UniqueConstraint("tenant_id", "invoice_number", name="uq_payable_inv_tenant_number"), Index("ix_payable_inv_supplier", "tenant_id", "supplier_id"), ) class Settlement(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "settlements" settlement_number: Mapped[str] = mapped_column(String(50), nullable=False) settlement_date: Mapped[date] = mapped_column(Date, nullable=False) settlement_type: Mapped[str] = mapped_column(String(20), nullable=False) party_type: Mapped[str] = mapped_column(String(20), nullable=False) party_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) total_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) status: Mapped[SettlementStatus] = mapped_column(nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) allocations: Mapped[list["SettlementAllocation"]] = relationship( back_populates="settlement", cascade="all, delete-orphan" ) __table_args__ = ( UniqueConstraint("tenant_id", "settlement_number", name="uq_settlement_tenant_number"), ) class SettlementAllocation(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "settlement_allocations" settlement_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("settlements.id", ondelete="CASCADE"), nullable=False ) invoice_type: Mapped[str] = mapped_column(String(20), nullable=False) invoice_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) allocated_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) settlement: Mapped["Settlement"] = relationship(back_populates="allocations") class CreditNote(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "credit_notes" note_number: Mapped[str] = mapped_column(String(50), nullable=False) note_date: Mapped[date] = mapped_column(Date, nullable=False) party_type: Mapped[str] = mapped_column(String(20), nullable=False) party_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) reason: Mapped[str | None] = mapped_column(Text, nullable=True) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) __table_args__ = ( UniqueConstraint("tenant_id", "note_number", name="uq_credit_note_tenant_number"), ) class DebitNote(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "debit_notes" note_number: Mapped[str] = mapped_column(String(50), nullable=False) note_date: Mapped[date] = mapped_column(Date, nullable=False) party_type: Mapped[str] = mapped_column(String(20), nullable=False) party_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) reason: Mapped[str | None] = mapped_column(Text, nullable=True) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) __table_args__ = ( UniqueConstraint("tenant_id", "note_number", name="uq_debit_note_tenant_number"), ) class AdvancePayment(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "advance_payments" supplier_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) payment_date: Mapped[date] = mapped_column(Date, nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) class AdvanceReceipt(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "advance_receipts" customer_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) receipt_date: Mapped[date] = mapped_column(Date, nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) class CustomerStatement(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "customer_statements" customer_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) statement_date: Mapped[date] = mapped_column(Date, nullable=False) opening_balance: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) closing_balance: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) statement_data: Mapped[str | None] = mapped_column(Text, nullable=True) class SupplierStatement(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "supplier_statements" supplier_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) statement_date: Mapped[date] = mapped_column(Date, nullable=False) opening_balance: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) closing_balance: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) statement_data: Mapped[str | None] = mapped_column(Text, nullable=True) class AgingSnapshot(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "aging_snapshots" party_type: Mapped[str] = mapped_column(String(20), nullable=False) party_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) snapshot_date: Mapped[date] = mapped_column(Date, nullable=False) current_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) days_1_30: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) days_31_60: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) days_61_90: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) days_90_plus: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) total_outstanding: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) __table_args__ = ( Index("ix_aging_snapshot_party", "tenant_id", "party_type", "party_id"), )