Surface user credit balance in GET /balance per libra-#41
Extends get_user_balance_bql and get_all_user_balances_bql to fold Liabilities:Credit:User-X into the same query as Payable and Receivable. Credit is the overpay-absorbing liability that libra owes the user going forward — it carries the same sign as Payable, so the existing fiat aggregation subtracts it from net obligation without further changes. Adds UserBalance.account_balances to surface the BQL per-account breakdown so libra extension UI and webapp can render Payable / Receivable / Credit as distinct line items. The legacy `accounts` field stays empty for back-compat with anything reading the older shape. Prepares for libra-#33 / libra-#41: settlement netting (#14 task) will write the overflow leg to credit; this changeset makes sure that, the moment credit exists, the displayed net everywhere already reflects it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a4b3022c2
commit
50658440a4
3 changed files with 16 additions and 4 deletions
|
|
@ -820,10 +820,15 @@ class FavaClient:
|
||||||
# GROUP BY currency prevents mixing EUR and SATS face values in sum(number).
|
# GROUP BY currency prevents mixing EUR and SATS face values in sum(number).
|
||||||
# sum(weight) gives SATS for both EUR @@ SATS entries and plain SATS entries.
|
# sum(weight) gives SATS for both EUR @@ SATS entries and plain SATS entries.
|
||||||
# sum(number) on EUR rows gives the fiat amount; on SATS rows gives sats paid.
|
# sum(number) on EUR rows gives the fiat amount; on SATS rows gives sats paid.
|
||||||
|
# Credit is the overpay-absorbing liability per libra-#41 — it lives
|
||||||
|
# on the same per-user namespace as Payable and contributes to the
|
||||||
|
# user's net obligation with the same sign as Payable (negative on
|
||||||
|
# Liabilities means libra owes user). Folding it into the same query
|
||||||
|
# means the displayed net always already accounts for credit.
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT account, currency, sum(number), sum(weight)
|
SELECT account, currency, sum(number), sum(weight)
|
||||||
WHERE account ~ ':User-{user_id_prefix}'
|
WHERE account ~ ':User-{user_id_prefix}'
|
||||||
AND (account ~ 'Payable' OR account ~ 'Receivable')
|
AND (account ~ 'Payable' OR account ~ 'Receivable' OR account ~ 'Credit')
|
||||||
AND flag = '*'
|
AND flag = '*'
|
||||||
GROUP BY account, currency
|
GROUP BY account, currency
|
||||||
"""
|
"""
|
||||||
|
|
@ -970,10 +975,11 @@ class FavaClient:
|
||||||
"""
|
"""
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
# GROUP BY currency prevents mixing EUR and SATS face values in sum(number)
|
# GROUP BY currency prevents mixing EUR and SATS face values in sum(number).
|
||||||
|
# Credit per libra-#41 — see get_user_balance_bql for the rationale.
|
||||||
query = """
|
query = """
|
||||||
SELECT account, currency, sum(number), sum(weight)
|
SELECT account, currency, sum(number), sum(weight)
|
||||||
WHERE (account ~ 'Payable:User-' OR account ~ 'Receivable:User-')
|
WHERE (account ~ 'Payable:User-' OR account ~ 'Receivable:User-' OR account ~ 'Credit:User-')
|
||||||
AND flag = '*'
|
AND flag = '*'
|
||||||
GROUP BY account, currency
|
GROUP BY account, currency
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,11 @@ class UserBalance(BaseModel):
|
||||||
user_id: str
|
user_id: str
|
||||||
balance: int # positive = libra owes user, negative = user owes libra
|
balance: int # positive = libra owes user, negative = user owes libra
|
||||||
accounts: list[Account] = []
|
accounts: list[Account] = []
|
||||||
|
# Per-account breakdown surfaced from get_user_balance_bql so UIs (libra
|
||||||
|
# extension dashboard + webapp) can render Payable / Receivable / Credit
|
||||||
|
# as distinct line items. Each entry: {"account": str, "sats": int,
|
||||||
|
# "eur": Decimal}. Wired up for libra-#41's display contract.
|
||||||
|
account_balances: list[dict] = []
|
||||||
fiat_balances: dict[str, Decimal] = {} # e.g. {"EUR": Decimal("250.0"), "USD": Decimal("100.0")}
|
fiat_balances: dict[str, Decimal] = {} # e.g. {"EUR": Decimal("250.0"), "USD": Decimal("100.0")}
|
||||||
# Lifetime totals (original entries only; not net of reconciliation)
|
# Lifetime totals (original entries only; not net of reconciliation)
|
||||||
total_expenses_sats: int = 0
|
total_expenses_sats: int = 0
|
||||||
|
|
|
||||||
|
|
@ -1609,7 +1609,8 @@ async def api_get_my_balance(
|
||||||
return UserBalance(
|
return UserBalance(
|
||||||
user_id=wallet.wallet.user,
|
user_id=wallet.wallet.user,
|
||||||
balance=balance_data["balance"],
|
balance=balance_data["balance"],
|
||||||
accounts=[], # Could populate from balance_data["accounts"] if needed
|
accounts=[],
|
||||||
|
account_balances=balance_data.get("accounts", []),
|
||||||
fiat_balances=balance_data["fiat_balances"],
|
fiat_balances=balance_data["fiat_balances"],
|
||||||
total_expenses_sats=totals["total_expenses_sats"],
|
total_expenses_sats=totals["total_expenses_sats"],
|
||||||
total_expenses_fiat=totals["total_expenses_fiat"],
|
total_expenses_fiat=totals["total_expenses_fiat"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue