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:
parent
cfca10b782
commit
1362ada362
2 changed files with 39 additions and 7 deletions
|
|
@ -652,7 +652,7 @@ class FavaClient:
|
||||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||||
response = await client.delete(
|
response = await client.delete(
|
||||||
f"{self.base_url}/source_slice",
|
f"{self.base_url}/source_slice",
|
||||||
json={
|
params={
|
||||||
"entry_hash": entry_hash,
|
"entry_hash": entry_hash,
|
||||||
"sha256sum": sha256sum
|
"sha256sum": sha256sum
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
views_api.py
44
views_api.py
|
|
@ -2017,7 +2017,10 @@ async def api_reject_expense_entry(
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
) -> dict:
|
) -> 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 lnbits.settings import settings as lnbits_settings
|
||||||
from .fava_client import get_fava_client
|
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"
|
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)
|
context = await fava.get_entry_context(target_entry_hash)
|
||||||
|
source = context.get("slice", "")
|
||||||
sha256sum = context.get("sha256sum", "")
|
sha256sum = context.get("sha256sum", "")
|
||||||
|
|
||||||
# 4. Delete the entry
|
if not source:
|
||||||
result = await fava.delete_entry(target_entry_hash, sha256sum)
|
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 {
|
return {
|
||||||
"message": f"Entry {entry_id} rejected and deleted successfully",
|
"message": f"Entry {entry_id} rejected (marked as voided)",
|
||||||
"entry_id": entry_id,
|
"entry_id": entry_id,
|
||||||
"entry_hash": target_entry_hash,
|
"entry_hash": target_entry_hash,
|
||||||
"date": target_entry.get("date", ""),
|
"date": date_str,
|
||||||
"description": target_entry.get("narration", "")
|
"description": target_entry.get("narration", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue