Rejects pending expense entries by voiding them

Instead of deleting pending expense entries, marks them as voided by adding a #voided tag.
This ensures an audit trail while excluding them from balances.

Updates the Fava client to use 'params' for the delete request.
This commit is contained in:
padreug 2025-11-10 00:35:41 +01:00
parent cfca10b782
commit 1362ada362
2 changed files with 39 additions and 7 deletions

View file

@ -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", "")
}