Improves Beancount entry generation and sanitization
Adds a function to sanitize strings for use as Beancount links, ensuring compatibility with Beancount's link restrictions. Refactors the journal entry creation process to use EUR-based postings when fiat currency is provided, improving accuracy and consistency. The legacy SATS-based fallback is retained for cases without fiat currency information. Adjusts reference generation for Beancount entries using the sanitized description.
This commit is contained in:
parent
0e93fc5ffc
commit
a6b67b7416
2 changed files with 57 additions and 11 deletions
|
|
@ -238,6 +238,30 @@ class AccountLookup:
|
||||||
|
|
||||||
# ===== CONVERSION FUNCTIONS =====
|
# ===== CONVERSION FUNCTIONS =====
|
||||||
|
|
||||||
|
def sanitize_link(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Sanitize a string to make it valid for Beancount links.
|
||||||
|
|
||||||
|
Beancount links can only contain: A-Z, a-z, 0-9, -, _, /, .
|
||||||
|
All other characters are replaced with hyphens.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> sanitize_link("Test (pending)")
|
||||||
|
'Test-pending'
|
||||||
|
>>> sanitize_link("Invoice #123")
|
||||||
|
'Invoice-123'
|
||||||
|
>>> sanitize_link("import-20250623-Action Ressourcerie")
|
||||||
|
'import-20250623-Action-Ressourcerie'
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
# Replace any character that's not alphanumeric, dash, underscore, slash, or period with a hyphen
|
||||||
|
sanitized = re.sub(r'[^A-Za-z0-9\-_/.]', '-', text)
|
||||||
|
# Remove consecutive hyphens
|
||||||
|
sanitized = re.sub(r'-+', '-', sanitized)
|
||||||
|
# Remove leading/trailing hyphens
|
||||||
|
sanitized = sanitized.strip('-')
|
||||||
|
return sanitized
|
||||||
|
|
||||||
def eur_to_sats(eur_amount: Decimal, btc_eur_rate: float) -> int:
|
def eur_to_sats(eur_amount: Decimal, btc_eur_rate: float) -> int:
|
||||||
"""Convert EUR to satoshis using BTC/EUR rate"""
|
"""Convert EUR to satoshis using BTC/EUR rate"""
|
||||||
btc_amount = eur_amount / Decimal(str(btc_eur_rate))
|
btc_amount = eur_amount / Decimal(str(btc_eur_rate))
|
||||||
|
|
@ -454,10 +478,13 @@ def convert_to_castle_entry(parsed: dict, btc_eur_rate: float, account_lookup: A
|
||||||
"metadata": metadata
|
"metadata": metadata
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Create sanitized reference link
|
||||||
|
desc_part = sanitize_link(parsed['description'][:30])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"description": parsed['description'],
|
"description": parsed['description'],
|
||||||
"entry_date": parsed['date'].isoformat(),
|
"entry_date": parsed['date'].isoformat(),
|
||||||
"reference": f"import-{parsed['date'].strftime('%Y%m%d')}-{parsed['description'][:20].replace(' ', '-')}",
|
"reference": f"import-{parsed['date'].strftime('%Y%m%d')}-{desc_part}",
|
||||||
"flag": "*",
|
"flag": "*",
|
||||||
"meta": {
|
"meta": {
|
||||||
"source": "beancount_import",
|
"source": "beancount_import",
|
||||||
|
|
|
||||||
39
views_api.py
39
views_api.py
|
|
@ -611,19 +611,38 @@ async def api_create_journal_entry(
|
||||||
fiat_amount_str = line.metadata.get("fiat_amount")
|
fiat_amount_str = line.metadata.get("fiat_amount")
|
||||||
fiat_amount = Decimal(fiat_amount_str) if fiat_amount_str else None
|
fiat_amount = Decimal(fiat_amount_str) if fiat_amount_str else None
|
||||||
|
|
||||||
# Create posting metadata (excluding fiat fields that go in cost basis)
|
# Create posting metadata (excluding fiat fields that are used for primary amount)
|
||||||
posting_metadata = {k: v for k, v in line.metadata.items()
|
posting_metadata = {k: v for k, v in line.metadata.items()
|
||||||
if k not in ["fiat_currency", "fiat_amount"]}
|
if k not in ["fiat_currency", "fiat_amount"]}
|
||||||
if line.description:
|
|
||||||
posting_metadata["description"] = line.description
|
|
||||||
|
|
||||||
posting = format_posting_with_cost(
|
# If fiat currency is provided, use EUR-based format (primary amount in EUR, sats in metadata)
|
||||||
account=account.name,
|
# Otherwise, use SATS-based format
|
||||||
amount_sats=line.amount,
|
if fiat_currency and fiat_amount:
|
||||||
fiat_currency=fiat_currency,
|
# EUR-based posting (current architecture)
|
||||||
fiat_amount=abs(fiat_amount) if fiat_amount else None,
|
posting_metadata["sats-equivalent"] = str(abs(line.amount))
|
||||||
metadata=posting_metadata if posting_metadata else None
|
|
||||||
)
|
# Apply the sign from line.amount to fiat_amount
|
||||||
|
# line.amount is positive for debits, negative for credits
|
||||||
|
signed_fiat_amount = fiat_amount if line.amount >= 0 else -fiat_amount
|
||||||
|
|
||||||
|
posting = {
|
||||||
|
"account": account.name,
|
||||||
|
"amount": f"{signed_fiat_amount:.2f} {fiat_currency}",
|
||||||
|
"meta": posting_metadata if posting_metadata else None
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# SATS-based posting (legacy/fallback)
|
||||||
|
if line.description:
|
||||||
|
posting_metadata["description"] = line.description
|
||||||
|
|
||||||
|
posting = format_posting_with_cost(
|
||||||
|
account=account.name,
|
||||||
|
amount_sats=line.amount,
|
||||||
|
fiat_currency=None,
|
||||||
|
fiat_amount=None,
|
||||||
|
metadata=posting_metadata if posting_metadata else None
|
||||||
|
)
|
||||||
|
|
||||||
postings.append(posting)
|
postings.append(posting)
|
||||||
|
|
||||||
# Extract tags and links from meta
|
# Extract tags and links from meta
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue