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:
parent
0b50ba0f82
commit
5cc2630777
7 changed files with 196 additions and 144 deletions
|
|
@ -55,18 +55,51 @@ class BalanceCalculator:
|
||||||
else:
|
else:
|
||||||
return total_credit - total_debit
|
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
|
@staticmethod
|
||||||
def build_inventory_from_entry_lines(
|
def build_inventory_from_entry_lines(
|
||||||
entry_lines: List[Dict[str, Any]],
|
entry_lines: List[Dict[str, Any]],
|
||||||
account_type: AccountType
|
account_type: AccountType
|
||||||
) -> CastleInventory:
|
) -> CastleInventory:
|
||||||
"""
|
"""
|
||||||
Build a CastleInventory from journal entry lines.
|
Build a CastleInventory from journal entry lines (Beancount-style with single amount field).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry_lines: List of entry line dictionaries with keys:
|
entry_lines: List of entry line dictionaries with keys:
|
||||||
- debit: int (satoshis)
|
- amount: int (satoshis; positive = debit, negative = credit)
|
||||||
- credit: int (satoshis)
|
|
||||||
- metadata: str (JSON string with optional fiat_currency, fiat_amount)
|
- metadata: str (JSON string with optional fiat_currency, fiat_amount)
|
||||||
account_type: Type of account (affects sign of amounts)
|
account_type: Type of account (affects sign of amounts)
|
||||||
|
|
||||||
|
|
@ -86,33 +119,17 @@ class BalanceCalculator:
|
||||||
# Convert fiat amount to Decimal
|
# Convert fiat amount to Decimal
|
||||||
fiat_amount = Decimal(str(fiat_amount_raw)) if fiat_amount_raw else None
|
fiat_amount = Decimal(str(fiat_amount_raw)) if fiat_amount_raw else None
|
||||||
|
|
||||||
# Calculate amount based on debit/credit and account type
|
# Get amount (Beancount-style: positive = debit, negative = credit)
|
||||||
debit = line.get("debit", 0)
|
amount = line.get("amount", 0)
|
||||||
credit = line.get("credit", 0)
|
|
||||||
|
|
||||||
if debit > 0:
|
if amount != 0:
|
||||||
sats_amount = Decimal(debit)
|
sats_amount = Decimal(amount)
|
||||||
# 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
|
|
||||||
|
|
||||||
inventory.add_position(
|
# Apply account-specific sign adjustment
|
||||||
CastlePosition(
|
# For liability/equity/revenue: negative amounts increase balance
|
||||||
currency="SATS",
|
# For assets/expenses: positive amounts increase balance
|
||||||
amount=sats_amount,
|
if account_type in [AccountType.LIABILITY, AccountType.EQUITY, AccountType.REVENUE]:
|
||||||
cost_currency=fiat_currency,
|
# Invert sign for liability-type accounts
|
||||||
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:
|
|
||||||
sats_amount = -sats_amount
|
sats_amount = -sats_amount
|
||||||
fiat_amount = -fiat_amount if fiat_amount else None
|
fiat_amount = -fiat_amount if fiat_amount else None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ def validate_journal_entry(
|
||||||
entry_lines: List[Dict[str, Any]]
|
entry_lines: List[Dict[str, Any]]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Validate a journal entry and its lines.
|
Validate a journal entry and its lines (Beancount-style with single amount field).
|
||||||
|
|
||||||
Checks:
|
Checks:
|
||||||
1. Entry must have at least 2 lines (double-entry requirement)
|
1. Entry must have at least 2 lines (double-entry requirement)
|
||||||
2. Entry must be balanced (sum of debits = sum of credits)
|
2. Entry must be balanced (sum of amounts = 0)
|
||||||
3. All lines must have valid amounts (non-negative)
|
3. All lines must have account_id
|
||||||
4. All lines must have account_id
|
4. No line should have amount = 0 (would serve no purpose)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry: Journal entry dict with keys:
|
entry: Journal entry dict with keys:
|
||||||
|
|
@ -38,8 +38,7 @@ def validate_journal_entry(
|
||||||
- entry_date: datetime
|
- entry_date: datetime
|
||||||
entry_lines: List of entry line dicts with keys:
|
entry_lines: List of entry line dicts with keys:
|
||||||
- account_id: str
|
- account_id: str
|
||||||
- debit: int
|
- amount: int (positive = debit, negative = credit)
|
||||||
- credit: int
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: If validation fails
|
ValidationError: If validation fails
|
||||||
|
|
@ -66,64 +65,30 @@ def validate_journal_entry(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check amounts are non-negative
|
# Get amount (Beancount-style: positive = debit, negative = credit)
|
||||||
debit = line.get("debit", 0)
|
amount = line.get("amount", 0)
|
||||||
credit = line.get("credit", 0)
|
|
||||||
|
|
||||||
if debit < 0:
|
# Check that amount is non-zero (zero amounts serve no purpose)
|
||||||
|
if amount == 0:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Entry line {i + 1} has negative debit: {debit}",
|
f"Entry line {i + 1} has amount = 0 (serves no purpose)",
|
||||||
{
|
|
||||||
"entry_id": entry.get("id"),
|
|
||||||
"line_index": i,
|
|
||||||
"debit": debit,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if credit < 0:
|
|
||||||
raise ValidationError(
|
|
||||||
f"Entry line {i + 1} has negative credit: {credit}",
|
|
||||||
{
|
|
||||||
"entry_id": entry.get("id"),
|
|
||||||
"line_index": i,
|
|
||||||
"credit": credit,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that a line doesn't have both debit and credit
|
|
||||||
if debit > 0 and credit > 0:
|
|
||||||
raise ValidationError(
|
|
||||||
f"Entry line {i + 1} has both debit and credit",
|
|
||||||
{
|
|
||||||
"entry_id": entry.get("id"),
|
|
||||||
"line_index": i,
|
|
||||||
"debit": debit,
|
|
||||||
"credit": credit,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that a line has at least one non-zero amount
|
|
||||||
if debit == 0 and credit == 0:
|
|
||||||
raise ValidationError(
|
|
||||||
f"Entry line {i + 1} has both debit and credit as zero",
|
|
||||||
{
|
{
|
||||||
"entry_id": entry.get("id"),
|
"entry_id": entry.get("id"),
|
||||||
"line_index": i,
|
"line_index": i,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check entry is balanced
|
# Check entry is balanced (sum of amounts must equal 0)
|
||||||
total_debits = sum(line.get("debit", 0) for line in entry_lines)
|
# Beancount-style: positive amounts cancel out negative amounts
|
||||||
total_credits = sum(line.get("credit", 0) for line in entry_lines)
|
total_amount = sum(line.get("amount", 0) for line in entry_lines)
|
||||||
|
|
||||||
if total_debits != total_credits:
|
if total_amount != 0:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"Journal entry is not balanced",
|
"Journal entry is not balanced (sum of amounts must equal 0)",
|
||||||
{
|
{
|
||||||
"entry_id": entry.get("id"),
|
"entry_id": entry.get("id"),
|
||||||
"total_debits": total_debits,
|
"total_amount": total_amount,
|
||||||
"total_credits": total_credits,
|
"line_count": len(entry_lines),
|
||||||
"difference": total_debits - total_credits,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
51
crud.py
51
crud.py
|
|
@ -142,13 +142,13 @@ async def create_journal_entry(
|
||||||
) -> JournalEntry:
|
) -> JournalEntry:
|
||||||
entry_id = urlsafe_short_hash()
|
entry_id = urlsafe_short_hash()
|
||||||
|
|
||||||
# Validate that debits equal credits
|
# Validate that entry balances (sum of all amounts = 0)
|
||||||
total_debits = sum(line.debit for line in data.lines)
|
# Beancount-style: positive amounts cancel out negative amounts
|
||||||
total_credits = sum(line.credit for line in data.lines)
|
total_amount = sum(line.amount for line in data.lines)
|
||||||
|
|
||||||
if total_debits != total_credits:
|
if total_amount != 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Journal entry must balance: debits={total_debits}, credits={total_credits}"
|
f"Journal entry must balance (sum of amounts = 0): sum={total_amount}"
|
||||||
)
|
)
|
||||||
|
|
||||||
entry_date = data.entry_date or datetime.now()
|
entry_date = data.entry_date or datetime.now()
|
||||||
|
|
@ -191,23 +191,21 @@ async def create_journal_entry(
|
||||||
id=line_id,
|
id=line_id,
|
||||||
journal_entry_id=entry_id,
|
journal_entry_id=entry_id,
|
||||||
account_id=line_data.account_id,
|
account_id=line_data.account_id,
|
||||||
debit=line_data.debit,
|
amount=line_data.amount,
|
||||||
credit=line_data.credit,
|
|
||||||
description=line_data.description,
|
description=line_data.description,
|
||||||
metadata=line_data.metadata,
|
metadata=line_data.metadata,
|
||||||
)
|
)
|
||||||
# Insert with metadata as JSON string
|
# Insert with metadata as JSON string
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO entry_lines (id, journal_entry_id, account_id, debit, credit, description, metadata)
|
INSERT INTO entry_lines (id, journal_entry_id, account_id, amount, description, metadata)
|
||||||
VALUES (:id, :journal_entry_id, :account_id, :debit, :credit, :description, :metadata)
|
VALUES (:id, :journal_entry_id, :account_id, :amount, :description, :metadata)
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"id": line.id,
|
"id": line.id,
|
||||||
"journal_entry_id": line.journal_entry_id,
|
"journal_entry_id": line.journal_entry_id,
|
||||||
"account_id": line.account_id,
|
"account_id": line.account_id,
|
||||||
"debit": line.debit,
|
"amount": line.amount,
|
||||||
"credit": line.credit,
|
|
||||||
"description": line.description,
|
"description": line.description,
|
||||||
"metadata": json.dumps(line.metadata),
|
"metadata": json.dumps(line.metadata),
|
||||||
},
|
},
|
||||||
|
|
@ -259,8 +257,7 @@ async def get_entry_lines(journal_entry_id: str) -> list[EntryLine]:
|
||||||
id=row.id,
|
id=row.id,
|
||||||
journal_entry_id=row.journal_entry_id,
|
journal_entry_id=row.journal_entry_id,
|
||||||
account_id=row.account_id,
|
account_id=row.account_id,
|
||||||
debit=row.debit,
|
amount=row.amount,
|
||||||
credit=row.credit,
|
|
||||||
description=row.description,
|
description=row.description,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
)
|
)
|
||||||
|
|
@ -364,13 +361,21 @@ async def get_journal_entries_by_user(
|
||||||
|
|
||||||
|
|
||||||
async def get_account_balance(account_id: str) -> int:
|
async def get_account_balance(account_id: str) -> int:
|
||||||
"""Calculate account balance (debits - credits for assets/expenses, credits - debits for liabilities/equity/revenue)
|
"""
|
||||||
Only includes entries that are cleared (flag='*'), excludes pending/flagged/voided entries."""
|
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(
|
result = await db.fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT COALESCE(SUM(el.amount), 0) as total_amount
|
||||||
COALESCE(SUM(el.debit), 0) as total_debit,
|
|
||||||
COALESCE(SUM(el.credit), 0) as total_credit
|
|
||||||
FROM entry_lines el
|
FROM entry_lines el
|
||||||
JOIN journal_entries je ON el.journal_entry_id = je.id
|
JOIN journal_entries je ON el.journal_entry_id = je.id
|
||||||
WHERE el.account_id = :id
|
WHERE el.account_id = :id
|
||||||
|
|
@ -386,13 +391,12 @@ async def get_account_balance(account_id: str) -> int:
|
||||||
if not account:
|
if not account:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
total_debit = result["total_debit"]
|
total_amount = result["total_amount"]
|
||||||
total_credit = result["total_credit"]
|
|
||||||
|
|
||||||
# Use core BalanceCalculator for consistent logic
|
# Use core BalanceCalculator for consistent logic
|
||||||
core_account_type = CoreAccountType(account.account_type.value)
|
core_account_type = CoreAccountType(account.account_type.value)
|
||||||
return BalanceCalculator.calculate_account_balance(
|
return BalanceCalculator.calculate_account_balance_from_amount(
|
||||||
total_debit, total_credit, core_account_type
|
total_amount, core_account_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -500,8 +504,7 @@ async def get_account_transactions(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
journal_entry_id=row.journal_entry_id,
|
journal_entry_id=row.journal_entry_id,
|
||||||
account_id=row.account_id,
|
account_id=row.account_id,
|
||||||
debit=row.debit,
|
amount=row.amount,
|
||||||
credit=row.credit,
|
|
||||||
description=row.description,
|
description=row.description,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -541,3 +541,90 @@ async def m014_remove_legacy_equity_accounts(db):
|
||||||
"DELETE FROM accounts WHERE name = :name",
|
"DELETE FROM accounts WHERE name = :name",
|
||||||
{"name": "Equity:RetainedEarnings"}
|
{"name": "Equity:RetainedEarnings"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m015_convert_to_single_amount_field(db):
|
||||||
|
"""
|
||||||
|
Convert entry_lines from separate debit/credit columns to single amount field.
|
||||||
|
|
||||||
|
This aligns Castle with Beancount's elegant design:
|
||||||
|
- Positive amount = debit (increase assets/expenses, decrease liabilities/equity/revenue)
|
||||||
|
- Negative amount = credit (decrease assets/expenses, increase liabilities/equity/revenue)
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
- Simpler model (one field instead of two)
|
||||||
|
- Direct compatibility with Beancount import/export
|
||||||
|
- Eliminates invalid states (both debit and credit non-zero)
|
||||||
|
- More intuitive for programmers (positive/negative instead of accounting conventions)
|
||||||
|
|
||||||
|
Migration formula: amount = debit - credit
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Expense transaction:
|
||||||
|
* Expenses:Food:Groceries amount=+100 (debit)
|
||||||
|
* Liabilities:Payable:User amount=-100 (credit)
|
||||||
|
- Payment transaction:
|
||||||
|
* Liabilities:Payable:User amount=+100 (debit)
|
||||||
|
* Assets:Bitcoin:Lightning amount=-100 (credit)
|
||||||
|
"""
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
||||||
|
# Step 1: Add new amount column (nullable for migration)
|
||||||
|
try:
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE entry_lines ADD COLUMN amount INTEGER"
|
||||||
|
)
|
||||||
|
except OperationalError:
|
||||||
|
# Column might already exist if migration was partially run
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Step 2: Populate amount from existing debit/credit
|
||||||
|
# Formula: amount = debit - credit
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE entry_lines
|
||||||
|
SET amount = debit - credit
|
||||||
|
WHERE amount IS NULL
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 3: Create new table with amount field as NOT NULL
|
||||||
|
# SQLite doesn't support ALTER COLUMN, so we need to recreate the table
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE entry_lines_new (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
journal_entry_id TEXT NOT NULL,
|
||||||
|
account_id TEXT NOT NULL,
|
||||||
|
amount INTEGER NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
metadata TEXT DEFAULT '{}'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 4: Copy data from old table to new
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO entry_lines_new (id, journal_entry_id, account_id, amount, description, metadata)
|
||||||
|
SELECT id, journal_entry_id, account_id, amount, description, metadata
|
||||||
|
FROM entry_lines
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 5: Drop old table and rename new one
|
||||||
|
await db.execute("DROP TABLE entry_lines")
|
||||||
|
await db.execute("ALTER TABLE entry_lines_new RENAME TO entry_lines")
|
||||||
|
|
||||||
|
# Step 6: Recreate indexes
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX idx_entry_lines_journal_entry ON entry_lines (journal_entry_id)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX idx_entry_lines_account ON entry_lines (account_id)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -42,16 +42,14 @@ class EntryLine(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
journal_entry_id: str
|
journal_entry_id: str
|
||||||
account_id: str
|
account_id: str
|
||||||
debit: int = 0 # in satoshis
|
amount: int # in satoshis; positive = debit, negative = credit
|
||||||
credit: int = 0 # in satoshis
|
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
metadata: dict = {} # Stores currency info: fiat_currency, fiat_amount, fiat_rate, etc.
|
metadata: dict = {} # Stores currency info: fiat_currency, fiat_amount, fiat_rate, etc.
|
||||||
|
|
||||||
|
|
||||||
class CreateEntryLine(BaseModel):
|
class CreateEntryLine(BaseModel):
|
||||||
account_id: str
|
account_id: str
|
||||||
debit: int = 0
|
amount: int # in satoshis; positive = debit, negative = credit
|
||||||
credit: int = 0
|
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
metadata: dict = {} # Stores currency info
|
metadata: dict = {} # Stores currency info
|
||||||
|
|
||||||
|
|
|
||||||
6
tasks.py
6
tasks.py
|
|
@ -207,15 +207,13 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=lightning_account.id,
|
account_id=lightning_account.id,
|
||||||
debit=amount_sats,
|
amount=amount_sats, # Positive = debit (asset increase)
|
||||||
credit=0,
|
|
||||||
description="Lightning payment received",
|
description="Lightning payment received",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_receivable.id,
|
account_id=user_receivable.id,
|
||||||
debit=0,
|
amount=-amount_sats, # Negative = credit (asset decrease - receivable settled)
|
||||||
credit=amount_sats,
|
|
||||||
description="Payment applied to balance",
|
description="Payment applied to balance",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
48
views_api.py
48
views_api.py
|
|
@ -439,15 +439,13 @@ async def api_create_expense_entry(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=expense_account.id,
|
account_id=expense_account.id,
|
||||||
debit=amount_sats,
|
amount=amount_sats, # Positive = debit (expense increase)
|
||||||
credit=0,
|
|
||||||
description=f"Expense paid by user {wallet.wallet.user[:8]}",
|
description=f"Expense paid by user {wallet.wallet.user[:8]}",
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_account.id,
|
account_id=user_account.id,
|
||||||
debit=0,
|
amount=-amount_sats, # Negative = credit (liability/equity increase)
|
||||||
credit=amount_sats,
|
|
||||||
description=f"{'Equity contribution' if data.is_equity else 'Amount owed to user'}",
|
description=f"{'Equity contribution' if data.is_equity else 'Amount owed to user'}",
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
),
|
),
|
||||||
|
|
@ -525,15 +523,13 @@ async def api_create_receivable_entry(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_receivable.id,
|
account_id=user_receivable.id,
|
||||||
debit=amount_sats,
|
amount=amount_sats, # Positive = debit (asset increase - user owes castle)
|
||||||
credit=0,
|
|
||||||
description=f"Amount owed by user {data.user_id[:8]}",
|
description=f"Amount owed by user {data.user_id[:8]}",
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=revenue_account.id,
|
account_id=revenue_account.id,
|
||||||
debit=0,
|
amount=-amount_sats, # Negative = credit (revenue increase)
|
||||||
credit=amount_sats,
|
|
||||||
description="Revenue earned",
|
description="Revenue earned",
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
),
|
),
|
||||||
|
|
@ -580,14 +576,12 @@ async def api_create_revenue_entry(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=payment_account.id,
|
account_id=payment_account.id,
|
||||||
debit=data.amount,
|
amount=data.amount, # Positive = debit (asset increase)
|
||||||
credit=0,
|
|
||||||
description="Payment received",
|
description="Payment received",
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=revenue_account.id,
|
account_id=revenue_account.id,
|
||||||
debit=0,
|
amount=-data.amount, # Negative = credit (revenue increase)
|
||||||
credit=data.amount,
|
|
||||||
description="Revenue earned",
|
description="Revenue earned",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -871,15 +865,13 @@ async def api_record_payment(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=lightning_account.id,
|
account_id=lightning_account.id,
|
||||||
debit=amount_sats,
|
amount=amount_sats, # Positive = debit (asset increase)
|
||||||
credit=0,
|
|
||||||
description="Lightning payment received",
|
description="Lightning payment received",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_receivable.id,
|
account_id=user_receivable.id,
|
||||||
debit=0,
|
amount=-amount_sats, # Negative = credit (asset decrease - receivable settled)
|
||||||
credit=amount_sats,
|
|
||||||
description="Payment applied to balance",
|
description="Payment applied to balance",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
|
|
@ -927,14 +919,12 @@ async def api_pay_user(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_payable.id,
|
account_id=user_payable.id,
|
||||||
debit=amount,
|
amount=amount, # Positive = debit (liability decrease)
|
||||||
credit=0,
|
|
||||||
description="Payment made to user",
|
description="Payment made to user",
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=lightning_account.id,
|
account_id=lightning_account.id,
|
||||||
debit=0,
|
amount=-amount, # Negative = credit (asset decrease)
|
||||||
credit=amount,
|
|
||||||
description="Lightning payment sent",
|
description="Lightning payment sent",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -1070,15 +1060,13 @@ async def api_settle_receivable(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=payment_account.id,
|
account_id=payment_account.id,
|
||||||
debit=amount_in_sats,
|
amount=amount_in_sats, # Positive = debit (asset increase)
|
||||||
credit=0,
|
|
||||||
description=f"Payment received via {data.payment_method}",
|
description=f"Payment received via {data.payment_method}",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_receivable.id,
|
account_id=user_receivable.id,
|
||||||
debit=0,
|
amount=-amount_in_sats, # Negative = credit (asset decrease - receivable settled)
|
||||||
credit=amount_in_sats,
|
|
||||||
description="Receivable settled",
|
description="Receivable settled",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
|
|
@ -1216,15 +1204,13 @@ async def api_pay_user(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=user_payable.id,
|
account_id=user_payable.id,
|
||||||
debit=amount_in_sats,
|
amount=amount_in_sats, # Positive = debit (liability decrease)
|
||||||
credit=0,
|
|
||||||
description="Payable settled",
|
description="Payable settled",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=payment_account.id,
|
account_id=payment_account.id,
|
||||||
debit=0,
|
amount=-amount_in_sats, # Negative = credit (asset decrease)
|
||||||
credit=amount_in_sats,
|
|
||||||
description=f"Payment sent via {data.payment_method}",
|
description=f"Payment sent via {data.payment_method}",
|
||||||
metadata=line_metadata,
|
metadata=line_metadata,
|
||||||
),
|
),
|
||||||
|
|
@ -1510,14 +1496,12 @@ async def api_approve_manual_payment_request(
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=liability_account.id,
|
account_id=liability_account.id,
|
||||||
debit=request.amount, # Decrease liability (castle owes less)
|
amount=request.amount, # Positive = debit (liability decrease - castle owes less)
|
||||||
credit=0,
|
|
||||||
description="Payment to user",
|
description="Payment to user",
|
||||||
),
|
),
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=lightning_account.id,
|
account_id=lightning_account.id,
|
||||||
debit=0,
|
amount=-request.amount, # Negative = credit (asset decrease)
|
||||||
credit=request.amount, # Decrease asset (lightning balance reduced)
|
|
||||||
description="Payment from castle",
|
description="Payment from castle",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue