Refactors pending entries and adds fiat amounts
Improves the handling of pending entries by extracting and deduplicating data from Fava's query results. Adds support for displaying fiat amounts alongside entries and extracts them from the position data in Fava. Streamlines receivables/payables/equity checks on the frontend by relying on BQL query to supply account type metadata and tags.
This commit is contained in:
parent
37fe34668f
commit
56a3e9d4e9
3 changed files with 106 additions and 64 deletions
92
views_api.py
92
views_api.py
|
|
@ -377,9 +377,71 @@ async def api_get_pending_entries(
|
|||
fava = get_fava_client()
|
||||
all_entries = await fava.query_transactions(limit=1000, include_pending=True)
|
||||
|
||||
# Filter for pending flag
|
||||
pending_entries = [e for e in all_entries if e.get("flag") == "!"]
|
||||
return pending_entries
|
||||
# Deduplicate and extract amounts
|
||||
# BQL returns one row per posting, so we group by transaction
|
||||
seen_transactions = {}
|
||||
|
||||
for e in all_entries:
|
||||
if e.get("flag") == "!":
|
||||
# Create unique transaction key
|
||||
date = e.get("date", "")
|
||||
narration = e.get("narration", "")
|
||||
txn_key = f"{date}:{narration}"
|
||||
|
||||
# Extract entry ID from links field
|
||||
entry_id = None
|
||||
links = e.get("links", [])
|
||||
if isinstance(links, (list, set)):
|
||||
for link in links:
|
||||
if isinstance(link, str) and "castle-" in link:
|
||||
parts = link.split("castle-")
|
||||
if len(parts) > 1:
|
||||
entry_id = parts[-1]
|
||||
break
|
||||
|
||||
# Extract amount and fiat info from position field
|
||||
amount_sats = 0
|
||||
fiat_amount = None
|
||||
fiat_currency = None
|
||||
|
||||
position = e.get("position")
|
||||
if isinstance(position, dict):
|
||||
# Extract sats amount
|
||||
units = position.get("units", {})
|
||||
if isinstance(units, dict) and "number" in units:
|
||||
amount_sats = abs(int(units.get("number", 0)))
|
||||
|
||||
# Extract fiat amount from cost basis
|
||||
cost = position.get("cost", {})
|
||||
if isinstance(cost, dict):
|
||||
if "number" in cost:
|
||||
fiat_amount = cost.get("number")
|
||||
if "currency" in cost:
|
||||
fiat_currency = cost.get("currency")
|
||||
|
||||
# Only keep first occurrence (or update with positive amount)
|
||||
if txn_key not in seen_transactions or amount_sats > 0:
|
||||
entry_data = {
|
||||
"id": entry_id or "unknown",
|
||||
"date": date,
|
||||
"entry_date": date, # Add for frontend compatibility
|
||||
"flag": e.get("flag"),
|
||||
"description": narration,
|
||||
"payee": e.get("payee"),
|
||||
"tags": e.get("tags", []),
|
||||
"links": links,
|
||||
"amount": amount_sats,
|
||||
"account": e.get("account", ""),
|
||||
}
|
||||
|
||||
# Add fiat info if available
|
||||
if fiat_amount and fiat_currency:
|
||||
entry_data["fiat_amount"] = fiat_amount
|
||||
entry_data["fiat_currency"] = fiat_currency
|
||||
|
||||
seen_transactions[txn_key] = entry_data
|
||||
|
||||
return list(seen_transactions.values())
|
||||
|
||||
|
||||
@castle_api_router.get("/api/v1/entries/{entry_id}")
|
||||
|
|
@ -625,6 +687,15 @@ async def api_create_expense_entry(
|
|||
fiat_currency = metadata.get("fiat_currency") if metadata else None
|
||||
fiat_amount = Decimal(metadata.get("fiat_amount")) if metadata and metadata.get("fiat_amount") else None
|
||||
|
||||
# Generate unique entry ID for tracking
|
||||
import uuid
|
||||
entry_id = str(uuid.uuid4()).replace("-", "")[:16]
|
||||
|
||||
# Add castle ID as reference/link
|
||||
castle_reference = f"castle-{entry_id}"
|
||||
if data.reference:
|
||||
castle_reference = f"{data.reference}-{entry_id}"
|
||||
|
||||
# Format Beancount entry
|
||||
entry = format_expense_entry(
|
||||
user_id=wallet.wallet.user,
|
||||
|
|
@ -636,36 +707,35 @@ async def api_create_expense_entry(
|
|||
is_equity=data.is_equity,
|
||||
fiat_currency=fiat_currency,
|
||||
fiat_amount=fiat_amount,
|
||||
reference=data.reference
|
||||
reference=castle_reference # Add castle ID as link
|
||||
)
|
||||
|
||||
# Submit to Fava
|
||||
result = await fava.add_entry(entry)
|
||||
|
||||
# Return a JournalEntry-like response for compatibility
|
||||
# TODO: Query Fava to get the actual entry back with its hash
|
||||
from .models import EntryLine
|
||||
return JournalEntry(
|
||||
id=f"fava-{datetime.now().timestamp()}", # Temporary ID
|
||||
id=entry_id, # Use the generated castle entry ID
|
||||
description=data.description + description_suffix,
|
||||
entry_date=data.entry_date if data.entry_date else datetime.now(),
|
||||
created_by=wallet.wallet.id,
|
||||
created_at=datetime.now(),
|
||||
reference=data.reference,
|
||||
reference=castle_reference,
|
||||
flag=JournalEntryFlag.PENDING,
|
||||
meta=entry_meta,
|
||||
lines=[
|
||||
EntryLine(
|
||||
id=f"line-1-{datetime.now().timestamp()}",
|
||||
journal_entry_id=f"fava-{datetime.now().timestamp()}",
|
||||
id=f"line-1-{entry_id}",
|
||||
journal_entry_id=entry_id,
|
||||
account_id=expense_account.id,
|
||||
amount=amount_sats,
|
||||
description=f"Expense paid by user {wallet.wallet.user[:8]}",
|
||||
metadata=metadata or {}
|
||||
),
|
||||
EntryLine(
|
||||
id=f"line-2-{datetime.now().timestamp()}",
|
||||
journal_entry_id=f"fava-{datetime.now().timestamp()}",
|
||||
id=f"line-2-{entry_id}",
|
||||
journal_entry_id=entry_id,
|
||||
account_id=user_account.id,
|
||||
amount=-amount_sats,
|
||||
description=f"{'Equity contribution' if data.is_equity else 'Amount owed to user'}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue