Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
231 lines
9.2 KiB
Python
231 lines
9.2 KiB
Python
"""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"),
|
|
)
|