diff --git a/fava_client.py b/fava_client.py index 2c24cc9..c5f88bc 100644 --- a/fava_client.py +++ b/fava_client.py @@ -691,6 +691,9 @@ class FavaClient: """ 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: account_name: Full account name (e.g., "Assets:Receivable:User-abc123") currencies: List of currencies for this account (e.g., ["EUR", "SATS"]) @@ -698,7 +701,7 @@ class FavaClient: metadata: Optional metadata for the account Returns: - Response from Fava ({"data": "Stored 1 entries.", "mtime": "..."}) + Response from Fava ({"data": "new_sha256sum", "mtime": "..."}) Example: # Add a user's receivable account @@ -719,26 +722,67 @@ class FavaClient: if opening_date is None: 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: 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( - f"{self.base_url}/add_entries", - json={"entries": [open_directive]}, + f"{self.base_url}/source", + json=update_payload, headers={"Content-Type": "application/json"} ) response.raise_for_status() 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 except httpx.HTTPStatusError as e: