Add settlement links to payment entries for traceability
- Add settled_entry_links parameter to format_payment_entry and format_net_settlement_entry - Query unsettled expenses/receivables before creating settlement entries - Pass original entry links to format functions so settlements reference what they settle - Update all callers in views_api.py (5 locations) and tasks.py (1 location) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
da74e668c8
commit
e403ec223d
3 changed files with 59 additions and 9 deletions
|
|
@ -497,7 +497,8 @@ def format_payment_entry(
|
||||||
fiat_currency: Optional[str] = None,
|
fiat_currency: Optional[str] = None,
|
||||||
fiat_amount: Optional[Decimal] = None,
|
fiat_amount: Optional[Decimal] = None,
|
||||||
payment_hash: Optional[str] = None,
|
payment_hash: Optional[str] = None,
|
||||||
reference: Optional[str] = None
|
reference: Optional[str] = None,
|
||||||
|
settled_entry_links: Optional[List[str]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Format a payment entry (Lightning payment recorded).
|
Format a payment entry (Lightning payment recorded).
|
||||||
|
|
@ -516,6 +517,7 @@ def format_payment_entry(
|
||||||
fiat_amount: Optional fiat amount (unsigned)
|
fiat_amount: Optional fiat amount (unsigned)
|
||||||
payment_hash: Lightning payment hash
|
payment_hash: Lightning payment hash
|
||||||
reference: Optional reference
|
reference: Optional reference
|
||||||
|
settled_entry_links: List of expense/receivable links being settled (e.g., ["exp-abc123"])
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Fava API entry dict
|
Fava API entry dict
|
||||||
|
|
@ -584,6 +586,8 @@ def format_payment_entry(
|
||||||
entry_meta["payment-hash"] = payment_hash
|
entry_meta["payment-hash"] = payment_hash
|
||||||
|
|
||||||
links = []
|
links = []
|
||||||
|
if settled_entry_links:
|
||||||
|
links.extend(settled_entry_links)
|
||||||
if reference:
|
if reference:
|
||||||
links.append(reference)
|
links.append(reference)
|
||||||
if payment_hash:
|
if payment_hash:
|
||||||
|
|
@ -594,7 +598,7 @@ def format_payment_entry(
|
||||||
flag="*", # Cleared (payment already happened)
|
flag="*", # Cleared (payment already happened)
|
||||||
narration=description,
|
narration=description,
|
||||||
postings=postings,
|
postings=postings,
|
||||||
tags=["lightning-payment"],
|
tags=["lightning-payment", "settlement"],
|
||||||
links=links,
|
links=links,
|
||||||
meta=entry_meta
|
meta=entry_meta
|
||||||
)
|
)
|
||||||
|
|
@ -713,7 +717,8 @@ def format_net_settlement_entry(
|
||||||
description: str,
|
description: str,
|
||||||
entry_date: date,
|
entry_date: date,
|
||||||
payment_hash: Optional[str] = None,
|
payment_hash: Optional[str] = None,
|
||||||
reference: Optional[str] = None
|
reference: Optional[str] = None,
|
||||||
|
settled_entry_links: Optional[List[str]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Format a net settlement payment entry (user paying net balance).
|
Format a net settlement payment entry (user paying net balance).
|
||||||
|
|
@ -743,6 +748,7 @@ def format_net_settlement_entry(
|
||||||
entry_date: Date of payment
|
entry_date: Date of payment
|
||||||
payment_hash: Lightning payment hash
|
payment_hash: Lightning payment hash
|
||||||
reference: Optional reference
|
reference: Optional reference
|
||||||
|
settled_entry_links: List of expense/receivable links being settled (e.g., ["exp-abc123", "rcv-def456"])
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Fava API entry dict
|
Fava API entry dict
|
||||||
|
|
@ -780,6 +786,8 @@ def format_net_settlement_entry(
|
||||||
entry_meta["payment-hash"] = payment_hash
|
entry_meta["payment-hash"] = payment_hash
|
||||||
|
|
||||||
links = []
|
links = []
|
||||||
|
if settled_entry_links:
|
||||||
|
links.extend(settled_entry_links)
|
||||||
if reference:
|
if reference:
|
||||||
links.append(reference)
|
links.append(reference)
|
||||||
if payment_hash:
|
if payment_hash:
|
||||||
|
|
|
||||||
15
tasks.py
15
tasks.py
|
|
@ -287,6 +287,18 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
logger.error("Lightning account 'Assets:Bitcoin:Lightning' not found")
|
logger.error("Lightning account 'Assets:Bitcoin:Lightning' not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Query for unsettled entries to link this settlement back to them
|
||||||
|
# Net settlement can settle both expenses and receivables
|
||||||
|
settled_links = []
|
||||||
|
try:
|
||||||
|
unsettled_expenses = await fava.get_unsettled_entries_bql(user_id, "expense")
|
||||||
|
settled_links.extend([e["link"] for e in unsettled_expenses if e.get("link")])
|
||||||
|
unsettled_receivables = await fava.get_unsettled_entries_bql(user_id, "receivable")
|
||||||
|
settled_links.extend([e["link"] for e in unsettled_receivables if e.get("link")])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not query unsettled entries for settlement links: {e}")
|
||||||
|
# Continue without links - settlement will still be recorded
|
||||||
|
|
||||||
# Format as net settlement transaction
|
# Format as net settlement transaction
|
||||||
entry = format_net_settlement_entry(
|
entry = format_net_settlement_entry(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|
@ -301,7 +313,8 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
description=f"Lightning payment settlement from user {user_id[:8]}",
|
description=f"Lightning payment settlement from user {user_id[:8]}",
|
||||||
entry_date=datetime.now().date(),
|
entry_date=datetime.now().date(),
|
||||||
payment_hash=payment.payment_hash,
|
payment_hash=payment.payment_hash,
|
||||||
reference=payment.payment_hash
|
reference=payment.payment_hash,
|
||||||
|
settled_entry_links=settled_links if settled_links else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# Submit to Fava
|
# Submit to Fava
|
||||||
|
|
|
||||||
39
views_api.py
39
views_api.py
|
|
@ -1704,6 +1704,10 @@ async def api_record_payment(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Lightning account not found"
|
status_code=HTTPStatus.NOT_FOUND, detail="Lightning account not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get unsettled receivable entries to link to this settlement
|
||||||
|
unsettled = await fava.get_unsettled_entries_bql(target_user_id, "receivable")
|
||||||
|
settled_links = [e["link"] for e in unsettled if e.get("link")]
|
||||||
|
|
||||||
# Format payment entry and submit to Fava
|
# Format payment entry and submit to Fava
|
||||||
entry = format_payment_entry(
|
entry = format_payment_entry(
|
||||||
user_id=target_user_id,
|
user_id=target_user_id,
|
||||||
|
|
@ -1716,7 +1720,8 @@ async def api_record_payment(
|
||||||
fiat_currency=fiat_currency,
|
fiat_currency=fiat_currency,
|
||||||
fiat_amount=fiat_amount,
|
fiat_amount=fiat_amount,
|
||||||
payment_hash=data.payment_hash,
|
payment_hash=data.payment_hash,
|
||||||
reference=data.payment_hash
|
reference=data.payment_hash,
|
||||||
|
settled_entry_links=settled_links
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Formatted payment entry: {entry}")
|
logger.info(f"Formatted payment entry: {entry}")
|
||||||
|
|
@ -1764,6 +1769,10 @@ async def api_pay_user(
|
||||||
|
|
||||||
fava = get_fava_client()
|
fava = get_fava_client()
|
||||||
|
|
||||||
|
# Get unsettled expense entries to link to this settlement
|
||||||
|
unsettled = await fava.get_unsettled_entries_bql(user_id, "expense")
|
||||||
|
settled_links = [e["link"] for e in unsettled if e.get("link")]
|
||||||
|
|
||||||
entry = format_payment_entry(
|
entry = format_payment_entry(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
payment_account=lightning_account.name,
|
payment_account=lightning_account.name,
|
||||||
|
|
@ -1772,7 +1781,8 @@ async def api_pay_user(
|
||||||
description=f"Payment to user {user_id[:8]}",
|
description=f"Payment to user {user_id[:8]}",
|
||||||
entry_date=datetime.now().date(),
|
entry_date=datetime.now().date(),
|
||||||
is_payable=True, # Castle paying user
|
is_payable=True, # Castle paying user
|
||||||
reference=f"PAY-{user_id[:8]}"
|
reference=f"PAY-{user_id[:8]}",
|
||||||
|
settled_entry_links=settled_links
|
||||||
)
|
)
|
||||||
|
|
||||||
# Submit to Fava
|
# Submit to Fava
|
||||||
|
|
@ -1897,6 +1907,12 @@ async def api_settle_receivable(
|
||||||
fiat_currency = data.currency.upper() if data.currency else None
|
fiat_currency = data.currency.upper() if data.currency else None
|
||||||
fiat_amount = Decimal(str(data.amount)) if data.currency else None
|
fiat_amount = Decimal(str(data.amount)) if data.currency else None
|
||||||
|
|
||||||
|
# Get settled entry links (use provided or auto-query unsettled)
|
||||||
|
settled_links = data.settled_entry_links
|
||||||
|
if not settled_links:
|
||||||
|
unsettled = await fava.get_unsettled_entries_bql(data.user_id, "receivable")
|
||||||
|
settled_links = [e["link"] for e in unsettled if e.get("link")]
|
||||||
|
|
||||||
entry = format_payment_entry(
|
entry = format_payment_entry(
|
||||||
user_id=data.user_id,
|
user_id=data.user_id,
|
||||||
payment_account=payment_account.name,
|
payment_account=payment_account.name,
|
||||||
|
|
@ -1908,7 +1924,8 @@ async def api_settle_receivable(
|
||||||
fiat_currency=fiat_currency,
|
fiat_currency=fiat_currency,
|
||||||
fiat_amount=fiat_amount,
|
fiat_amount=fiat_amount,
|
||||||
payment_hash=data.payment_hash,
|
payment_hash=data.payment_hash,
|
||||||
reference=data.reference or f"MANUAL-{data.user_id[:8]}"
|
reference=data.reference or f"MANUAL-{data.user_id[:8]}",
|
||||||
|
settled_entry_links=settled_links
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add additional metadata to entry
|
# Add additional metadata to entry
|
||||||
|
|
@ -2051,6 +2068,12 @@ async def api_pay_user(
|
||||||
fiat_currency = None
|
fiat_currency = None
|
||||||
fiat_amount = None
|
fiat_amount = None
|
||||||
|
|
||||||
|
# Get settled entry links (use provided or auto-query unsettled)
|
||||||
|
settled_links = data.settled_entry_links
|
||||||
|
if not settled_links:
|
||||||
|
unsettled = await fava.get_unsettled_entries_bql(data.user_id, "expense")
|
||||||
|
settled_links = [e["link"] for e in unsettled if e.get("link")]
|
||||||
|
|
||||||
entry = format_payment_entry(
|
entry = format_payment_entry(
|
||||||
user_id=data.user_id,
|
user_id=data.user_id,
|
||||||
payment_account=payment_account.name,
|
payment_account=payment_account.name,
|
||||||
|
|
@ -2062,7 +2085,8 @@ async def api_pay_user(
|
||||||
fiat_currency=fiat_currency,
|
fiat_currency=fiat_currency,
|
||||||
fiat_amount=fiat_amount,
|
fiat_amount=fiat_amount,
|
||||||
payment_hash=data.payment_hash,
|
payment_hash=data.payment_hash,
|
||||||
reference=data.reference or f"PAY-{data.user_id[:8]}"
|
reference=data.reference or f"PAY-{data.user_id[:8]}",
|
||||||
|
settled_entry_links=settled_links
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add additional metadata to entry
|
# Add additional metadata to entry
|
||||||
|
|
@ -2550,6 +2574,10 @@ async def api_approve_manual_payment_request(
|
||||||
|
|
||||||
fava = get_fava_client()
|
fava = get_fava_client()
|
||||||
|
|
||||||
|
# Get unsettled expense entries to link to this settlement
|
||||||
|
unsettled = await fava.get_unsettled_entries_bql(request.user_id, "expense")
|
||||||
|
settled_links = [e["link"] for e in unsettled if e.get("link")]
|
||||||
|
|
||||||
entry = format_payment_entry(
|
entry = format_payment_entry(
|
||||||
user_id=request.user_id,
|
user_id=request.user_id,
|
||||||
payment_account=lightning_account.name,
|
payment_account=lightning_account.name,
|
||||||
|
|
@ -2558,7 +2586,8 @@ async def api_approve_manual_payment_request(
|
||||||
description=f"Manual payment to user: {request.description}",
|
description=f"Manual payment to user: {request.description}",
|
||||||
entry_date=datetime.now().date(),
|
entry_date=datetime.now().date(),
|
||||||
is_payable=True, # Castle paying user
|
is_payable=True, # Castle paying user
|
||||||
reference=f"MPR-{request.id}"
|
reference=f"MPR-{request.id}",
|
||||||
|
settled_entry_links=settled_links
|
||||||
)
|
)
|
||||||
|
|
||||||
# Submit to Fava
|
# Submit to Fava
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue