diff --git a/fava_client.py b/fava_client.py index 3c9861d..ab8c57a 100644 --- a/fava_client.py +++ b/fava_client.py @@ -652,7 +652,7 @@ class FavaClient: async with httpx.AsyncClient(timeout=self.timeout) as client: response = await client.delete( f"{self.base_url}/source_slice", - json={ + params={ "entry_hash": entry_hash, "sha256sum": sha256sum } diff --git a/views_api.py b/views_api.py index bf72ee8..a5f54ca 100644 --- a/views_api.py +++ b/views_api.py @@ -2017,7 +2017,10 @@ async def api_reject_expense_entry( wallet: WalletTypeInfo = Depends(require_admin_key), ) -> dict: """ - Reject a pending expense entry by deleting it from the Beancount file (admin only). + Reject a pending expense entry by marking it as voided (admin only). + + Adds #voided tag for audit trail while keeping the '!' flag. + Voided transactions are excluded from balances but preserved in the ledger. """ from lnbits.settings import settings as lnbits_settings from .fava_client import get_fava_client @@ -2058,18 +2061,47 @@ async def api_reject_expense_entry( detail=f"Pending entry {entry_id} not found in Beancount ledger" ) - # 3. Get the entry context for sha256sum + # 3. Get the entry context (source text + sha256sum) context = await fava.get_entry_context(target_entry_hash) + source = context.get("slice", "") sha256sum = context.get("sha256sum", "") - # 4. Delete the entry - result = await fava.delete_entry(target_entry_hash, sha256sum) + if not source: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Could not retrieve entry source from Fava" + ) + + # 4. Add #voided tag (keep ! flag as per convention) + date_str = target_entry.get("date", "") + + # Add #voided tag if not already present + if "#voided" not in source: + # Find the transaction line and add #voided to the tags + # Pattern: date ! "narration" #existing-tags + lines = source.split('\n') + for i, line in enumerate(lines): + if date_str in line and '"' in line and '!' in line: + # Add #voided tag to the transaction line + if '#' in line: + # Already has tags, append voided + lines[i] = line.rstrip() + ' #voided' + else: + # No tags yet, add after narration + lines[i] = line.rstrip() + ' #voided' + break + new_source = '\n'.join(lines) + else: + new_source = source + + # 5. Update the entry via Fava API + await fava.update_entry_source(target_entry_hash, new_source, sha256sum) return { - "message": f"Entry {entry_id} rejected and deleted successfully", + "message": f"Entry {entry_id} rejected (marked as voided)", "entry_id": entry_id, "entry_hash": target_entry_hash, - "date": target_entry.get("date", ""), + "date": date_str, "description": target_entry.get("narration", "") }