Fixes user account creation in Fava/Beancount
This commit fixes two critical bugs in the user account creation flow:
1. **Always check/create in Fava regardless of Castle DB status**
- Previously, if an account existed in Castle DB, the function would
return early without checking if the Open directive existed in Fava
- This caused accounts to exist in Castle DB but not in Beancount
- Now we always check Fava and create Open directives if needed
2. **Fix Open directive insertion to preserve metadata**
- The insertion logic now skips over metadata lines when finding
the insertion point
- Prevents new Open directives from being inserted between existing
directives and their metadata, which was causing orphaned metadata
3. **Add comprehensive logging**
- Added detailed logging with [ACCOUNT CHECK], [FAVA CHECK],
[FAVA CREATE], [CASTLE DB], and [WALLET UPDATE] prefixes
- Makes it easier to trace account creation flow and debug issues
4. **Fix Fava filename handling**
- Now queries /api/options to get the Beancount file path dynamically
- Fixes "Parameter 'filename' is missing" errors with /api/source
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a3c3e44e5f
commit
538751f21a
3 changed files with 45 additions and 13 deletions
27
crud.py
27
crud.py
|
|
@ -125,7 +125,12 @@ async def get_or_create_user_account(
|
|||
Account,
|
||||
)
|
||||
|
||||
if not account:
|
||||
logger.info(f"[ACCOUNT CHECK] User {user_id[:8]}, Account: {account_name}, In Castle DB: {account is not None}")
|
||||
|
||||
# Always check/create in Fava, even if account exists in Castle DB
|
||||
# This ensures Beancount has the Open directive
|
||||
fava_account_exists = False
|
||||
if True: # Always check Fava
|
||||
# Check if account exists in Fava/Beancount
|
||||
fava = get_fava_client()
|
||||
try:
|
||||
|
|
@ -140,11 +145,12 @@ async def get_or_create_user_account(
|
|||
result = response.json()
|
||||
|
||||
# Check if account exists in Fava
|
||||
fava_has_account = len(result.get("data", {}).get("rows", [])) > 0
|
||||
fava_account_exists = len(result.get("data", {}).get("rows", [])) > 0
|
||||
logger.info(f"[FAVA CHECK] Account {account_name} exists in Fava: {fava_account_exists}")
|
||||
|
||||
if not fava_has_account:
|
||||
if not fava_account_exists:
|
||||
# Create account in Fava/Beancount via Open directive
|
||||
logger.info(f"Creating account in Fava: {account_name}")
|
||||
logger.info(f"[FAVA CREATE] Creating account in Fava: {account_name}")
|
||||
await fava.add_account(
|
||||
account_name=account_name,
|
||||
currencies=["EUR", "SATS", "USD"], # Support common currencies
|
||||
|
|
@ -153,15 +159,15 @@ async def get_or_create_user_account(
|
|||
"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}")
|
||||
logger.info(f"[FAVA CREATE] Successfully created account in Fava: {account_name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not check/create account in Fava: {e}")
|
||||
logger.error(f"[FAVA ERROR] Could not check/create account in Fava: {e}", exc_info=True)
|
||||
# Continue anyway - account creation in Castle DB is still useful for metadata
|
||||
|
||||
# Create account in Castle DB for metadata tracking
|
||||
# Create account in Castle DB for metadata tracking (only if it doesn't exist)
|
||||
if not account:
|
||||
logger.info(f"[CASTLE DB] Creating account in Castle DB: {account_name}")
|
||||
account = await create_account(
|
||||
CreateAccount(
|
||||
name=account_name,
|
||||
|
|
@ -170,6 +176,9 @@ async def get_or_create_user_account(
|
|||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
logger.info(f"[CASTLE DB] Created account in Castle DB: {account_name}")
|
||||
else:
|
||||
logger.info(f"[CASTLE DB] Account already exists in Castle DB: {account_name}")
|
||||
|
||||
return account
|
||||
|
||||
|
|
|
|||
|
|
@ -724,12 +724,22 @@ class FavaClient:
|
|||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
# Step 1: Get current source file
|
||||
response = await client.get(f"{self.base_url}/source")
|
||||
# Step 1: Get the main Beancount file path from Fava
|
||||
options_response = await client.get(f"{self.base_url}/options")
|
||||
options_response.raise_for_status()
|
||||
options_data = options_response.json()["data"]
|
||||
file_path = options_data["beancount_options"]["filename"]
|
||||
|
||||
logger.debug(f"Fava main file: {file_path}")
|
||||
|
||||
# Step 2: Get current source file
|
||||
response = await client.get(
|
||||
f"{self.base_url}/source",
|
||||
params={"filename": file_path}
|
||||
)
|
||||
response.raise_for_status()
|
||||
source_data = response.json()["data"]
|
||||
|
||||
file_path = source_data["file_path"]
|
||||
sha256sum = source_data["sha256sum"]
|
||||
source = source_data["source"]
|
||||
|
||||
|
|
@ -738,12 +748,16 @@ class FavaClient:
|
|||
logger.info(f"Account {account_name} already exists in Beancount file")
|
||||
return {"data": sha256sum, "mtime": source_data.get("mtime", "")}
|
||||
|
||||
# Step 3: Find insertion point (after last Open directive)
|
||||
# Step 3: Find insertion point (after last Open directive AND its metadata)
|
||||
lines = source.split('\n')
|
||||
insert_index = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith(('open ', f'{opening_date.year}-')) and 'open' in line:
|
||||
# Found an Open directive, now skip over any metadata lines
|
||||
insert_index = i + 1
|
||||
# Skip metadata lines (lines starting with whitespace)
|
||||
while insert_index < len(lines) and lines[insert_index].startswith((' ', '\t')) and lines[insert_index].strip():
|
||||
insert_index += 1
|
||||
|
||||
# Step 4: Format Open directive as Beancount text
|
||||
currencies_str = ", ".join(currencies)
|
||||
|
|
|
|||
|
|
@ -37,19 +37,28 @@ async def get_user_wallet(user_id: str) -> UserWalletSettings:
|
|||
async def update_user_wallet(
|
||||
user_id: str, data: UserWalletSettings
|
||||
) -> UserWalletSettings:
|
||||
from loguru import logger
|
||||
|
||||
logger.info(f"[WALLET UPDATE] Starting update_user_wallet for user {user_id[:8]}")
|
||||
|
||||
settings = await get_user_wallet_settings(user_id)
|
||||
if not settings:
|
||||
logger.info(f"[WALLET UPDATE] Creating new wallet settings for user {user_id[:8]}")
|
||||
settings = await create_user_wallet_settings(user_id, data)
|
||||
else:
|
||||
logger.info(f"[WALLET UPDATE] Updating existing wallet settings for user {user_id[:8]}")
|
||||
settings = await update_user_wallet_settings(user_id, data)
|
||||
|
||||
# Proactively create core user accounts when wallet is configured
|
||||
# This ensures all users have a consistent account structure from the start
|
||||
logger.info(f"[WALLET UPDATE] Creating LIABILITY account for user {user_id[:8]}")
|
||||
await get_or_create_user_account(
|
||||
user_id, AccountType.LIABILITY, "Accounts Payable"
|
||||
)
|
||||
logger.info(f"[WALLET UPDATE] Creating ASSET account for user {user_id[:8]}")
|
||||
await get_or_create_user_account(
|
||||
user_id, AccountType.ASSET, "Accounts Receivable"
|
||||
)
|
||||
logger.info(f"[WALLET UPDATE] Completed update_user_wallet for user {user_id[:8]}")
|
||||
|
||||
return settings
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue