from datetime import datetime from decimal import Decimal from enum import Enum from typing import Optional from pydantic import BaseModel, Field class AccountType(str, Enum): ASSET = "asset" LIABILITY = "liability" EQUITY = "equity" REVENUE = "revenue" EXPENSE = "expense" class JournalEntryFlag(str, Enum): """Transaction status flags (Beancount-style)""" CLEARED = "*" # Fully reconciled/confirmed PENDING = "!" # Not yet confirmed/awaiting approval FLAGGED = "#" # Needs review/attention VOID = "x" # Voided/cancelled entry class Account(BaseModel): id: str name: str account_type: AccountType description: Optional[str] = None user_id: Optional[str] = None # For user-specific accounts created_at: datetime class CreateAccount(BaseModel): name: str account_type: AccountType description: Optional[str] = None user_id: Optional[str] = None class EntryLine(BaseModel): id: str journal_entry_id: str account_id: str debit: int = 0 # in satoshis credit: int = 0 # in satoshis description: Optional[str] = None metadata: dict = {} # Stores currency info: fiat_currency, fiat_amount, fiat_rate, etc. class CreateEntryLine(BaseModel): account_id: str debit: int = 0 credit: int = 0 description: Optional[str] = None metadata: dict = {} # Stores currency info class JournalEntry(BaseModel): id: str description: str entry_date: datetime created_by: str # wallet ID of user who created it created_at: datetime reference: Optional[str] = None # Invoice ID or reference number lines: list[EntryLine] = [] flag: JournalEntryFlag = JournalEntryFlag.CLEARED # Transaction status meta: dict = {} # Metadata: source, tags, links, notes, etc. class CreateJournalEntry(BaseModel): description: str entry_date: Optional[datetime] = None reference: Optional[str] = None lines: list[CreateEntryLine] flag: JournalEntryFlag = JournalEntryFlag.CLEARED meta: dict = {} class UserBalance(BaseModel): user_id: str balance: int # positive = castle owes user, negative = user owes castle accounts: list[Account] = [] fiat_balances: dict[str, Decimal] = {} # e.g. {"EUR": Decimal("250.0"), "USD": Decimal("100.0")} class ExpenseEntry(BaseModel): """Helper model for creating expense entries""" description: str amount: Decimal # Amount in the specified currency (or satoshis if currency is None) expense_account: str # account name or ID is_equity: bool = False # True = equity contribution, False = liability (castle owes user) user_wallet: str reference: Optional[str] = None currency: Optional[str] = None # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.) class ReceivableEntry(BaseModel): """Helper model for creating accounts receivable entries""" description: str amount: Decimal # Amount in the specified currency (or satoshis if currency is None) revenue_account: str # account name or ID user_id: str # The user_id (not wallet_id) of the user who owes the castle reference: Optional[str] = None currency: Optional[str] = None # If None, amount is in satoshis. Otherwise, fiat currency code class RevenueEntry(BaseModel): """Helper model for creating revenue entries""" description: str amount: Decimal # Amount in the specified currency (or satoshis if currency is None) revenue_account: str payment_method_account: str # e.g., "Cash", "Bank", "Lightning" reference: Optional[str] = None currency: Optional[str] = None # If None, amount is in satoshis. Otherwise, fiat currency code class CastleSettings(BaseModel): """Settings for the Castle extension""" castle_wallet_id: Optional[str] = None # The wallet ID that represents the Castle updated_at: datetime = Field(default_factory=lambda: datetime.now()) @classmethod def is_admin_only(cls) -> bool: return True class UserCastleSettings(CastleSettings): """User-specific settings (stored with user_id)""" id: str class UserWalletSettings(BaseModel): """Per-user wallet settings""" user_wallet_id: Optional[str] = None # The wallet ID for this specific user updated_at: datetime = Field(default_factory=lambda: datetime.now()) class StoredUserWalletSettings(UserWalletSettings): """Stored user wallet settings with user ID""" id: str # user_id class ManualPaymentRequest(BaseModel): """Manual payment request from user to castle""" id: str user_id: str amount: int # in satoshis description: str status: str = "pending" # pending, approved, rejected created_at: datetime reviewed_at: Optional[datetime] = None reviewed_by: Optional[str] = None # user_id of castle admin who reviewed journal_entry_id: Optional[str] = None # set when approved class CreateManualPaymentRequest(BaseModel): """Create a manual payment request""" amount: int description: str class GeneratePaymentInvoice(BaseModel): """Generate payment invoice request""" amount: int class RecordPayment(BaseModel): """Record a payment""" payment_hash: str