- Fix default amount showing fiat instead of sats when lightning payment selected
- Fix invoice response field name (bolt11 instead of payment_request)
- Fix NameError in payables/pay endpoint (wallet -> auth.user_id)
- Add get_user_wallet_settings_by_prefix() for truncated 8-char user IDs
- Update user-wallet endpoint to handle truncated IDs from Beancount accounts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Fava /context endpoint returns structured entry data, not raw source
text with slice/sha256sum as expected. Updated both endpoints to:
1. Get entry metadata (filename, lineno) from the parsed entry
2. Read the full source file via GET /source
3. Modify the specific line at the entry's line number
4. Write back via PUT /source with sha256sum for concurrency control
- Approve: Changes flag from '!' to '*' at the entry line
- Reject: Adds #voided tag to the entry line
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add settled_entry_links parameter to format_payment_entry and format_net_settlement_entry
- Query unsettled expenses/receivables before creating settlement entries
- Pass original entry links to format functions so settlements reference what they settle
- Update all callers in views_api.py (5 locations) and tasks.py (1 location)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pass entry_id from views_api.py to format_expense_entry and
format_receivable_entry so that all links use the same ID:
- ^castle-{entry_id}
- ^exp-{entry_id} / ^rcv-{entry_id}
- entry-id metadata
Previously, views_api.py generated an entry_id for castle-* links
but didn't pass it to the format functions, which generated their
own separate IDs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added typing.Optional import that was missing after adding the
report endpoints with optional date parameters.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
New endpoints:
- GET /api/v1/reports/expenses - Expense summary by account or month
- GET /api/v1/reports/contributions - User contribution totals
New FavaClient methods:
- get_expense_summary_bql() - Aggregates expenses with date filtering
- get_user_contributions_bql() - Aggregates user expense submissions
Both use sum(weight) for efficient SATS aggregation from price notation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace sum(position) with sum(weight) for efficient SATS aggregation
from price notation. Also return fiat amount from sum(number).
This simplifies the parsing logic and provides consistent SATS totals
across all BQL-based balance methods.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace inefficient approach that fetched ALL journal entries with
targeted BQL queries that:
- Filter by account pattern and tags in the database
- Use weight column for SATS amounts (no string parsing)
- Query only expense/receivable entries for the specific user
This significantly reduces data transfer and processing overhead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Now that the ledger uses @@ SATS price notation, BQL can efficiently
aggregate SATS balances using the weight column instead of manual
entry-by-entry aggregation.
Changes:
- fava_client.py: Update get_user_balance_bql() and get_all_user_balances_bql()
to use sum(weight) for SATS aggregation (5-10x performance improvement)
- views_api.py: Switch all balance endpoints to use BQL methods
The weight column returns the @@ price value, enabling server-side
filtering and aggregation instead of fetching all entries.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement transaction linking to connect expenses with their settlements,
enabling audit trails and tracking of individual expense reimbursements.
Changes:
- beancount_format.py: Use @@ SATS price notation for BQL queryability,
generate unique ^exp-{id} and ^rcv-{id} links, add #settlement tag
- fava_client.py: Add get_unsettled_entries() to find unlinked expenses
- models.py: Add settled_entry_links field to PayUser/SettleReceivable
- views_api.py: Add GET /users/{id}/unsettled-entries endpoint,
pass settlement links through pay_user and settle_receivable
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enables users to filter transactions by a custom date range, providing more flexibility in viewing transaction history.
Prioritizes custom date range over preset days for filtering.
Displays a warning if a user attempts to apply a custom date range without selecting both start and end dates.
Fixed critical bugs preventing users from seeing accounts through their assigned roles:
1. **Fixed duplicate function definition** (crud.py)
- Removed duplicate auto_assign_default_role() that only took 1 parameter
- Kept correct version with proper signature and logging
- Added get_all_user_roles() helper function
2. **Added role-based permissions to accounts endpoint** (views_api.py)
- Previously only checked direct user permissions
- Now retrieves and combines both direct AND role permissions
- Auto-assigns default role to new users on first access
3. **Fixed permission inheritance logic** (views_api.py)
- Inheritance check now uses combined permissions (direct + role)
- Previously only checked direct user permissions for parents
- Users can now inherit access to child accounts via role permissions
Changes enable proper RBAC functionality:
- Users with "Employee" role (or any role) now see permitted accounts
- Permission inheritance works correctly with role-based permissions
- Auto-assignment of default role on first Castle access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented comprehensive REST API for role-based access control:
Role Management Endpoints (Admin only):
- GET /api/v1/admin/roles - List all roles with user/permission counts
- POST /api/v1/admin/roles - Create new role
- GET /api/v1/admin/roles/{role_id} - Get role details with permissions and users
- PUT /api/v1/admin/roles/{role_id} - Update role (name, description, is_default)
- DELETE /api/v1/admin/roles/{role_id} - Delete role (cascades to permissions/assignments)
Role Permission Endpoints (Admin only):
- POST /api/v1/admin/roles/{role_id}/permissions - Add permission to role
- DELETE /api/v1/admin/roles/{role_id}/permissions/{permission_id} - Remove permission
User Role Assignment Endpoints (Admin only):
- POST /api/v1/admin/user-roles - Assign user to role (with optional expiration)
- GET /api/v1/admin/user-roles/{user_id} - Get user's role assignments
- DELETE /api/v1/admin/user-roles/{user_role_id} - Revoke role assignment
User Endpoints:
- GET /api/v1/users/me/roles - Get current user's roles and effective permissions
(includes both role-based and direct permissions)
All endpoints include:
- Proper error handling with HTTP status codes
- Admin key requirement for management operations
- Rich response data with timestamps and metadata
- Role details enriched with user counts and permission counts
Next: Implement Roles tab UI and JavaScript integration
🤖 Generated with Claude Code
Implemented performance optimization to reduce Fava API load for ledgers
with large transaction histories. Users can now choose to view transactions
from the last 5, 30, 60, or 90 days instead of loading all entries.
Changes:
- Backend (views_api.py): Added 'days' parameter to api_get_user_entries
endpoint with default value of 5 days
- Backend (fava_client.py - previously committed): get_journal_entries
supports optional days parameter with date filtering logic
- Frontend (index.js): Added setTransactionDays() method and days
parameter handling in loadTransactions()
- Frontend (index.html): Added q-btn-toggle UI control for date range
selection visible to all users
Default: 5 days (aggressive optimization for large ledgers)
Options: 5, 30, 60, 90 days
Performance impact: ~10x improvement for typical ledgers (229 entries
reduced to 20-50 entries for 5-day window).
Co-Authored-By: Claude <noreply@anthropic.com>
Performance improvement for large ledgers:
- Added optional 'days' parameter to get_journal_entries()
- User dashboard now fetches only last 30 days of entries
- Dramatically reduces data transfer for ledgers with 100+ entries
- Filters in Python after fetching from Fava API
Example impact: 229 entries → ~20-50 entries (typical 30-day activity)
This is a "quick win" optimization as recommended for accounting systems
with growing transaction history. Admin endpoints still fetch all entries.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two related fixes for account access:
1. **Super user bypass for permission filtering**
- Super users now bypass permission checks and see all accounts
- Fixes issue where Castle system account was blocked from seeing accounts
- Regular users still get filtered by permissions as expected
2. **Show virtual accounts in permissions management UI**
- Permissions page now passes exclude_virtual=false
- Admins need to see virtual accounts to grant permissions on them
- Enables granting permission on 'Expenses:Supplies' to give access to all children
Impact:
- Super user can now create entries and see all accounts ✓
- Admins can grant permissions on virtual parent accounts ✓
- Regular users still only see permitted, non-virtual accounts ✓
- Permission inheritance works correctly for all users ✓
🤖 Generated with Claude Code (https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two critical fixes for user account access:
1. **Permission inheritance for ALL permission types**
- Previously only checked READ permission inheritance
- Now checks ALL permission types (read, submit_expense, manage)
- Fixes issue where users with submit_expense on parent virtual accounts
couldn't see child expense accounts
2. **Virtual account filtering after permission check**
- Virtual accounts are now filtered AFTER permission inheritance logic
- This allows permission inheritance to work correctly for virtual parents
- Virtual accounts are still excluded from final results for users
3. **User-specific account filtering**
- Frontend now passes filter_by_user=true to only show permitted accounts
- Prevents users from seeing accounts they don't have access to
Flow now works correctly:
- Admin grants user submit_expense permission on virtual 'Expenses:Supplies'
- Permission inheritance checks ALL permission types (not just read)
- User sees all 'Expenses:Supplies:*' child accounts (Food, Kitchen, etc.)
- Virtual parent 'Expenses:Supplies' is filtered out from final results
- User only sees real expense accounts they can submit to
Fixes loading hang and empty account list in Add Expense dialog.
🤖 Generated with Claude Code (https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
New Features:
- BulkGrantPermission model: Grant same permission to multiple users
- BulkGrantResult model: Detailed success/failure results
- POST /api/v1/admin/permissions/bulk-grant endpoint
This simplifies the admin workflow for granting the same account
permission to multiple users at once (e.g., onboarding a team).
The endpoint validates the account exists and is active, then grants
the permission to each user, collecting successes and failures to
return a detailed result.
Related: UI-IMPROVEMENTS-PLAN.md Phase 1
Integration Components:
1. Manual API Endpoints (admin-only):
- POST /api/v1/admin/accounts/sync (full sync)
- POST /api/v1/admin/accounts/sync/{account_name} (single account)
2. Scheduled Background Sync:
- Hourly background task (wait_for_account_sync)
- Registered in castle_start() lifecycle
- Automatically syncs new accounts from Beancount to Castle DB
3. Auto-sync on User Account Creation:
- Updated get_or_create_user_account() in crud.py
- Uses sync_single_account_from_beancount() for consistency
- Ensures receivable/payable accounts are synced when users register
Flow:
- User associates wallet → creates receivable/payable in Beancount
→ syncs to Castle DB → permissions can be granted
- Admin manually syncs → all Beancount accounts added to Castle DB
- Hourly task → catches any accounts created directly in Beancount
This ensures Beancount remains the source of truth while Castle DB
maintains metadata for permissions and user associations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Balance assertions now use a hybrid architecture where Beancount is the source
of truth for validation, while Castle stores metadata for UI convenience.
Backend changes:
- Add format_balance() function to beancount_format.py for formatting balance directives
- Update POST /api/v1/assertions to write balance directive to Beancount first (via Fava)
- Store metadata in Castle DB (created_by, tolerance, notes) for UI features
- Validate assertions immediately by querying Fava for actual balance
Frontend changes:
- Update dialog description to explain Beancount validation
- Update button tooltip to clarify balance assertions are written to Beancount
- Update empty state message to mention Beancount checkpoints
Benefits:
- Single source of truth (Beancount ledger file)
- Automatic validation by Beancount
- Best of both worlds: robust validation + friendly UI
See misc-docs/BALANCE-ASSERTIONS-HYBRID-APPROACH.md for full documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhances the journal entries API to include username information.
This is achieved by extracting the user ID from transaction
metadata or account names and retrieving the corresponding
username. A default username is provided if the user is not found.
The pending entries API is updated with the same functionality.
Prioritizes parsing amount strings in the new EUR/USD format and introduces support for metadata containing sats equivalent.
Fallbacks to legacy parsing when the new format is not detected.
This ensures correct interpretation of amount data from different sources.
Ensures that user-provided reference strings for expense,
receivable, and revenue entries are sanitized before being
included as Beancount links. This prevents issues caused by
invalid characters in the links, improving compatibility with
Beancount's link format. A new utility function is introduced
to handle the sanitization process.
Adds a function to sanitize strings for use as Beancount links,
ensuring compatibility with Beancount's link restrictions.
Refactors the journal entry creation process to use EUR-based
postings when fiat currency is provided, improving accuracy
and consistency. The legacy SATS-based fallback is retained for
cases without fiat currency information.
Adjusts reference generation for Beancount entries using the
sanitized description.
Improves the Beancount import process to send SATS amounts with fiat metadata to the API, enabling automatic conversion to EUR-based postings.
Updates the API to store entries in Fava instead of the Castle DB, simplifying the JournalEntry creation process.
Adds error handling to the upload entry function.
Includes a note about imported transactions being stored in EUR with SATS in metadata.
Introduces a function to format fiat settlement entries for Beancount, handling cash, bank transfers, and other non-lightning payments.
This allows for recording transactions in fiat currency with sats as metadata.
Updates the API endpoint to use the new function when settling receivables with fiat currencies.
Improves payment recording logic by fetching recent entries and filtering using Python, replacing the BQL query.
This addresses issues with matching against set types in BQL, enhancing reliability.
Adds support for parsing direct EUR/USD amount strings in the format "37.22 EUR" or "12.34 USD".
It also retrieves the SATS equivalent from the metadata if present, for the new amount format. This ensures compatibility with both the old "SATS {EUR}" format and the newer, direct fiat formats.
Updates the amount parsing logic to support a new format where fiat amounts (EUR/USD) are specified directly.
Adds support for tracking SATS equivalents from metadata when the new format is used.
Also tracks fiat amounts specified in metadata as a fallback for backward compatibility.
Reverses the calculation of net balance to correctly reflect receivables and liabilities.
Refactors user balance calculation to directly parse journal
entries, enhancing accuracy and efficiency. This change
eliminates reliance on direct database queries and provides a
more reliable mechanism for determining user balances.
Adds logging for debugging purposes.
Also extracts and uses fiat metadata from invoice/payment extras.
Ensures unique identification for receivable and
revenue entries by generating a UUID and incorporating
it into a castle reference.
This enhances tracking and linking capabilities by
providing a consistent and easily identifiable
reference across the system.
Updates the pending entries API to correctly parse the amount and fiat values from the amount string, which can now contain both SATS and fiat information.
This change handles different formats of the amount string, including cases where the fiat amount is present within curly braces.
Improves handling of the amount field in user entries by parsing string formats that include both SATS and fiat currency information.
This change allows extracting the SATS amount and fiat amount/currency directly from the string, accommodating different display formats.
Switches to retrieving all journal entries from Fava and filtering in the application to allow filtering by account type and user.
This provides more flexibility and control over the data being presented to the user.
Also extracts and includes relevant metadata such as entry ID, fiat amounts, and references for improved frontend display.
Ensures that voided transactions are not included in the
list of pending entries. This prevents displaying
transactions that have been cancelled or reversed,
providing a more accurate view of truly pending items.
Instead of deleting pending expense entries, marks them as voided by adding a #voided tag.
This ensures an audit trail while excluding them from balances.
Updates the Fava client to use 'params' for the delete request.
Adds functionality to interact with Fava for managing
Beancount entries, including fetching, updating, and
deleting entries directly from the Beancount ledger.
This allows for approving/rejecting pending entries
via the API by modifying the source file through Fava.
The changes include:
- Adds methods to the Fava client for fetching all journal
entries, retrieving entry context (source and hash),
updating the entry source, and deleting entries.
- Updates the pending entries API to use the Fava journal
endpoint instead of querying transactions.
- Implements entry approval and rejection using the new
Fava client methods to modify the underlying Beancount file.
Improves the handling of pending entries by extracting and deduplicating data from Fava's query results.
Adds support for displaying fiat amounts alongside entries and extracts them from the position data in Fava.
Streamlines receivables/payables/equity checks on the frontend by relying on BQL query to supply account type metadata and tags.
Updates journal entry flags to align with Beancount's limited flag support.
Beancount only uses cleared (*) and pending (!) flags.
Removes the VOID and FLAGGED flags and recommends using tags instead
(e.g., "! + #voided" for voided entries, "! + #review" for flagged entries).
Updates the API to reflect this change, removing the ability to directly
"reject" an expense entry via the void flag. Instead, instructs users to
add the #voided tag in Fava.
Updates reconciliation summary to count entries with voided/review tags
instead of voided/flagged flags.
Replaces direct database queries for transactions with calls to the Fava API,
centralizing transaction logic and improving data consistency.
This change removes redundant code and simplifies the API by relying on Fava
for querying transactions based on account patterns and other criteria.
Specifically, the commit introduces new methods in the FavaClient class for
querying transactions, retrieving account transactions, and retrieving user
transactions. The API endpoints are updated to utilize these methods.
Removes direct journal entry creation in favor of using Fava for accounting.
This change centralizes accounting logic in Fava, improving auditability and consistency.
It replaces direct database interactions for recording payments and settlements with calls to the Fava client.
The changes also refactor balance retrieval to fetch data from Fava.
Integrates Fava/Beancount for managing journal entries.
This change introduces functions to format entries into Beancount
format and submit them to a Fava instance.
It replaces the previous direct database entry creation with Fava
submission for expense, receivable, and revenue entries. The existing
create_journal_entry function is also updated to submit generic
journal entries to Fava.
Refactors account balance retrieval to fetch data from Fava/Beancount
for improved accounting accuracy.
Updates user balance retrieval to use Fava/Beancount data source.
Updates Castle settings ledger slug name.
Improvements to equity account handling across the Castle extension:
Transaction Categorization (views_api.py):
- Prioritize equity accounts when enriching transaction entries
- Use two-pass lookup: first search for equity accounts, then fall back to liability/asset accounts
- Ensures transactions with Equity:User-<id> accounts are correctly categorized as equity
UI Enhancements (index.html, index.js):
- Add 'Equity' filter option to Recent Transactions table
- Display blue "Equity" badge for equity entries (before receivable/payable badges)
- Add isEquity() helper function to identify equity account entries
Beancount Import (import_beancount.py):
- Support importing Beancount Equity:<name> accounts
- Map Beancount "Equity:Pat" to Castle "Equity:User-<id>" accounts
- Update extract_user_from_user_account() to handle Equity: prefix
- Improve error messages to include equity account examples
- Add equity account lookup in get_account_id() with helpful error if equity not enabled
These changes ensure equity accounts (representing user capital contributions) are properly distinguished from payables and receivables throughout the system.