Commit graph

213 commits

Author SHA1 Message Date
deeec7e2c5 Add lifetime income/expense totals to UserBalance v0.2.1
New get_user_lifetime_totals_bql() runs tag-filtered BQL queries
(Payable + expense-entry, Receivable + income-entry) to compute
per-user lifetime totals separately from the net balance. Plumbed
through /api/v1/balance and /api/v1/balance/{user_id}; existing
clients keep working (fields default to zero / empty dict).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:06:16 +02:00
483e89163e Tag each pending approval row with an INCOME / EXPENSE badge v0.2.0
With both kinds of entry sharing the Pending Approvals list, the row
alone didn't tell the reviewer which direction the accounting goes.
Adds a small green INCOME / red EXPENSE badge as a caption line above
the description (so it doesn't compete with description wrapping),
driven by an isIncomeEntry(entry) helper that reads the Beancount tag
set the API already returns. Also drops the now-redundant orange
pending-icon avatar — the card title already says these are pending,
and the badge does the heavier lifting.
2026-05-17 15:26:34 +02:00
6a110545e2 Rename Pending Approvals card from "Pending Expense Approvals"
Both expense and income entries land in the same pending list (the
backend's /entries/pending endpoint already returns all pending
transactions regardless of type, and approve/reject is type-agnostic),
so the expense-specific title was misleading once income approval
shipped in #13.
2026-05-17 15:21:46 +02:00
1edd126a43 Reset role permission/user state when closing the role dialog
closeViewRoleDialog already clears rolePermissionsForView and
roleUsersForView; closeRoleDialog (used by both Edit and Create flows)
did not. With editRole now populating those arrays, leftover state
would otherwise survive a close → open-Create round trip. The Create
template branch doesn't read the arrays today (v-if guarded on
editingRole), so this is defensive — keeps the two close handlers
symmetrical and avoids future regressions if the Create branch ever
starts referencing them.
2026-05-17 13:52:50 +02:00
55f8249f2c Load role permissions when opening the Edit Role dialog
The dialog reads from rolePermissionsForView / roleUsersForView, but
editRole(role) only ever populated the form fields and showed the
dialog — those arrays were left at whatever state the rest of the page
had set them to. Result: opening Edit Role for a role with existing
permissions showed "No permissions assigned to this role yet", and the
list only "appeared" because adding a permission triggered a refresh.

Mirror viewRole's pattern: clear both arrays, GET /admin/roles/{id},
populate from the response, then show the dialog after $nextTick.

Closes #14
2026-05-17 13:52:36 +02:00
0f2a38ee7f Record income receipts as a user receivable, not an entity asset
When a user submits income, the money is physically in *their* pocket,
not the entity's cash drawer. The original income endpoint posted DR
on a configurable payment-method asset account (Cash/Bank/Lightning),
which implicitly assumed the entity already had the funds.

Mirror the expense flow instead: DR Assets:Receivable:User-{id[:8]}
(via get_or_create_user_account), CR the revenue account. The user
now owes the entity until they hand the cash over via the existing
/settle-receivable workflow. With this, the per-user Outstanding
Balances card correctly nets expenses (entity owes user, -liability)
against income receipts (user owes entity, +receivable).

Drops payment_method_account from IncomeEntry — no longer needed.
2026-05-16 23:40:08 +02:00
61952d0015 Expose SUBMIT_INCOME in permission management UI
Adds the new permission type to the grant/bulk-grant dialog dropdown
(static/js/permissions.js) so admins can grant 'Submit Income' on
revenue accounts the same way they grant 'Submit Expense' on expense
accounts. Without this, the backend's SUBMIT_INCOME check on the new
income endpoint is ungranted-able from the UI and users see a 403.

Uses 'teal' + the 'payments' icon to distinguish income-grant badges
from green-and-add_circle expense-grant badges in the role/account
permission lists. Also updates a stale comment in migrations.py
listing the valid permission_type values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:55:28 +02:00
93b5c2677c Add user-facing income/revenue submission endpoint
Mirrors the existing expense submission flow so non-admin users can log
income on behalf of the organization for super-user review. New endpoint
POST /api/v1/entries/income takes invoice-key auth, creates a Beancount
transaction with the pending '!' flag, and reuses the existing
/entries/{id}/approve and /reject endpoints (which match by libra-{id}
link regardless of entry type).

