Add user-facing asset purchase entry (parallel to expense/income) #20

Open
opened 2026-05-18 21:44:36 +00:00 by padreug · 0 comments
Owner

Summary

Libra has user-facing endpoints for expense (out-of-pocket spend that gets consumed) and income (money received on the org's behalf), but no way for a user to record that they bought an asset on the org's behalf — something the org now owns.

Concrete scenario: a member buys a flock of sheep for €2,000 on the org's behalf. Today they'd have to either (a) submit it as an expense (wrong — expenses are P&L; the org still owns €2,000 worth of value, that's a balance-sheet asset, not a consumed cost), or (b) wait for an admin to create the entry directly via POST /api/v1/entries. Neither is right.

Posting shape

The accounting itself is structurally identical to the expense flow, but the debit lands on an asset account instead of an expense account:

DR Assets:Livestock                2000.00 EUR @@ <sats> SATS
   CR Liabilities:Payable:User-X     -2000.00 EUR @@ <sats> SATS

The user is out €2,000 (org owes them), and the org's balance sheet has €2,000 of new asset value. Settlement of the resulting payable goes through the existing POST /api/v1/pay-user flow — no new settlement code needed.

Why a distinct entry type rather than overloading expense

Expense vs asset purchase isn't just a labelling difference; the books need to reflect it:

  • Expense debits a P&L account (Expenses:*) — value consumed, reduces net income.
  • Asset purchase debits a balance-sheet account (Assets:*) — value retained, doesn't touch P&L until disposal or depreciation.

Letting users pick whether their submission is "expense" or "asset" — instead of training them to know that Assets:Livestock exists and route there — is the cleanest separation. A submission against an Assets:* account in the expense endpoint should arguably be rejected (related to #15's account-type mismatch).

Proposed scaffolding (backend, libra ext)

  1. Endpoint: POST /api/v1/entries/asset — mirrors the expense flow exactly:
    • require_invoice_key auth.
    • Pending ! flag, reuses existing approve/reject endpoints (entry-type-agnostic).
    • AssetEntry request model: description, amount (Decimal), asset_account (must resolve to an AccountType.ASSET account, excluding user receivables and Lightning/cash operating accounts — see open question 3 below), currency (required), reference?, entry_date?.
  2. Permission: new PermissionType.SUBMIT_ASSET granted on specific Asset accounts. Don't reuse SUBMIT_EXPENSE; the two operations target distinct account types and should be grantable independently (same reasoning that gave us SUBMIT_INCOME).
  3. Formatter: format_asset_entry() in beancount_format.py, fiat-first @@ SATS notation (current convention; pending #18), flag="!", tag #asset-entry, link ^ast-{entry_id} for tracking.
  4. Default chart: add a couple of common starter Asset accounts to account_utils.py so installations have something to point at out of the box (suggestions in open question 4).

Proposed scaffolding (frontend)

  1. LNbits ext (templates/libra/index.html): a third Quick Action button "Add Asset" next to Add Expense, with a parallel dialog. Account picker filtered to AccountType.ASSET minus operating/receivable accounts.
  2. Webapp Record page: a third tile "Add Asset" alongside Add Expense / Add Income. Same permission-gating pattern as the other two.
  3. Permission management UI: the dropdown in #15 needs the new SUBMIT_ASSET row; the role view should render the matching badge. (This is the same surface that issue #15 also touches — worth bundling.)

Open design questions

  1. Which asset accounts qualify? Not all Assets:* accounts should accept user purchase entries. Assets:Receivable:User-* is a per-user accounting construct, not something a user buys; Assets:Cash / Assets:Bank / Assets:Bitcoin:Lightning are operating cash buckets where "buying" something into them is semantically wrong. The endpoint should accept only "capital asset" accounts — likely modelled as either:
    • A boolean flag on the account (e.g. is_capital_asset: bool), or
    • A subtree convention (only accounts under Assets:Capital:* or Assets:Inventory:* qualify), or
    • A new account sub-type enum.
  2. Permission inheritance / granularity. Granting SUBMIT_ASSET on Assets:Livestock is obvious; do we want a parent-account grant (SUBMIT_ASSET on Assets:*) for trusted users? get_user_permissions_with_inheritance already handles parent-child cascading, so this likely works without code changes.
  3. Depreciation, disposal, revaluation. Out of scope here, but worth flagging: livestock especially grows, multiplies, and dies — values change over time without a "transaction" per se. Fixed-asset depreciation, inventory revaluation, and asset disposal (selling the flock) are future features. For now we just need acquisition; disposal/depreciation is a follow-up issue if/when it matters.
  4. Default chart suggestions. A few starters that feel applicable across collectives:
    • Assets:Inventory (parent)
    • Assets:Inventory:Livestock
    • Assets:Inventory:Supplies (consumables held for resale or use)
    • Assets:Equipment (long-lived tools, furniture)
    • Assets:Vehicles
      Communities can extend as needed; we just need credible defaults so the UI isn't empty.
  5. UI labelling. "Add Asset" reads ambiguously (asset transaction? asset account?). Better names: "Record Purchase", "Add Asset Acquisition", "Buy Asset"? Worth a quick UX call before shipping.
  6. Settlement after approval. Once approved, the resulting Liabilities:Payable:User-X entry behaves identically to an expense-derived payable. Existing pay-user / outstanding balances logic should handle it without changes. Confirm with a manual test path before closing.
  7. Cross-currency. If the user buys an asset priced in CAD while the org's books generally run in EUR, the entry should record the CAD cost (per the existing fiat-first convention). Multi-currency Outstanding Balances now displays this correctly per the recent fix. No new work expected on the cross-currency display side.

Severity / priority

Medium feature — unblocks legitimate use cases that today either route through the wrong account type or require admin intervention. Conceptually close to the expense flow, so the implementation cost is small (an endpoint + a model + a formatter + UI tile + permission), but the design questions above (especially "which accounts qualify") deserve a decision before code lands.

Related: #15 (permission-type/account-type dropdown filter — would also gain a SUBMIT_ASSET arm), #18 (unit-of-account convention — affects how cost basis is recorded for the asset).

## Summary Libra has user-facing endpoints for **expense** (out-of-pocket spend that gets consumed) and **income** (money received on the org's behalf), but no way for a user to record that they **bought an asset** on the org's behalf — something the org now owns. Concrete scenario: a member buys a flock of sheep for €2,000 on the org's behalf. Today they'd have to either (a) submit it as an expense (wrong — expenses are P&L; the org still owns €2,000 worth of value, that's a balance-sheet asset, not a consumed cost), or (b) wait for an admin to create the entry directly via `POST /api/v1/entries`. Neither is right. ## Posting shape The accounting itself is structurally identical to the expense flow, but the debit lands on an asset account instead of an expense account: ``` DR Assets:Livestock 2000.00 EUR @@ <sats> SATS CR Liabilities:Payable:User-X -2000.00 EUR @@ <sats> SATS ``` The user is out €2,000 (org owes them), and the org's balance sheet has €2,000 of new asset value. Settlement of the resulting payable goes through the existing `POST /api/v1/pay-user` flow — no new settlement code needed. ## Why a distinct entry type rather than overloading expense Expense vs asset purchase isn't just a labelling difference; the books need to reflect it: - **Expense** debits a P&L account (`Expenses:*`) — value consumed, reduces net income. - **Asset purchase** debits a balance-sheet account (`Assets:*`) — value retained, doesn't touch P&L until disposal or depreciation. Letting users pick whether their submission is "expense" or "asset" — instead of training them to know that `Assets:Livestock` exists and route there — is the cleanest separation. A submission against an `Assets:*` account in the expense endpoint should arguably be rejected (related to #15's account-type mismatch). ## Proposed scaffolding (backend, libra ext) 1. **Endpoint**: `POST /api/v1/entries/asset` — mirrors the expense flow exactly: - `require_invoice_key` auth. - Pending `!` flag, reuses existing approve/reject endpoints (entry-type-agnostic). - `AssetEntry` request model: `description`, `amount` (Decimal), `asset_account` (must resolve to an `AccountType.ASSET` account, **excluding** user receivables and Lightning/cash operating accounts — see open question 3 below), `currency` (required), `reference?`, `entry_date?`. 2. **Permission**: new `PermissionType.SUBMIT_ASSET` granted on specific Asset accounts. Don't reuse `SUBMIT_EXPENSE`; the two operations target distinct account types and should be grantable independently (same reasoning that gave us `SUBMIT_INCOME`). 3. **Formatter**: `format_asset_entry()` in `beancount_format.py`, fiat-first `@@ SATS` notation (current convention; pending #18), `flag="!"`, tag `#asset-entry`, link `^ast-{entry_id}` for tracking. 4. **Default chart**: add a couple of common starter Asset accounts to `account_utils.py` so installations have something to point at out of the box (suggestions in open question 4). ## Proposed scaffolding (frontend) 1. **LNbits ext** (`templates/libra/index.html`): a third Quick Action button "Add Asset" next to Add Expense, with a parallel dialog. Account picker filtered to `AccountType.ASSET` minus operating/receivable accounts. 2. **Webapp Record page**: a third tile "Add Asset" alongside Add Expense / Add Income. Same permission-gating pattern as the other two. 3. **Permission management UI**: the dropdown in #15 needs the new `SUBMIT_ASSET` row; the role view should render the matching badge. (This is the same surface that issue #15 also touches — worth bundling.) ## Open design questions 1. **Which asset accounts qualify?** Not all `Assets:*` accounts should accept user purchase entries. `Assets:Receivable:User-*` is a per-user accounting construct, not something a user buys; `Assets:Cash` / `Assets:Bank` / `Assets:Bitcoin:Lightning` are operating cash buckets where "buying" something into them is semantically wrong. The endpoint should accept only "capital asset" accounts — likely modelled as either: - A boolean flag on the account (e.g. `is_capital_asset: bool`), or - A subtree convention (only accounts under `Assets:Capital:*` or `Assets:Inventory:*` qualify), or - A new account sub-type enum. 2. **Permission inheritance / granularity.** Granting `SUBMIT_ASSET` on `Assets:Livestock` is obvious; do we want a parent-account grant (`SUBMIT_ASSET` on `Assets:*`) for trusted users? `get_user_permissions_with_inheritance` already handles parent-child cascading, so this likely works without code changes. 3. **Depreciation, disposal, revaluation.** Out of scope here, but worth flagging: livestock especially grows, multiplies, and dies — values change over time without a "transaction" per se. Fixed-asset depreciation, inventory revaluation, and asset disposal (selling the flock) are future features. For now we just need acquisition; disposal/depreciation is a follow-up issue if/when it matters. 4. **Default chart suggestions.** A few starters that feel applicable across collectives: - `Assets:Inventory` (parent) - `Assets:Inventory:Livestock` - `Assets:Inventory:Supplies` (consumables held for resale or use) - `Assets:Equipment` (long-lived tools, furniture) - `Assets:Vehicles` Communities can extend as needed; we just need credible defaults so the UI isn't empty. 5. **UI labelling.** "Add Asset" reads ambiguously (asset *transaction*? asset *account*?). Better names: "Record Purchase", "Add Asset Acquisition", "Buy Asset"? Worth a quick UX call before shipping. 6. **Settlement after approval.** Once approved, the resulting `Liabilities:Payable:User-X` entry behaves identically to an expense-derived payable. Existing pay-user / outstanding balances logic should handle it without changes. Confirm with a manual test path before closing. 7. **Cross-currency.** If the user buys an asset priced in CAD while the org's books generally run in EUR, the entry should record the CAD cost (per the existing fiat-first convention). Multi-currency Outstanding Balances now displays this correctly per the recent fix. No new work expected on the cross-currency display side. ## Severity / priority Medium feature — unblocks legitimate use cases that today either route through the wrong account type or require admin intervention. Conceptually close to the expense flow, so the implementation cost is small (an endpoint + a model + a formatter + UI tile + permission), but the design questions above (especially "which accounts qualify") deserve a decision before code lands. Related: #15 (permission-type/account-type dropdown filter — would also gain a `SUBMIT_ASSET` arm), #18 (unit-of-account convention — affects how cost basis is recorded for the asset).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/libra#20
No description provided.