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.
This commit is contained in:
Padreug 2026-05-16 23:40:08 +02:00
commit 0f2a38ee7f
3 changed files with 21 additions and 23 deletions

View file

@ -1235,21 +1235,6 @@ async def api_create_income_entry(
detail=f"Account '{revenue_account.name}' is not a revenue account (type: {revenue_account.account_type.value})",
)
# Resolve payment method account by name or ID
payment_account = await get_account_by_name(data.payment_method_account)
if not payment_account:
payment_account = await get_account(data.payment_method_account)
if not payment_account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Payment account '{data.payment_method_account}' not found",
)
if payment_account.account_type != AccountType.ASSET:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Account '{payment_account.name}' is not an asset account (type: {payment_account.account_type.value})",
)
# Permission check on the revenue account
from .crud import get_user_permissions_with_inheritance
@ -1262,6 +1247,12 @@ async def api_create_income_entry(
detail=f"You do not have permission to submit income to account '{revenue_account.name}'. Please contact an administrator to request access.",
)
# Income lands on the user as a receivable — they're holding cash on
# behalf of the entity until they hand it over via /settle-receivable.
user_account = await get_or_create_user_account(
wallet.wallet.user, AccountType.ASSET, "Accounts Receivable"
)
# Convert fiat to sats
fiat_currency = data.currency.upper()
amount_sats = await fiat_amount_as_satoshis(float(data.amount), data.currency)
@ -1288,7 +1279,7 @@ async def api_create_income_entry(
entry = format_income_entry(
user_id=wallet.wallet.user,
payment_account=payment_account.name,
user_account=user_account.name,
revenue_account=revenue_account.name,
amount_sats=amount_sats,
description=data.description,
@ -1323,9 +1314,9 @@ async def api_create_income_entry(
EntryLine(
id=f"line-1-{entry_id}",
journal_entry_id=entry_id,
account_id=payment_account.id,
account_id=user_account.id,
amount=amount_sats,
description=f"Income received into {payment_account.name}",
description=f"User holds cash receivable to entity ({user_account.name})",
metadata=metadata,
),
EntryLine(