Adds PermissionType.SUBMIT_INCOME granted on revenue accounts (parallel
to SUBMIT_EXPENSE on expense accounts) rather than overloading
SUBMIT_EXPENSE — the two operations target distinct account types and
should be grantable independently. Enforces AccountType.REVENUE on the
income account and AccountType.ASSET on the payment-method account;
fiat currency is required (matches the expense flow's effective
requirement). Income entries get a 'income-entry' tag and an
^inc-{entry_id} link for tracking, and surface in the existing
/entries/pending list for super-user approval.

UI work lives in the standalone webapp, out of scope here.

Closes #9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:01:47 +02:00
4085280711 Filter synthetic Beancount entries from journal listings
The Recent Transactions card was showing Beancount-generated opening-balance
entries from ledger summarization (flag 'S'). Adds a _SYNTHETIC_FLAGS set
mirroring Fava's _EXCL_FLAGS (S/T/C/P/U/R/M) and skips matching entries in
the two user-facing endpoints that previously only filtered by transaction
type. Other journal callers already filter by flag '!' so are unaffected.

Closes #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:43:32 +02:00
b2b2c109a4 Clean up awkward "the Libra" phrasing left over from rename
Replaces entity-sense references to "the Libra" with "the
organization"/"the collective" where Libra was being used as a
stand-in for the original "Castle" entity, and drops the redundant
"(like cooperatives)" parenthetical in DOCUMENTATION.md. Also swaps
the 🏰 emoji in the import helper for ⚖️.

Closes #12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:37:30 +02:00
c174cda48d Rename Castle Accounting extension to Libra v0.1.0
Full identifier rename: module path lnbits.extensions.castle →
lnbits.extensions.libra, DB ext_castle → ext_libra, URL prefix
/castle/ → /libra/, manifest id castle → libra, fava ledger slug
default castle-ledger → libra-ledger, Beancount source metadata
castle-api → libra-api and link prefixes castle-{entry,tx}- →
libra-{entry,tx}-, column castle_wallet_id → libra_wallet_id, all
Python/JS/HTML identifiers (castle_ext, CastleSettings,
castle_reference, castleWalletConfigured, etc.).

Display name "Castle Accounting" → "Libra" (the scales/balance
metaphor — fits double-entry bookkeeping).

No backward compat: production hosts will be force-updated. Old
castle-prefixed Beancount metadata in existing Fava ledgers is
historical; new entries use libra-* prefixes going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 10:24:46 +02:00
9c577c740c Add account sync button to super user toolbar v0.0.9
Wires the existing POST /api/v1/admin/accounts/sync endpoint into the
Castle index toolbar (sync icon between permissions and settings).
Surfaces sync stats (added/reactivated/deactivated/virtual_parents/errors)
via a Quasar notification and refreshes the accounts list on success.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:51:16 +02:00
9a1893c546 Fix startup: load Fava settings from DB instead of hardcoded defaults v0.0.8
castle_start() was using CastleSettings() defaults (slug=castle-ledger)
instead of reading the saved settings from the database. This caused all
Fava queries to 404 on instances where the ledger slug differs from the
default (e.g. demo-ledger).

Now loads settings from extension_settings table at startup, falling
back to defaults only if no saved settings exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 19:08:13 +02:00
f2f9183106 Fix get_all_accounts to discover accounts from open directives v0.0.7
The previous BQL query (SELECT DISTINCT account) only returned accounts
with postings, missing all accounts that were opened but had no
transactions yet. On a fresh ledger this returned 0 accounts, causing
the account sync to deactivate everything.

Now uses Fava's balance_sheet and income_statement API endpoints which
return the full account tree including zero-balance accounts. Falls back
to BQL if the tree endpoints fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 18:54:54 +02:00
Patrick Mulligan
b06c53c40f Fix BQL balance queries mixing EUR and SATS face values v0.0.6
The BQL queries in get_user_balance_bql() and get_all_user_balances_bql()
used GROUP BY account without currency, causing sum(number) to add EUR
face values from expense entries (EUR @@ SATS notation) with SATS face
values from payment entries (plain SATS). This inflated displayed fiat
amounts by orders of magnitude for users with settlement payments.

