diff --git a/beancount_format.py b/beancount_format.py index 446ce26..a1bf874 100644 --- a/beancount_format.py +++ b/beancount_format.py @@ -889,7 +889,7 @@ def format_revenue_entry( def format_income_entry( user_id: str, - payment_account: str, + user_account: str, revenue_account: str, amount_sats: int, description: str, @@ -906,7 +906,8 @@ def format_income_entry( 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. + Postings: DR user_account (Assets:Receivable:User-{id} — user owes + the entity until they hand the cash over), 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") @@ -921,7 +922,7 @@ def format_income_entry( postings = [ { - "account": payment_account, + "account": user_account, "amount": f"{fiat_amount_abs:.2f} {fiat_currency} @@ {sats_abs} SATS", }, { diff --git a/models.py b/models.py index 9fe3b9a..25323dd 100644 --- a/models.py +++ b/models.py @@ -128,12 +128,18 @@ class RevenueEntry(BaseModel): class IncomeEntry(BaseModel): - """Helper model for user-facing income/revenue submission (pending approval)""" + """Helper model for user-facing income/revenue submission (pending approval). + + The user records that they personally received money on the entity's + behalf — so the postings are DR Assets:Receivable:User-{id} / CR + revenue_account. The user now owes the entity until they settle via + the existing /settle-receivable flow. Symmetric with ExpenseEntry, + which credits Liabilities:Payable:User-{id} (entity owes user). + """ description: str amount: Decimal # Fiat amount in the specified currency revenue_account: str # Income/Revenue account name or ID - payment_method_account: str # Asset account receiving the funds (Cash, Bank, Lightning) currency: str # Required: fiat currency code (EUR, USD, etc.) reference: Optional[str] = None entry_date: Optional[datetime] = None diff --git a/views_api.py b/views_api.py index f77bea7..dc0dc69 100644 --- a/views_api.py +++ b/views_api.py @@ -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(