Extends user balance information to include fiat currency balances, calculated based on entry line metadata and account types. This allows for a more comprehensive view of user balances, including both satoshi and fiat currency holdings. Updates the castle index template and API to display fiat balances.
168 lines
4.6 KiB
Python
168 lines
4.6 KiB
Python
from datetime import datetime
|
|
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 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] = []
|
|
|
|
|
|
class CreateJournalEntry(BaseModel):
|
|
description: str
|
|
entry_date: Optional[datetime] = None
|
|
reference: Optional[str] = None
|
|
lines: list[CreateEntryLine]
|
|
|
|
|
|
class UserBalance(BaseModel):
|
|
user_id: str
|
|
balance: int # positive = castle owes user, negative = user owes castle
|
|
accounts: list[Account] = []
|
|
fiat_balances: dict[str, float] = {} # e.g. {"EUR": 250.0, "USD": 100.0}
|
|
|
|
|
|
class ExpenseEntry(BaseModel):
|
|
"""Helper model for creating expense entries"""
|
|
|
|
description: str
|
|
amount: float # 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: float # 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: float # 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
|