Fix: add currency to GROUP BY so EUR and SATS rows are separate, use
sum(weight) for net SATS (correct across all entry formats), and scale
fiat proportionally for partial settlements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:43:38 -04:00
Patrick Mulligan
c4f784360d Fix Pay User lightning payment bugs v0.0.5
- 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>
2026-01-19 07:17:12 -05:00
8d9e14ee5a Fix approve/reject endpoints to use Fava source API correctly v0.0.4
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>
2026-01-07 15:47:09 +01:00
cb9bc2d658 Add Fava settings UI and fix race conditions in toolbar buttons
- Add Fava URL, ledger slug, and timeout settings to super admin Settings dialog
- Reinitialize Fava client when settings are updated via services.py
- Add settingsLoaded flag to prevent race conditions where wrong toolbar
  buttons appeared before isSuperUser was determined
- Remove premature Vue mount() call from permissions.js that caused
  "Cannot read properties of undefined (reading 'user')" error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 15:24:19 +01:00
5eb007b936 Merge branch 'fix/authorization-security-refactor' 2026-01-07 13:37:54 +01:00
ca0cee7312 Add centralized authorization module and fix security vulnerabilities
- Create auth.py with AuthContext, require_super_user, require_authenticated
- Fix 6 CRITICAL unprotected endpoints exposing sensitive data
- Consolidate 16+ admin endpoints with duplicated super_user checks
- Standardize on user_id (wallet.wallet.user) instead of wallet_id

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 13:35:07 +01:00
b5c36504fb Add concurrency protection for Fava/Beancount ledger writes
This commit addresses critical race conditions when multiple requests
try to write to the ledger file simultaneously.

Changes:
- Add global asyncio.Lock to FavaClient to serialize all write operations
- Add per-user locks for finer-grained concurrency control
- Wrap add_entry(), update_entry_source(), delete_entry() with write lock
- Add retry logic with exponential backoff to add_account() for checksum conflicts
- Add new add_entry_idempotent() method to prevent duplicate entries
- Add ChecksumConflictError exception for conflict handling
- Update on_invoice_paid() to use per-user locking and idempotent entry creation

Fixes #4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:57:03 +01:00
Patrick Mulligan
e403ec223d Add settlement links to payment entries for traceability v0.0.3
- 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>
2026-01-02 19:34:25 +01:00
Patrick Mulligan
da74e668c8 Fix entry_id mismatch between castle links and exp/rcv links
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>
2026-01-02 18:34:45 +01:00
Patrick Mulligan
69ce1d9601 Fix Fava API endpoint for getting entry context v0.0.2
Use GET /api/context instead of GET /api/source_slice. Fava's API
naming convention means source_slice only supports PUT and DELETE,
while context is the correct endpoint for reading entry data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 18:23:18 +01:00
f8af54f90b Include both expense and receivable links in net settlements v0.0.1
Both settlement dialogs now fetch BOTH expense and receivable entries
to properly link all entries being netted in a settlement.

This ensures that when a user has:
- 2 expenses (80 EUR - castle owes user)
- 1 receivable (1000 EUR - user owes castle)

The net settlement (920 EUR) includes links to all three entries:
^exp-xxx ^exp-xxx ^rcv-xxx

This allows proper tracking of which specific entries were settled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 01:40:12 +01:00
7362d6292e Fix settlement linking to original expense/receivable entries
The frontend now:
1. Fetches unsettled entries when opening settlement dialogs
2. Includes entry links (exp-xxx/rcv-xxx) in settlement payloads
3. Passes settled_entry_links to backend for proper linking

