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>
This commit is contained in:
parent
4085280711
commit
93b5c2677c
3 changed files with 219 additions and 0 deletions
|
|
@ -885,3 +885,67 @@ def format_revenue_entry(
|
|||
links=links,
|
||||
meta=entry_meta
|
||||
)
|
||||
|
||||
|
||||
def format_income_entry(
|
||||
user_id: str,
|
||||
payment_account: str,
|
||||
revenue_account: str,
|
||||
amount_sats: int,
|
||||
description: str,
|
||||
entry_date: date,
|
||||
fiat_currency: str,
|
||||
fiat_amount: Decimal,
|
||||
reference: Optional[str] = None,
|
||||
entry_id: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Format a user-submitted income/revenue entry for Fava (pending approval).
|
||||
|
||||
Mirrors format_expense_entry: pending flag (!) for super-user review,
|
||||
fiat-first price notation (@@ SATS) for BQL queryability, unique link
|
||||
(^inc-{entry_id}) for tracking through the approve/reject flow.
|
||||
|
||||
Postings: DR payment_account (asset receives funds), CR revenue_account.
|
||||
"""
|
||||
if not fiat_currency or not fiat_amount or fiat_amount <= 0:
|
||||
raise ValueError("fiat_currency and a positive fiat_amount are required for income entries")
|
||||
|
||||
if not entry_id:
|
||||
entry_id = generate_entry_id()
|
||||
|
||||
fiat_amount_abs = abs(fiat_amount)
|
||||
sats_abs = abs(amount_sats)
|
||||
|
||||
narration = f"{description} ({fiat_amount_abs:.2f} {fiat_currency})"
|
||||
|
||||
postings = [
|
||||
{
|
||||
"account": payment_account,
|
||||
"amount": f"{fiat_amount_abs:.2f} {fiat_currency} @@ {sats_abs} SATS",
|
||||
},
|
||||
{
|
||||
"account": revenue_account,
|
||||
"amount": f"-{fiat_amount_abs:.2f} {fiat_currency} @@ {sats_abs} SATS",
|
||||
},
|
||||
]
|
||||
|
||||
entry_meta = {
|
||||
"user-id": user_id,
|
||||
"source": "libra-api",
|
||||
"entry-id": entry_id,
|
||||
}
|
||||
|
||||
links = [f"inc-{entry_id}"]
|
||||
if reference:
|
||||
links.append(sanitize_link(reference))
|
||||
|
||||
return format_transaction(
|
||||
date_val=entry_date,
|
||||
flag="!", # Pending - requires admin approval
|
||||
narration=narration,
|
||||
postings=postings,
|
||||
tags=["income-entry"],
|
||||
links=links,
|
||||
meta=entry_meta,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue