Creates accounts in Fava if they don't exist

This change ensures that user-specific accounts are automatically created
in the Fava/Beancount ledger when they are first requested. It checks for
the existence of the account via a Fava query and creates it via an Open
directive if it's missing.  This simplifies account management and
ensures that all necessary accounts are available for transactions.

This implementation adds a new `add_account` method to the `FavaClient`
class which makes use of the /add_entries endpoint to create an account
using an Open Directive.
This commit is contained in:
padreug 2025-11-10 15:56:22 +01:00
parent 51ae2e8e47
commit b6886793ee
2 changed files with 113 additions and 2 deletions

46
crud.py
View file

@ -2,6 +2,7 @@ import json
from datetime import datetime
from typing import Optional
import httpx
from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash
@ -96,6 +97,10 @@ async def get_or_create_user_account(
"""
Get or create a user-specific account with hierarchical naming.
This function checks if the account exists in Fava/Beancount and creates it
if it doesn't exist. The account is also registered in Castle's database for
metadata tracking (permissions, descriptions, etc.).
Examples:
get_or_create_user_account("af983632", AccountType.ASSET, "Accounts Receivable")
"Assets:Receivable:User-af983632"
@ -104,11 +109,13 @@ async def get_or_create_user_account(
"Liabilities:Payable:User-af983632"
"""
from .account_utils import format_hierarchical_account_name
from .fava_client import get_fava_client
from loguru import logger
# Generate hierarchical account name
account_name = format_hierarchical_account_name(account_type, base_name, user_id)
# Try to find existing account with this hierarchical name
# Try to find existing account with this hierarchical name in Castle DB
account = await db.fetchone(
"""
SELECT * FROM accounts
@ -119,7 +126,42 @@ async def get_or_create_user_account(
)
if not account:
# Create new account with hierarchical name
# Check if account exists in Fava/Beancount
fava = get_fava_client()
try:
# Query Fava for this account
query = f"SELECT account WHERE account = '{account_name}'"
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(
f"{fava.base_url}/query",
params={"query_string": query}
)
response.raise_for_status()
result = response.json()
# Check if account exists in Fava
fava_has_account = len(result.get("data", {}).get("rows", [])) > 0
if not fava_has_account:
# Create account in Fava/Beancount via Open directive
logger.info(f"Creating account in Fava: {account_name}")
await fava.add_account(
account_name=account_name,
currencies=["EUR", "SATS", "USD"], # Support common currencies
metadata={
"user_id": user_id,
"description": f"User-specific {account_type.value} account"
}
)
logger.info(f"Created account in Fava: {account_name}")
else:
logger.info(f"Account already exists in Fava: {account_name}")
except Exception as e:
logger.warning(f"Could not check/create account in Fava: {e}")
# Continue anyway - account creation in Castle DB is still useful for metadata
# Create account in Castle DB for metadata tracking
account = await create_account(
CreateAccount(
name=account_name,