This enables the settlement transaction to include links back to
the original entries it is settling, making it possible to track
which expenses/receivables have been paid.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 01:33:35 +01:00
1ae5c8c927 Fix missing Optional import in views_api.py
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>
2025-12-15 01:21:44 +01:00
7dabe8700d Add BQL-based report endpoints for expenses and contributions
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>
2025-12-15 01:15:29 +01:00
addf4cd05f Optimize get_journal_entries with server-side date filtering
Use Fava's 'time' query parameter to filter entries on the server
instead of fetching all entries and filtering in Python.

This reduces:
- Data transfer (only relevant entries are sent)
- Memory usage (no need to hold all entries)
- Processing time (no Python-side date parsing/filtering)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 01:12:33 +01:00
49d18c3e73 Update get_account_balance to use sum(weight) for SATS
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>
2025-12-15 01:10:46 +01:00
048d19f90b Add BQL-optimized get_unsettled_entries_bql method
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>
2025-12-15 01:08:38 +01:00
55df2b36e0 Fix Pay User dialog showing negative values
Use Math.abs() to display liability amounts as positive values in the
Pay User dialog. Liabilities are stored as negative (castle owes user)
but should display as positive when framed as "Amount Castle Owes".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 00:54:51 +01:00
116355b502 Fix get_entry_context to use /source_slice endpoint
The /context endpoint returns entry metadata but not the editable source.
The /source_slice endpoint returns the actual source text and sha256sum
needed for approving/rejecting entries.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 00:35:22 +01:00
913e4705b1 Fix amount parsing to handle both @ and @@ SATS notation
Pending expense entries use per-unit price notation (@ SATS) while
migrated entries use total price notation (@@ SATS).

Formats handled:
- "50.00 EUR @@ 50000 SATS" - total price (multiply = amount)
- "50.00 EUR @ 1000.5 SATS" - per-unit price (multiply for total)
- "50.00 EUR" with metadata - legacy format
- "50000 SATS" - old SATS-first format

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 00:19:49 +01:00
7173e051fe Use BQL sum(weight) for efficient SATS balance queries
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>
2025-12-14 23:58:56 +01:00
dfdcc441a1 Add expense-to-settlement linking with price notation
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>
2025-12-14 23:40:33 +01:00
df00def8d8 add package.json 2025-12-14 12:58:33 +01:00
862fe0bfad Add Docs 2025-12-14 12:47:34 +01:00
1d2eb05c36 Adds custom date range filtering to transactions
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.
2025-12-14 12:47:23 +01:00
f2df2f543b Enhance RBAC user management UI and fix permission checks
- Add role management to "By User" tab
  - Show all users with roles and/or direct permissions
  - Add ability to assign/revoke roles from users
  - Display role chips as clickable and removable
  - Add "Assign Role" button for each user

- Fix account_id validation error in permission granting
  - Extract account_id string from Quasar q-select object
  - Apply fix to grantPermission, bulkGrantPermissions, and addRolePermission

- Fix role-based permission checking for expense submission
  - Update get_user_permissions_with_inheritance() to include role permissions
  - Ensures users with role-based permissions can submit expenses

- Improve Vue reactivity for role details dialog
  - Use spread operator to create fresh arrays
  - Add $nextTick() before showing dialog

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 10:17:28 +01:00
52c6c3f8f1 Fix RBAC role-based permissions for accounts endpoint
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>
2025-11-12 03:00:17 +01:00
c086916be8 Add RBAC API endpoints - Phase 2A
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
2025-11-11 23:47:13 +01:00
46e910ba25 Add RBAC (Role-Based Access Control) system - Phase 1
Implemented comprehensive role-based permission management system:

Database:
- Added m004_add_rbac_tables migration
- roles table: Define named permission bundles (Employee, Contractor, etc.)
- role_permissions table: Map roles to account permissions
- user_roles table: Assign users to roles with optional expiration
- Created 4 default roles: Employee (default), Contractor, Accountant, Manager

Models (models.py):
- Role, CreateRole, UpdateRole
- RolePermission, CreateRolePermission
- UserRole, AssignUserRole
- RoleWithPermissions, UserWithRoles

