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.
167 lines
4.8 KiB
Markdown
167 lines
4.8 KiB
Markdown
# Expense Approval Workflow
|
|
|
|
## Overview
|
|
|
|
The Castle extension now requires admin approval for all user-submitted expenses. This prevents invalid or incorrect expenses from affecting balances until they are verified by the Castle admin.
|
|
|
|
## How It Works
|
|
|
|
### 1. User Submits Expense
|
|
- User fills out the expense form with description, amount, category, etc.
|
|
- Expense is created with `flag='!'` (PENDING status)
|
|
- Entry is saved to the database but **does not affect balances**
|
|
|
|
### 2. Admin Reviews Pending Expenses
|
|
- Admin sees "Pending Expense Approvals" card on the main page
|
|
- Card shows all pending expenses with:
|
|
- Description and amount
|
|
- User who submitted it
|
|
- Date submitted
|
|
- Fiat amount (if applicable)
|
|
- Reference number
|
|
|
|
### 3. Admin Takes Action
|
|
|
|
#### Option A: Approve
|
|
- Admin clicks "Approve" button
|
|
- Entry flag changes from `!` to `*` (CLEARED)
|
|
- Entry **now affects balances** (user's balance updates)
|
|
- User sees the expense in their transaction history
|
|
- Entry appears with green checkmark icon
|
|
|
|
#### Option B: Reject
|
|
- Admin clicks "Reject" button
|
|
- Entry flag changes from `!` to `x` (VOID)
|
|
- Entry **never affects balances**
|
|
- Entry appears with grey cancel icon (voided)
|
|
|
|
## Balance Calculation
|
|
|
|
Only entries with `flag='*'` (CLEARED) are included in balance calculations:
|
|
|
|
```sql
|
|
-- Balance query excludes pending/flagged/voided entries
|
|
SELECT SUM(amount)
|
|
FROM entry_lines el
|
|
JOIN journal_entries je ON el.journal_entry_id = je.id
|
|
WHERE el.account_id = :account_id
|
|
AND je.flag = '*' -- Only cleared entries
|
|
```
|
|
|
|
## Transaction Flags
|
|
|
|
| Flag | Symbol | Status | Affects Balance | Description |
|
|
|------|--------|--------|----------------|-------------|
|
|
| `*` | ✅ | CLEARED | Yes | Confirmed and reconciled |
|
|
| `!` | ⏱️ | PENDING | No | Awaiting approval |
|
|
| `#` | 🚩 | FLAGGED | No | Needs review |
|
|
| `x` | ❌ | VOID | No | Cancelled/rejected |
|
|
|
|
## API Endpoints
|
|
|
|
### Get Pending Entries (Admin Only)
|
|
```
|
|
GET /castle/api/v1/entries/pending
|
|
Authorization: Admin Key
|
|
|
|
Returns: list[JournalEntry]
|
|
```
|
|
|
|
### Approve Expense (Admin Only)
|
|
```
|
|
POST /castle/api/v1/entries/{entry_id}/approve
|
|
Authorization: Admin Key
|
|
|
|
Returns: JournalEntry (with flag='*')
|
|
```
|
|
|
|
### Reject Expense (Admin Only)
|
|
```
|
|
POST /castle/api/v1/entries/{entry_id}/reject
|
|
Authorization: Admin Key
|
|
|
|
Returns: JournalEntry (with flag='x')
|
|
```
|
|
|
|
## Implementation Details
|
|
|
|
### Files Modified
|
|
|
|
1. **views_api.py**
|
|
- Line 284: Set expenses to `JournalEntryFlag.PENDING` on creation
|
|
- Lines 181-197: Added `/api/v1/entries/pending` endpoint
|
|
- Lines 972-1011: Added approve endpoint
|
|
- Lines 1013-1053: Added reject endpoint
|
|
|
|
2. **crud.py**
|
|
- Lines 315-329: Updated `get_account_balance()` to filter by flag
|
|
- Lines 367-376: Updated fiat balance calculation to filter by flag
|
|
- Lines 238-269: Fixed `get_all_journal_entries()` to parse flag/meta
|
|
|
|
3. **index.html**
|
|
- Lines 157-209: Added "Pending Expense Approvals" card
|
|
|
|
4. **index.js**
|
|
- Line 68: Added `pendingExpenses` data property
|
|
- Lines 497-511: Added `loadPendingExpenses()` method
|
|
- Lines 545-563: Added `approveExpense()` method
|
|
- Lines 564-580: Added `rejectExpense()` method
|
|
- Line 731: Load pending expenses on page load for admins
|
|
|
|
## User Experience
|
|
|
|
### For Regular Users
|
|
1. Submit expense via "Add Expense" button
|
|
2. See expense with orange pending icon (⏱️) in transaction list
|
|
3. Balance does NOT change yet
|
|
4. Wait for admin approval
|
|
|
|
### For Admin (Super User)
|
|
1. See "Pending Expense Approvals" card at top of page
|
|
2. Review expense details
|
|
3. Click "Approve" → User's balance updates
|
|
4. Click "Reject" → Expense is voided, no balance change
|
|
|
|
## Security
|
|
|
|
- All approval endpoints require admin key
|
|
- Super user check prevents regular users from approving their own expenses
|
|
- Voided entries are never included in balance calculations
|
|
- Full audit trail in `meta` field tracks who created and reviewed each entry
|
|
|
|
## Testing
|
|
|
|
1. **Submit test expense as regular user**
|
|
```
|
|
POST /castle/api/v1/entries/expense
|
|
{
|
|
"description": "Test groceries",
|
|
"amount": 50.00,
|
|
"currency": "EUR",
|
|
"expense_account": "utilities",
|
|
"is_equity": false
|
|
}
|
|
```
|
|
|
|
2. **Verify it's pending**
|
|
- Check user balance → should NOT include this expense
|
|
- Check transaction list → should show orange pending icon
|
|
|
|
3. **Login as admin and approve**
|
|
- See expense in "Pending Expense Approvals"
|
|
- Click "Approve"
|
|
- Verify user balance updates
|
|
|
|
4. **Submit another expense and reject it**
|
|
- Submit expense
|
|
- Admin clicks "Reject"
|
|
- Verify balance never changed
|
|
- Entry shows grey cancel icon
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] Email notifications when expense is approved/rejected
|
|
- [ ] Bulk approve/reject multiple expenses
|
|
- [ ] Admin notes when rejecting (reason for rejection)
|
|
- [ ] Expense revision system (user can edit and resubmit rejected expenses)
|
|
- [ ] Approval workflow with multiple approvers
|