castle/CLAUDE.md
padreug 5e67ce562b Adds CLAUDE.md to guide Claude Code
Creates a markdown file to provide guidance to Claude Code (claude.ai/code) when interacting with the Castle Accounting codebase.

This document outlines project overview, architecture, key files, database schema, transaction flows, API endpoints, development notes, and other crucial information. It aims to improve Claude's ability to understand and assist with code-related tasks.
2025-11-01 23:22:25 +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, debit=50000),
CreateEntryLine(account_id=cash_account_id, credit=50000)
],
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