Adds settle receivable functionality

Implements a "Settle Receivable" feature for super users to record manual payments from users who owe money.

Introduces a dialog for inputting payment details (amount, method, description, reference), triggers an API call to record the transaction, and updates user balances and transaction history.

This is for non-lightning payments like cash, bank transfers, or checks.
This commit is contained in:
padreug 2025-10-23 02:57:21 +02:00
parent d06f46a63c
commit 1412359172
4 changed files with 278 additions and 1 deletions

View file

@ -59,6 +59,7 @@ from .models import (
ReceivableEntry,
RecordPayment,
RevenueEntry,
SettleReceivable,
UserBalance,
UserWalletSettings,
)
@ -727,6 +728,116 @@ async def api_pay_user(
}
@castle_api_router.post("/api/v1/receivables/settle")
async def api_settle_receivable(
data: SettleReceivable,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> dict:
"""
Manually settle a receivable (record when user pays castle in person).
This endpoint is for non-lightning payments like:
- Cash payments
- Bank transfers
- Other manual settlements
Admin only.
"""
from lnbits.settings import settings as lnbits_settings
if wallet.wallet.user != lnbits_settings.super_user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="Only super user can settle receivables",
)
# Validate payment method
valid_methods = ["cash", "bank_transfer", "check", "other"]
if data.payment_method.lower() not in valid_methods:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Invalid payment method. Must be one of: {', '.join(valid_methods)}",
)
# Get user's receivable account (what user owes)
user_receivable = await get_or_create_user_account(
data.user_id, AccountType.ASSET, "Accounts Receivable"
)
# Get the appropriate asset account based on payment method
payment_account_map = {
"cash": "Cash",
"bank_transfer": "Bank Account",
"check": "Bank Account",
"other": "Cash"
}
account_name = payment_account_map.get(data.payment_method.lower(), "Cash")
payment_account = await get_account_by_name(account_name)
# If account doesn't exist, try to find or create a generic one
if not payment_account:
# Try to find any asset account that's not receivable
all_accounts = await get_all_accounts()
for acc in all_accounts:
if acc.account_type == AccountType.ASSET and "receivable" not in acc.name.lower():
payment_account = acc
break
if not payment_account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Payment account '{account_name}' not found. Please create it first.",
)
# Create journal entry
# DR Cash/Bank (asset increased), CR Accounts Receivable (asset decreased)
# This records that user paid their debt
# Add meta information for audit trail
entry_meta = {
"source": "manual_settlement",
"payment_method": data.payment_method,
"settled_by": wallet.wallet.user,
"payer_user_id": data.user_id,
}
entry_data = CreateJournalEntry(
description=data.description,
reference=data.reference or f"MANUAL-{data.user_id[:8]}",
flag=JournalEntryFlag.CLEARED, # Manual payments are immediately cleared
meta=entry_meta,
lines=[
CreateEntryLine(
account_id=payment_account.id,
debit=data.amount,
credit=0,
description=f"Payment received via {data.payment_method}",
),
CreateEntryLine(
account_id=user_receivable.id,
debit=0,
credit=data.amount,
description="Receivable settled",
),
],
)
entry = await create_journal_entry(entry_data, wallet.wallet.id)
# Get updated balance
balance = await get_user_balance(data.user_id)
return {
"journal_entry_id": entry.id,
"user_id": data.user_id,
"amount_settled": data.amount,
"payment_method": data.payment_method,
"new_balance": balance.balance,
"message": f"Receivable settled successfully via {data.payment_method}",
}
# ===== SETTINGS ENDPOINTS =====