Add lifetime income/expense totals to UserBalance
New get_user_lifetime_totals_bql() runs tag-filtered BQL queries
(Payable + expense-entry, Receivable + income-entry) to compute
per-user lifetime totals separately from the net balance. Plumbed
through /api/v1/balance and /api/v1/balance/{user_id}; existing
clients keep working (fields default to zero / empty dict).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
483e89163e
commit
deeec7e2c5
3 changed files with 82 additions and 0 deletions
|
|
@ -827,6 +827,66 @@ class FavaClient:
|
|||
"accounts": accounts
|
||||
}
|
||||
|
||||
async def get_user_lifetime_totals_bql(self, user_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get lifetime totals of expenses submitted and income recorded by this user.
|
||||
|
||||
Sums original entries only (tag-filtered) — does not net against payments
|
||||
or other reconciliation activity, so totals match "amounts ever entered".
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
|
||||
Returns:
|
||||
{
|
||||
"total_expenses_sats": int,
|
||||
"total_expenses_fiat": {"EUR": Decimal("...")},
|
||||
"total_income_sats": int,
|
||||
"total_income_fiat": {"EUR": Decimal("...")},
|
||||
}
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
user_id_prefix = user_id[:8]
|
||||
|
||||
async def _sum_for(account_pattern: str, tag: str):
|
||||
query = f"""
|
||||
SELECT currency, sum(number), sum(weight)
|
||||
WHERE account ~ '{account_pattern}:User-{user_id_prefix}'
|
||||
AND '{tag}' IN tags
|
||||
AND flag = '*'
|
||||
GROUP BY currency
|
||||
"""
|
||||
result = await self.query_bql(query)
|
||||
sats_total = 0
|
||||
fiat_total: Dict[str, Decimal] = {}
|
||||
for row in result["rows"]:
|
||||
currency, number_sum, weight_sum = row
|
||||
# Skip SATS-currency rows (payment/reconciliation legs)
|
||||
if currency == "SATS":
|
||||
continue
|
||||
if isinstance(weight_sum, dict) and "SATS" in weight_sum:
|
||||
sats_total += abs(int(Decimal(str(weight_sum["SATS"]))))
|
||||
fiat_amount = abs(Decimal(str(number_sum))) if number_sum else Decimal(0)
|
||||
if fiat_amount > 0:
|
||||
fiat_total[currency] = fiat_total.get(currency, Decimal(0)) + fiat_amount
|
||||
return sats_total, fiat_total
|
||||
|
||||
exp_sats, exp_fiat = await _sum_for("Liabilities:Payable", "expense-entry")
|
||||
inc_sats, inc_fiat = await _sum_for("Assets:Receivable", "income-entry")
|
||||
|
||||
logger.info(
|
||||
f"User {user_id[:8]} lifetime totals (BQL): "
|
||||
f"expenses={exp_sats} sats {dict(exp_fiat)}, income={inc_sats} sats {dict(inc_fiat)}"
|
||||
)
|
||||
|
||||
return {
|
||||
"total_expenses_sats": exp_sats,
|
||||
"total_expenses_fiat": exp_fiat,
|
||||
"total_income_sats": inc_sats,
|
||||
"total_income_fiat": inc_fiat,
|
||||
}
|
||||
|
||||
async def get_all_user_balances_bql(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get balances for all users using BQL with currency-grouped aggregation.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue