castle/CLAUDE.md
padreug 4ae6a8f7d2 Refactors journal entry lines to use single amount
Simplifies the representation of journal entry lines by replacing separate debit and credit fields with a single 'amount' field.

Positive amounts represent debits, while negative amounts represent credits, aligning with Beancount's approach. This change improves code readability and simplifies calculations for balancing entries.
2025-11-08 11:48:08 +01:00

280 lines
11 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Castle Accounting is a double-entry bookkeeping extension for LNbits that enables collectives (co-living spaces, makerspaces, community projects) to track finances with proper accounting principles. It integrates Lightning Network payments with traditional accounting, supporting both cryptocurrency and fiat currency tracking.
## Architecture
### Core Design Principles
**Double-Entry Accounting**: Every transaction affects at least two accounts. Debits must equal credits. Five account types: Assets, Liabilities, Equity, Revenue (Income), Expenses.
**Pure Functional Core**: The `core/` directory contains pure accounting logic independent of the database layer:
- `core/balance.py` - Balance calculation from journal entries
- `core/inventory.py` - Multi-currency position tracking (similar to Beancount's Inventory)
- `core/validation.py` - Entry validation rules
**Account Hierarchy**: Beancount-style hierarchical naming with `:` separators:
- `Assets:Lightning:Balance`
- `Assets:Receivable:User-af983632`
- `Liabilities:Payable:User-af983632`
- `Expenses:Food:Supplies`
**Metadata System**: Each `entry_line` stores JSON metadata preserving original fiat amounts. Critical: fiat balances are calculated by summing `fiat_amount` from metadata, NOT by converting current satoshi balances. This prevents exchange rate fluctuations from affecting historical records.
### Key Files
- `models.py` - Pydantic models for API I/O and data structures
- `crud.py` - Database operations (create/read/update accounts, journal entries)
- `views_api.py` - FastAPI endpoints for all operations
- `views.py` - Web interface routing
- `services.py` - Settings management layer
- `migrations.py` - Database schema migrations
- `tasks.py` - Background tasks (daily reconciliation checks)
- `account_utils.py` - Hierarchical account naming utilities
### Database Schema
**accounts**: Chart of accounts with hierarchical names
- `user_id` field for per-user accounts (Receivable, Payable, Equity)
- Indexed on `user_id` and `account_type`
**journal_entries**: Transaction headers
- `flag` field: `*` (cleared), `!` (pending), `#` (flagged), `x` (void)
- `meta` field: JSON storing source, tags, audit info
- `reference` field: Links to payment_hash, invoice numbers, etc.
**entry_lines**: Individual debit/credit lines
- Always balanced (sum of debits = sum of credits per entry)
- `metadata` field stores fiat currency info as JSON
- Indexed on `journal_entry_id` and `account_id`
**balance_assertions**: Reconciliation checkpoints (Beancount-style)
- Assert expected balance at a date
- Status: pending, passed, failed
- Used for daily reconciliation checks
**extension_settings**: Castle wallet configuration (admin-only)
**user_wallet_settings**: Per-user wallet configuration
**manual_payment_requests**: User requests for cash/manual payments
## Transaction Flows
### User Adds Expense (Liability)
User pays cash for groceries, Castle owes them:
```
DR Expenses:Food 39,669 sats
CR Liabilities:Payable:User-af983632 39,669 sats
```
Metadata preserves: `{"fiat_currency": "EUR", "fiat_amount": "36.93", "fiat_rate": "1074.192"}`
### Castle Adds Receivable
User owes Castle for accommodation:
```
DR Assets:Receivable:User-af983632 268,548 sats
CR Income:Accommodation 268,548 sats
```
### User Pays with Lightning
Invoice generated on **Castle's wallet** (not user's). After payment:
```
DR Assets:Lightning:Balance 268,548 sats
CR Assets:Receivable:User-af983632 268,548 sats
```
### Manual Payment Approval
User requests cash payment → Admin approves → Journal entry created:
```
DR Liabilities:Payable:User-af983632 39,669 sats
CR Assets:Lightning:Balance 39,669 sats
```
## Balance Calculation Logic
**User Balance**:
- Positive = Castle owes user (LIABILITY accounts have credit balance)
- Negative = User owes Castle (ASSET accounts have debit balance)
- Calculated from sum of all entry lines across user's accounts
- Fiat balances summed from metadata, NOT converted from sats
**Perspective-Based UI**:
- **User View**: Green = Castle owes them, Red = They owe Castle
- **Castle Admin View**: Green = User owes Castle, Red = Castle owes user
## API Endpoints
### Accounts
- `GET /api/v1/accounts` - List all accounts
- `POST /api/v1/accounts` - Create account (admin)
- `GET /api/v1/accounts/{id}/balance` - Get account balance
### Journal Entries
- `POST /api/v1/entries/expense` - User adds expense (creates liability or equity)
- `POST /api/v1/entries/receivable` - Admin records what user owes (admin only)
- `POST /api/v1/entries/revenue` - Admin records direct revenue (admin only)
- `GET /api/v1/entries/user` - Get user's journal entries
- `POST /api/v1/entries` - Create raw journal entry (admin only)
### Payments & Balances
- `GET /api/v1/balance` - Get user balance (or Castle total if super user)
- `GET /api/v1/balances/all` - Get all user balances (admin, enriched with usernames)
- `POST /api/v1/generate-payment-invoice` - Generate invoice for user to pay Castle
- `POST /api/v1/record-payment` - Record Lightning payment from user to Castle
- `POST /api/v1/settle-receivable` - Manually settle receivable (cash/bank)
- `POST /api/v1/pay-user` - Castle pays user (cash/bank/lightning)
### Manual Payment Requests
- `POST /api/v1/manual-payment-requests` - User requests payment
- `GET /api/v1/manual-payment-requests` - User's requests
- `GET /api/v1/manual-payment-requests/all` - All requests (admin)
- `POST /api/v1/manual-payment-requests/{id}/approve` - Approve (admin)
- `POST /api/v1/manual-payment-requests/{id}/reject` - Reject (admin)
### Reconciliation
- `POST /api/v1/assertions/balance` - Create balance assertion
- `GET /api/v1/assertions/balance` - List balance assertions
- `POST /api/v1/assertions/balance/{id}/check` - Check assertion
- `POST /api/v1/tasks/daily-reconciliation` - Run daily reconciliation (admin)
### Settings
- `GET /api/v1/settings` - Get Castle settings (super user)
- `PUT /api/v1/settings` - Update Castle settings (super user)
- `GET /api/v1/user/wallet` - Get user wallet settings
- `PUT /api/v1/user/wallet` - Update user wallet settings
## Development Notes
### Testing Entry Creation
When creating journal entries programmatically, use the helper endpoints:
- `POST /api/v1/entries/expense` for user expenses (handles account creation automatically)
- `POST /api/v1/entries/receivable` for what users owe
- `POST /api/v1/entries/revenue` for direct revenue
For custom entries, use `POST /api/v1/entries` with properly balanced lines.
### User Account Management
User-specific accounts are created automatically with format:
- Assets: `Assets:Receivable:User-{user_id[:8]}`
- Liabilities: `Liabilities:Payable:User-{user_id[:8]}`
- Equity: `Equity:MemberEquity:User-{user_id[:8]}`
Use `get_or_create_user_account()` in crud.py to ensure consistency.
### Currency Handling
**CRITICAL**: Use `Decimal` for all fiat amounts, never `float`. Fiat amounts are stored in metadata as strings to preserve precision:
```python
from decimal import Decimal
metadata = {
"fiat_currency": "EUR",
"fiat_amount": str(Decimal("250.00")),
"fiat_rate": str(Decimal("1074.192")),
"btc_rate": str(Decimal("0.000931"))
}
```
When reading: `fiat_amount = Decimal(metadata["fiat_amount"])`
### Balance Assertions for Reconciliation
Create balance assertions to verify accounting accuracy:
```python
await create_balance_assertion(
account_id="lightning_account_id",
expected_balance_sats=1000000,
expected_balance_fiat=Decimal("500.00"),
fiat_currency="EUR",
tolerance_sats=100
)
```
Run `POST /api/v1/tasks/daily-reconciliation` to check all assertions.
### Permission Model
- **Super User**: Full access (check via `wallet.wallet.user == lnbits_settings.super_user`)
- **Admin Key**: Required for creating receivables, approving payments, viewing all balances
- **Invoice Key**: Read access to user's own data
- **Users**: Can only see/manage their own accounts and transactions
### Extension as LNbits Module
This extension follows LNbits extension structure:
- Registered via `castle_ext` router in `__init__.py`
- Static files served from `static/` directory
- Templates in `templates/castle/`
- Database accessed via `db = Database("ext_castle")`
## Common Tasks
### Add New Expense Account
```python
await create_account(CreateAccount(
name="Expenses:Internet",
account_type=AccountType.EXPENSE,
description="Internet service costs"
))
```
### Manually Record Cash Payment
```python
await create_journal_entry(CreateJournalEntry(
description="Cash payment for groceries",
lines=[
CreateEntryLine(account_id=expense_account_id, amount=50000), # Positive = debit (expense increase)
CreateEntryLine(account_id=cash_account_id, amount=-50000) # Negative = credit (asset decrease)
],
flag=JournalEntryFlag.CLEARED,
meta={"source": "manual", "payment_method": "cash"}
))
```
### Check User Balance
```python
balance = await get_user_balance(user_id)
print(f"Sats: {balance.balance}") # Positive = Castle owes user
print(f"Fiat: {balance.fiat_balances}") # {"EUR": Decimal("36.93")}
```
### Export to Beancount (Future)
Follow patterns in `docs/BEANCOUNT_PATTERNS.md` for implementing Beancount export. Use hierarchical account names and preserve metadata in Beancount comments.
## Data Integrity
**Critical Invariants**:
1. Every journal entry MUST have balanced debits and credits
2. Fiat balances calculated from metadata, not from converting sats
3. User accounts use `user_id` (NOT `wallet_id`) for consistency
4. Balance assertions checked daily via background task
**Validation** is performed in `core/validation.py`:
- `validate_journal_entry()` - Checks balance, minimum lines
- `validate_balance()` - Verifies account balance calculation
- `validate_receivable_entry()` - Ensures receivable entries are valid
- `validate_expense_entry()` - Ensures expense entries are valid
## Known Issues & Future Work
See `docs/DOCUMENTATION.md` for comprehensive list. Key items:
- No journal entry editing/deletion (use reversing entries)
- No date range filtering on list endpoints (hardcoded limit of 100)
- No batch operations for bulk imports
- Plugin system architecture designed but not implemented
- Beancount export endpoint not yet implemented
## Related Documentation
- `docs/README.md` - User-facing overview
- `docs/DOCUMENTATION.md` - Comprehensive technical documentation
- `docs/BEANCOUNT_PATTERNS.md` - Beancount-inspired design patterns
- `docs/PHASE1_COMPLETE.md`, `PHASE2_COMPLETE.md`, `PHASE3_COMPLETE.md` - Development milestones
- `docs/EXPENSE_APPROVAL.md` - Manual payment request workflow
- `docs/DAILY_RECONCILIATION.md` - Automated reconciliation system