CRUD Operations (crud.py):
- Role management: create_role, get_role, get_all_roles, update_role, delete_role
- get_default_role() - get auto-assigned role for new users
- Role permissions: create_role_permission, get_role_permissions, delete_role_permission
- User role assignment: assign_user_role, get_user_roles, revoke_user_role
- Helper functions:
  - get_user_permissions_from_roles() - resolve user permissions via roles
  - check_user_has_role_permission() - check role-based access
  - auto_assign_default_role() - auto-assign default role to new users

Permission Resolution Order:
1. Individual account_permissions (direct grants/exceptions)
2. Role-based permissions (via user_roles → role_permissions)
3. Inherited permissions (hierarchical account names)
4. Deny by default

Next: API endpoints, UI, and permission resolution logic integration

🤖 Generated with Claude Code
2025-11-11 23:34:28 +01:00
142b26d7da Set default permission type to 'submit_expense' in grant forms
Changed default permission type from 'read' to 'submit_expense' in
all permission grant forms, as this is the most common use case when
Castle admins grant permissions to users.

Changes:
- grantForm initialization (line 31): 'read' → 'submit_expense'
- bulkGrantForm initialization (line 42): 'read' → 'submit_expense'
- resetGrantForm() method (line 315): 'read' → 'submit_expense'
- resetBulkGrantForm() method (line 402): 'read' → 'submit_expense'

Rationale: Most users need to submit expenses to their assigned
accounts, making 'submit_expense' a more practical default than
'read'. Admins can still select other permission types from the
dropdown if needed.

Affected: static/js/permissions.js

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 23:18:40 +01:00
5d38dc188b Fix loading state hang when user has no permissions
Fixed UI hanging indefinitely on "Loading..." when users have no
account permissions or when API calls fail.

Problem: When API calls failed (due to no permissions, timeout, or
other errors), the error handlers would show error notifications but
wouldn't clear the loading state. This left data properties as null
or undefined, causing v-if/v-else templates to show spinners forever.

Solution: Set default/empty values in error handlers to clear loading
states and allow UI to render properly:

- loadBalance(): Set balance to {balance: 0, fiat_balances: {}, accounts: []}
- loadTransactions(): Set transactions to [] and pagination.total to 0
- loadAccounts(): Set accounts to []

Now when API calls fail, users see:
- Error notification (existing behavior)
- Empty state UI instead of infinite spinner (new behavior)
- "No transactions yet" / "0 sats" instead of "Loading..."

Affected files:
- static/js/index.js (lines 326-331, 391-393, 434-435)

Co-Authored-By: Claude <noreply@anthropic.com>

Fix Chart of Accounts loading spinner stuck issue

Fixed the Chart of Accounts section showing "Loading accounts..."
indefinitely when user has no account permissions.

Problem: The previous commit set accounts = [] in error handler to
clear loading state. However, the template logic was:
- v-if="accounts.length > 0" → show accounts list
- v-else → show loading spinner

When accounts = [] (empty array), it triggered v-else and showed
the spinner forever.

Solution: Changed the v-else block from loading spinner to empty
state message "No accounts available" with grey text styling.

Now when loadAccounts() fails or returns empty:
- Shows "No accounts available" instead of infinite spinner
- Consistent with other empty states (transactions, balances)
- User sees informative message instead of fake loading state

Affected: templates/castle/index.html (line 792-794)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 23:03:05 +01:00
61a3831b15 Add user-selectable date range filters for Recent Transactions
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>
2025-11-11 22:54:14 +01:00
bf79495ceb Optimize recent transactions with 30-day date filter
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>
2025-11-11 22:39:22 +01:00
72e8fe8ee4 Fix UNIQUE constraint error in get_or_create_user_account
Handles race condition where user account already exists from initial sync
but without user_id set. When user configures wallet, code now:
- Catches IntegrityError on UNIQUE constraint for accounts.name
- Fetches existing account by name
- Updates user_id if NULL or different
- Returns existing account instead of failing

This fixes the error that occurred when users configured their wallet after
their accounts were created during the initial Beancount sync.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 19:47:17 +01:00
a71d9b7fa5 FIX: add fava extension settings with default values 2025-11-11 19:04:55 +01:00
ff6853a030 MIGRATION FIX: remove castle_ prefixes 2025-11-11 18:50:47 +01:00