Fix add_account to use PUT /api/source endpoint
Update FavaClient.add_account() to use PUT /api/source instead of POST /api/add_entries because Fava does not support Open directives via add_entries endpoint. Changes: - Fetch current Beancount source file via GET /api/source - Check if account already exists to avoid duplicates - Format Open directive as plain text (not JSON) - Insert directive after existing Open directives - Update source file via PUT /api/source with sha256sum validation This fixes the issue where Open directives were not being written to the Beancount file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
74115b7e5b
commit
28832d6bfe
1 changed files with 57 additions and 13 deletions
|
|
@ -691,6 +691,9 @@ class FavaClient:
|
||||||
"""
|
"""
|
||||||
Add an account to the Beancount ledger via an Open directive.
|
Add an account to the Beancount ledger via an Open directive.
|
||||||
|
|
||||||
|
NOTE: Fava's /api/add_entries endpoint does NOT support Open directives.
|
||||||
|
This method uses /api/source to directly edit the Beancount file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
account_name: Full account name (e.g., "Assets:Receivable:User-abc123")
|
account_name: Full account name (e.g., "Assets:Receivable:User-abc123")
|
||||||
currencies: List of currencies for this account (e.g., ["EUR", "SATS"])
|
currencies: List of currencies for this account (e.g., ["EUR", "SATS"])
|
||||||
|
|
@ -698,7 +701,7 @@ class FavaClient:
|
||||||
metadata: Optional metadata for the account
|
metadata: Optional metadata for the account
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response from Fava ({"data": "Stored 1 entries.", "mtime": "..."})
|
Response from Fava ({"data": "new_sha256sum", "mtime": "..."})
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
# Add a user's receivable account
|
# Add a user's receivable account
|
||||||
|
|
@ -719,26 +722,67 @@ class FavaClient:
|
||||||
if opening_date is None:
|
if opening_date is None:
|
||||||
opening_date = date_type.today()
|
opening_date = date_type.today()
|
||||||
|
|
||||||
# Format Open directive for Fava
|
|
||||||
open_directive = {
|
|
||||||
"t": "Open",
|
|
||||||
"date": opening_date.isoformat(),
|
|
||||||
"account": account_name,
|
|
||||||
"currencies": currencies,
|
|
||||||
"meta": metadata or {}
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||||
|
# Step 1: Get current source file
|
||||||
|
response = await client.get(f"{self.base_url}/source")
|
||||||
|
response.raise_for_status()
|
||||||
|
source_data = response.json()["data"]
|
||||||
|
|
||||||
|
file_path = source_data["file_path"]
|
||||||
|
sha256sum = source_data["sha256sum"]
|
||||||
|
source = source_data["source"]
|
||||||
|
|
||||||
|
# Step 2: Check if account already exists
|
||||||
|
if f"open {account_name}" in source:
|
||||||
|
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)
|
||||||
|
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:
|
||||||
|
insert_index = i + 1
|
||||||
|
|
||||||
|
# Step 4: Format Open directive as Beancount text
|
||||||
|
currencies_str = ", ".join(currencies)
|
||||||
|
open_lines = [
|
||||||
|
"",
|
||||||
|
f"{opening_date.isoformat()} open {account_name} {currencies_str}"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add metadata if provided
|
||||||
|
if metadata:
|
||||||
|
for key, value in metadata.items():
|
||||||
|
# Format metadata with proper indentation
|
||||||
|
if isinstance(value, str):
|
||||||
|
open_lines.append(f' {key}: "{value}"')
|
||||||
|
else:
|
||||||
|
open_lines.append(f' {key}: {value}')
|
||||||
|
|
||||||
|
# Step 5: Insert into source
|
||||||
|
for i, line in enumerate(open_lines):
|
||||||
|
lines.insert(insert_index + i, line)
|
||||||
|
|
||||||
|
new_source = '\n'.join(lines)
|
||||||
|
|
||||||
|
# Step 6: Update source file via PUT /api/source
|
||||||
|
update_payload = {
|
||||||
|
"file_path": file_path,
|
||||||
|
"source": new_source,
|
||||||
|
"sha256sum": sha256sum
|
||||||
|
}
|
||||||
|
|
||||||
response = await client.put(
|
response = await client.put(
|
||||||
f"{self.base_url}/add_entries",
|
f"{self.base_url}/source",
|
||||||
json={"entries": [open_directive]},
|
json=update_payload,
|
||||||
headers={"Content-Type": "application/json"}
|
headers={"Content-Type": "application/json"}
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
result = response.json()
|
result = response.json()
|
||||||
|
|
||||||
logger.info(f"Added account {account_name} to Fava with currencies {currencies}")
|
logger.info(f"Added account {account_name} to Beancount file with currencies {currencies}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue