Add user-facing income/revenue submission endpoint #13

Merged
padreug merged 3 commits from feature/income-submission-endpoint into main 2026-05-17 12:00:17 +00:00
Owner

Summary

  • New POST /api/v1/entries/income endpoint mirroring the expense flow: invoice-key auth, pending ! flag, reuses the existing /entries/{id}/approve and /reject endpoints (which match by libra-{id} link, entry-type-agnostic).
  • Adds PermissionType.SUBMIT_INCOME granted on revenue accounts — kept distinct from SUBMIT_EXPENSE so admins can grant the two independently.
  • Enforces AccountType.REVENUE on the income account and AccountType.ASSET on the payment-method account; fiat currency is required.

Closes #9

Test plan

  • Grant SUBMIT_INCOME to a non-admin user on an Income:* account; submit via POST /libra/api/v1/entries/income with invoice key — entry appears in Fava with ! flag and #income-entry tag.
  • Verify the entry surfaces in GET /libra/api/v1/entries/pending alongside pending expenses.
  • Approve via POST /libra/api/v1/entries/{id}/approve — Beancount source flag flips !* and balances update.
  • Reject via POST /libra/api/v1/entries/{id}/reject — entry gets #voided tag and is excluded from balances.
  • Submitting without SUBMIT_INCOME permission returns 403 with a clear error.
  • Submitting against a non-Revenue account (e.g. an Expense account) returns 400 with the account-type validation error; same for a non-Asset payment account.
  • Submitting without currency returns a Pydantic validation error.

🤖 Generated with Claude Code

## Summary - New `POST /api/v1/entries/income` endpoint mirroring the expense flow: invoice-key auth, pending `!` flag, reuses the existing `/entries/{id}/approve` and `/reject` endpoints (which match by `libra-{id}` link, entry-type-agnostic). - Adds `PermissionType.SUBMIT_INCOME` granted on revenue accounts — kept distinct from `SUBMIT_EXPENSE` so admins can grant the two independently. - Enforces `AccountType.REVENUE` on the income account and `AccountType.ASSET` on the payment-method account; fiat currency is required. Closes #9 ## Test plan - [ ] Grant `SUBMIT_INCOME` to a non-admin user on an `Income:*` account; submit via `POST /libra/api/v1/entries/income` with invoice key — entry appears in Fava with `!` flag and `#income-entry` tag. - [ ] Verify the entry surfaces in `GET /libra/api/v1/entries/pending` alongside pending expenses. - [ ] Approve via `POST /libra/api/v1/entries/{id}/approve` — Beancount source flag flips `!` → `*` and balances update. - [ ] Reject via `POST /libra/api/v1/entries/{id}/reject` — entry gets `#voided` tag and is excluded from balances. - [ ] Submitting without `SUBMIT_INCOME` permission returns 403 with a clear error. - [ ] Submitting against a non-Revenue account (e.g. an Expense account) returns 400 with the account-type validation error; same for a non-Asset payment account. - [ ] Submitting without `currency` returns a Pydantic validation error. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
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>
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.
Author
Owner

@narayan got it working!

image
image

However, the plot thickens!

If User A accepts income from another User B within the system, then the other User B has settled that amount of debt with the organization and the User A has assumed it!

I suppose there could be, when adding an Income, an option to choose a user within the organization they received from.

Might need to walk through that one with you 😆

@narayan got it working! ![image](/attachments/43894083-2524-4973-937e-3057db887bb2) ![image](/attachments/64ac68e2-4b9a-4f33-92b6-2677e365640e) However, the plot thickens! If `User A` accepts income from another `User B` within the system, then the other `User B` has settled that amount of debt with the organization and the `User A` has assumed it! I suppose there could be, when adding an Income, an option to choose a user within the organization they received from. Might need to walk through that one with you 😆
padreug deleted branch feature/income-submission-endpoint 2026-05-17 12:00:17 +00:00
Sign in to join this conversation.
No reviewers
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!13
No description provided.