Removes core balance calculation logic
Migrates balance calculation and inventory tracking to Fava/Beancount, leveraging Fava's query API for all accounting calculations. This simplifies the core module and centralizes accounting logic in Fava.
This commit is contained in:
parent
efc09aa5ce
commit
88ff3821ce
4 changed files with 13 additions and 585 deletions
139
crud.py
139
crud.py
|
|
@ -29,8 +29,6 @@ from .models import (
|
|||
)
|
||||
|
||||
# Import core accounting logic
|
||||
from .core.balance import BalanceCalculator, AccountType as CoreAccountType
|
||||
from .core.inventory import CastleInventory, CastlePosition
|
||||
from .core.validation import (
|
||||
ValidationError,
|
||||
validate_journal_entry,
|
||||
|
|
@ -484,128 +482,6 @@ async def count_journal_entries_by_user_and_account_type(user_id: str, account_t
|
|||
# ===== BALANCE AND REPORTING =====
|
||||
|
||||
|
||||
async def get_account_balance(account_id: str) -> int:
|
||||
"""
|
||||
Calculate account balance using single amount field (Beancount-style).
|
||||
Only includes entries that are cleared (flag='*'), excludes pending/flagged/voided entries.
|
||||
|
||||
For each account type:
|
||||
- Assets/Expenses: balance = sum of amounts (positive amounts increase, negative decrease)
|
||||
- Liabilities/Equity/Revenue: balance = -sum of amounts (negative amounts increase, positive decrease)
|
||||
|
||||
This works because we store amounts consistently:
|
||||
- Debit (asset/expense increase) = positive amount
|
||||
- Credit (liability/equity/revenue increase) = negative amount
|
||||
"""
|
||||
result = await db.fetchone(
|
||||
"""
|
||||
SELECT COALESCE(SUM(el.amount), 0) as total_amount
|
||||
FROM entry_lines el
|
||||
JOIN journal_entries je ON el.journal_entry_id = je.id
|
||||
WHERE el.account_id = :id
|
||||
AND je.flag = '*'
|
||||
""",
|
||||
{"id": account_id},
|
||||
)
|
||||
|
||||
if not result:
|
||||
return 0
|
||||
|
||||
account = await get_account(account_id)
|
||||
if not account:
|
||||
return 0
|
||||
|
||||
total_amount = result["total_amount"]
|
||||
|
||||
# Use core BalanceCalculator for consistent logic
|
||||
core_account_type = CoreAccountType(account.account_type.value)
|
||||
return BalanceCalculator.calculate_account_balance_from_amount(
|
||||
total_amount, core_account_type
|
||||
)
|
||||
|
||||
|
||||
async def get_user_balance(user_id: str) -> UserBalance:
|
||||
"""Get user's balance with the Castle (positive = castle owes user, negative = user owes castle)"""
|
||||
# Get all user-specific accounts
|
||||
user_accounts = await db.fetchall(
|
||||
"SELECT * FROM accounts WHERE user_id = :user_id",
|
||||
{"user_id": user_id},
|
||||
Account,
|
||||
)
|
||||
|
||||
# Calculate balances for each account
|
||||
account_balances = {}
|
||||
account_inventories = {}
|
||||
|
||||
for account in user_accounts:
|
||||
# Get satoshi balance
|
||||
balance = await get_account_balance(account.id)
|
||||
account_balances[account.id] = balance
|
||||
|
||||
# Get all entry lines for this account to build inventory
|
||||
# Only include cleared entries (exclude pending/flagged/voided)
|
||||
entry_lines = await db.fetchall(
|
||||
"""
|
||||
SELECT el.*
|
||||
FROM entry_lines el
|
||||
JOIN journal_entries je ON el.journal_entry_id = je.id
|
||||
WHERE el.account_id = :account_id
|
||||
AND je.flag = '*'
|
||||
""",
|
||||
{"account_id": account.id},
|
||||
)
|
||||
|
||||
# Use BalanceCalculator to build inventory from entry lines
|
||||
core_account_type = CoreAccountType(account.account_type.value)
|
||||
inventory = BalanceCalculator.build_inventory_from_entry_lines(
|
||||
[dict(line) for line in entry_lines],
|
||||
core_account_type
|
||||
)
|
||||
account_inventories[account.id] = inventory
|
||||
|
||||
# Use BalanceCalculator to calculate total user balance
|
||||
accounts_list = [
|
||||
{"id": acc.id, "account_type": acc.account_type.value}
|
||||
for acc in user_accounts
|
||||
]
|
||||
balance_result = BalanceCalculator.calculate_user_balance(
|
||||
accounts_list,
|
||||
account_balances,
|
||||
account_inventories
|
||||
)
|
||||
|
||||
return UserBalance(
|
||||
user_id=user_id,
|
||||
balance=balance_result["balance"],
|
||||
accounts=user_accounts,
|
||||
fiat_balances=balance_result["fiat_balances"],
|
||||
)
|
||||
|
||||
|
||||
async def get_all_user_balances() -> list[UserBalance]:
|
||||
"""Get balances for all users (used by castle to see who they owe)"""
|
||||
# Get all user-specific accounts
|
||||
all_accounts = await db.fetchall(
|
||||
"SELECT * FROM accounts WHERE user_id IS NOT NULL",
|
||||
{},
|
||||
Account,
|
||||
)
|
||||
|
||||
# Get unique user IDs
|
||||
user_ids = set(account.user_id for account in all_accounts if account.user_id)
|
||||
|
||||
# Calculate balance for each user using the refactored function
|
||||
user_balances = []
|
||||
for user_id in user_ids:
|
||||
balance = await get_user_balance(user_id)
|
||||
|
||||
# Include users with non-zero balance or fiat balances
|
||||
if balance.balance != 0 or balance.fiat_balances:
|
||||
user_balances.append(balance)
|
||||
|
||||
return user_balances
|
||||
|
||||
|
||||
async def get_account_transactions(
|
||||
account_id: str, limit: int = 100
|
||||
) -> list[tuple[JournalEntry, EntryLine]]:
|
||||
|
|
@ -1013,26 +889,31 @@ async def check_balance_assertion(assertion_id: str) -> BalanceAssertion:
|
|||
"""
|
||||
Check a balance assertion by comparing expected vs actual balance.
|
||||
Updates the assertion with the check results.
|
||||
Uses Fava/Beancount for balance queries.
|
||||
"""
|
||||
from decimal import Decimal
|
||||
from .fava_client import get_fava_client
|
||||
|
||||
assertion = await get_balance_assertion(assertion_id)
|
||||
if not assertion:
|
||||
raise ValueError(f"Balance assertion {assertion_id} not found")
|
||||
|
||||
# Get actual account balance
|
||||
# Get actual account balance from Fava
|
||||
account = await get_account(assertion.account_id)
|
||||
if not account:
|
||||
raise ValueError(f"Account {assertion.account_id} not found")
|
||||
|
||||
# Calculate balance at the assertion date
|
||||
actual_balance = await get_account_balance(assertion.account_id)
|
||||
fava = get_fava_client()
|
||||
|
||||
# Get balance from Fava
|
||||
balance_data = await fava.get_account_balance(account.name)
|
||||
actual_balance = balance_data["sats"]
|
||||
|
||||
# Get fiat balance if needed
|
||||
actual_fiat_balance = None
|
||||
if assertion.fiat_currency and account.user_id:
|
||||
user_balance = await get_user_balance(account.user_id)
|
||||
actual_fiat_balance = user_balance.fiat_balances.get(assertion.fiat_currency, Decimal("0"))
|
||||
user_balance_data = await fava.get_user_balance(account.user_id)
|
||||
actual_fiat_balance = user_balance_data["fiat_balances"].get(assertion.fiat_currency, Decimal("0"))
|
||||
|
||||
# Check sats balance
|
||||
difference_sats = actual_balance - assertion.expected_balance_sats
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue