Extends account lookup for user accounts
Implements account lookup logic for user-specific accounts, specifically Liabilities:Payable and Assets:Receivable. This allows the system to automatically map Beancount accounts to corresponding accounts in the Castle system based on user ID. Improves error messages when user accounts are not properly configured.
This commit is contained in:
parent
992a8fe554
commit
4b327a0aab
1 changed files with 58 additions and 27 deletions
|
|
@ -150,32 +150,58 @@ class AccountLookup:
|
||||||
"""
|
"""
|
||||||
Get Castle account ID for a Beancount account name.
|
Get Castle account ID for a Beancount account name.
|
||||||
|
|
||||||
Special handling for Equity accounts:
|
Special handling for user-specific accounts:
|
||||||
- "Equity:Pat" -> looks up Pat's user_id and finds their equity account
|
- "Liabilities:Payable:Pat" -> looks up Pat's user_id and finds their Castle payable account
|
||||||
|
- "Assets:Receivable:Pat" -> looks up Pat's user_id and finds their Castle receivable account
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
account_name: Beancount account name (e.g., "Expenses:Food:Supplies" or "Equity:Pat")
|
account_name: Beancount account name (e.g., "Expenses:Food:Supplies", "Liabilities:Payable:Pat", "Assets:Receivable:Pat")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Castle account UUID or None if not found
|
Castle account UUID or None if not found
|
||||||
"""
|
"""
|
||||||
# Check if this is an Equity:<name> account
|
# Check if this is a Liabilities:Payable:<name> account
|
||||||
if account_name.startswith("Equity:"):
|
# Map Beancount Liabilities:Payable:Pat to Castle Liabilities:Payable:User-<id>
|
||||||
user_name = extract_user_from_equity_account(account_name)
|
if account_name.startswith("Liabilities:Payable:"):
|
||||||
|
user_name = extract_user_from_user_account(account_name)
|
||||||
if user_name:
|
if user_name:
|
||||||
# Look up user's actual user_id
|
# Look up user's actual user_id
|
||||||
user_id = USER_MAPPINGS.get(user_name)
|
user_id = USER_MAPPINGS.get(user_name)
|
||||||
if user_id:
|
if user_id:
|
||||||
# Find this user's equity account
|
# Find this user's liability (payable) account
|
||||||
|
# This is the Liabilities:Payable:User-<id> account in Castle
|
||||||
if user_id in self.accounts_by_user:
|
if user_id in self.accounts_by_user:
|
||||||
equity_account_id = self.accounts_by_user[user_id].get('equity')
|
liability_account_id = self.accounts_by_user[user_id].get('liability')
|
||||||
if equity_account_id:
|
if liability_account_id:
|
||||||
return equity_account_id
|
return liability_account_id
|
||||||
|
|
||||||
# If not found, provide helpful error
|
# If not found, provide helpful error
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"User '{user_name}' (ID: {user_id}) does not have an equity account.\n"
|
f"User '{user_name}' (ID: {user_id}) does not have a payable account.\n"
|
||||||
f"Please enable equity eligibility for this user in Castle first."
|
f"This should have been created when they configured their wallet.\n"
|
||||||
|
f"Please configure the wallet for user ID: {user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if this is an Assets:Receivable:<name> account
|
||||||
|
# Map Beancount Assets:Receivable:Pat to Castle Assets:Receivable:User-<id>
|
||||||
|
elif account_name.startswith("Assets:Receivable:"):
|
||||||
|
user_name = extract_user_from_user_account(account_name)
|
||||||
|
if user_name:
|
||||||
|
# Look up user's actual user_id
|
||||||
|
user_id = USER_MAPPINGS.get(user_name)
|
||||||
|
if user_id:
|
||||||
|
# Find this user's asset (receivable) account
|
||||||
|
# This is the Assets:Receivable:User-<id> account in Castle
|
||||||
|
if user_id in self.accounts_by_user:
|
||||||
|
asset_account_id = self.accounts_by_user[user_id].get('asset')
|
||||||
|
if asset_account_id:
|
||||||
|
return asset_account_id
|
||||||
|
|
||||||
|
# If not found, provide helpful error
|
||||||
|
raise ValueError(
|
||||||
|
f"User '{user_name}' (ID: {user_id}) does not have a receivable account.\n"
|
||||||
|
f"This should have been created when they configured their wallet.\n"
|
||||||
|
f"Please configure the wallet for user ID: {user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normal account lookup by name
|
# Normal account lookup by name
|
||||||
|
|
@ -304,36 +330,40 @@ def parse_beancount_transaction(txn_text: str) -> Optional[Dict]:
|
||||||
|
|
||||||
# ===== HELPER FUNCTIONS =====
|
# ===== HELPER FUNCTIONS =====
|
||||||
|
|
||||||
def extract_user_from_equity_account(account_name: str) -> Optional[str]:
|
def extract_user_from_user_account(account_name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Extract user name from Equity account.
|
Extract user name from user-specific accounts (Payable or Receivable).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
"Equity:Pat" -> "Pat"
|
"Liabilities:Payable:Pat" -> "Pat"
|
||||||
"Equity:Alice" -> "Alice"
|
"Assets:Receivable:Alice" -> "Alice"
|
||||||
"Expenses:Food" -> None
|
"Expenses:Food" -> None
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
User name or None if not an Equity account
|
User name or None if not a user-specific account
|
||||||
"""
|
"""
|
||||||
if account_name.startswith("Equity:"):
|
if account_name.startswith("Liabilities:Payable:"):
|
||||||
parts = account_name.split(":")
|
parts = account_name.split(":")
|
||||||
if len(parts) >= 2:
|
if len(parts) >= 3:
|
||||||
return parts[1]
|
return parts[2]
|
||||||
|
elif account_name.startswith("Assets:Receivable:"):
|
||||||
|
parts = account_name.split(":")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
return parts[2]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def determine_user_id(postings: list) -> Optional[str]:
|
def determine_user_id(postings: list) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Determine which user ID to use for this transaction based on Equity accounts.
|
Determine which user ID to use for this transaction based on user-specific accounts.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
postings: List of posting dicts with 'account' key
|
postings: List of posting dicts with 'account' key
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
User ID (wallet ID) from USER_MAPPINGS, or None if no Equity account found
|
User ID (wallet ID) from USER_MAPPINGS, or None if no user account found
|
||||||
"""
|
"""
|
||||||
for posting in postings:
|
for posting in postings:
|
||||||
user_name = extract_user_from_equity_account(posting['account'])
|
user_name = extract_user_from_user_account(posting['account'])
|
||||||
if user_name:
|
if user_name:
|
||||||
user_id = USER_MAPPINGS.get(user_name)
|
user_id = USER_MAPPINGS.get(user_name)
|
||||||
if not user_id:
|
if not user_id:
|
||||||
|
|
@ -343,7 +373,7 @@ def determine_user_id(postings: list) -> Optional[str]:
|
||||||
)
|
)
|
||||||
return user_id
|
return user_id
|
||||||
|
|
||||||
# No Equity account found - this shouldn't happen for typical transactions
|
# No user-specific account found - this shouldn't happen for typical transactions
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ===== CASTLE CONVERTER =====
|
# ===== CASTLE CONVERTER =====
|
||||||
|
|
@ -351,12 +381,13 @@ def determine_user_id(postings: list) -> Optional[str]:
|
||||||
def convert_to_castle_entry(parsed: dict, btc_eur_rate: float, account_lookup: AccountLookup) -> dict:
|
def convert_to_castle_entry(parsed: dict, btc_eur_rate: float, account_lookup: AccountLookup) -> dict:
|
||||||
"""Convert parsed Beancount transaction to Castle format"""
|
"""Convert parsed Beancount transaction to Castle format"""
|
||||||
|
|
||||||
# Determine which user this transaction is for (based on Equity accounts)
|
# Determine which user this transaction is for (based on user-specific accounts)
|
||||||
user_id = determine_user_id(parsed['postings'])
|
user_id = determine_user_id(parsed['postings'])
|
||||||
if not user_id:
|
if not user_id:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Could not determine user ID for transaction.\n"
|
f"Could not determine user ID for transaction.\n"
|
||||||
f"Transactions must have an Equity:<name> account (e.g., Equity:Pat)."
|
f"Transactions must have a Liabilities:Payable:<name> or Assets:Receivable:<name> account.\n"
|
||||||
|
f"Examples: Liabilities:Payable:Pat, Assets:Receivable:Pat"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build entry lines
|
# Build entry lines
|
||||||
|
|
@ -502,7 +533,7 @@ def import_beancount_file(beancount_file: str, dry_run: bool = False):
|
||||||
# Get user name for display
|
# Get user name for display
|
||||||
user_name = None
|
user_name = None
|
||||||
for posting in parsed['postings']:
|
for posting in parsed['postings']:
|
||||||
user_name = extract_user_from_equity_account(posting['account'])
|
user_name = extract_user_from_user_account(posting['account'])
|
||||||
if user_name:
|
if user_name:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue