REFACTOR Migrates to single 'amount' field for transactions

Refactors the data model to use a single 'amount' field for journal entry lines, aligning with the Beancount approach.
This simplifies the model, enhances compatibility, and eliminates invalid states.

Includes a database migration to convert existing debit/credit columns to the new 'amount' field.

Updates balance calculation logic to utilize the new amount field for improved accuracy and efficiency.
This commit is contained in:
padreug 2025-11-08 10:26:14 +01:00
parent 0b50ba0f82
commit 5cc2630777
7 changed files with 196 additions and 144 deletions

View file

@ -55,18 +55,51 @@ class BalanceCalculator:
else:
return total_credit - total_debit
@staticmethod
def calculate_account_balance_from_amount(
total_amount: int,
account_type: AccountType
) -> int:
"""
Calculate account balance from total amount (Beancount-style single amount field).
This method uses Beancount's elegant single amount field approach:
- Positive amounts represent debits (increase assets/expenses)
- Negative amounts represent credits (increase liabilities/equity/revenue)
Args:
total_amount: Sum of all amounts for this account (positive/negative)
account_type: Type of account
Returns:
Balance in satoshis
Examples:
# Asset account with +100 (debit):
calculate_account_balance_from_amount(100, AccountType.ASSET) 100
# Liability account with -100 (credit = liability increase):
calculate_account_balance_from_amount(-100, AccountType.LIABILITY) 100
"""
if account_type in [AccountType.ASSET, AccountType.EXPENSE]:
# For assets and expenses, positive amounts increase balance
return total_amount
else:
# For liabilities, equity, and revenue, negative amounts increase balance
# So we invert the sign for display
return -total_amount
@staticmethod
def build_inventory_from_entry_lines(
entry_lines: List[Dict[str, Any]],
account_type: AccountType
) -> CastleInventory:
"""
Build a CastleInventory from journal entry lines.
Build a CastleInventory from journal entry lines (Beancount-style with single amount field).
Args:
entry_lines: List of entry line dictionaries with keys:
- debit: int (satoshis)
- credit: int (satoshis)
- amount: int (satoshis; positive = debit, negative = credit)
- metadata: str (JSON string with optional fiat_currency, fiat_amount)
account_type: Type of account (affects sign of amounts)
@ -86,33 +119,17 @@ class BalanceCalculator:
# Convert fiat amount to Decimal
fiat_amount = Decimal(str(fiat_amount_raw)) if fiat_amount_raw else None
# Calculate amount based on debit/credit and account type
debit = line.get("debit", 0)
credit = line.get("credit", 0)
# Get amount (Beancount-style: positive = debit, negative = credit)
amount = line.get("amount", 0)
if debit > 0:
sats_amount = Decimal(debit)
# For liability accounts: debit decreases balance (negative)
# For asset accounts: debit increases balance (positive)
if account_type == AccountType.LIABILITY:
sats_amount = -sats_amount
fiat_amount = -fiat_amount if fiat_amount else None
if amount != 0:
sats_amount = Decimal(amount)
inventory.add_position(
CastlePosition(
currency="SATS",
amount=sats_amount,
cost_currency=fiat_currency,
cost_amount=fiat_amount,
metadata=metadata,
)
)
if credit > 0:
sats_amount = Decimal(credit)
# For liability accounts: credit increases balance (positive)
# For asset accounts: credit decreases balance (negative)
if account_type == AccountType.ASSET:
# Apply account-specific sign adjustment
# For liability/equity/revenue: negative amounts increase balance
# For assets/expenses: positive amounts increase balance
if account_type in [AccountType.LIABILITY, AccountType.EQUITY, AccountType.REVENUE]:
# Invert sign for liability-type accounts
sats_amount = -sats_amount
fiat_amount = -fiat_amount if fiat_amount else None