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:
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ def validate_journal_entry(
|
|||
entry_lines: List[Dict[str, Any]]
|
||||
) -> None:
|
||||
"""
|
||||
Validate a journal entry and its lines.
|
||||
Validate a journal entry and its lines (Beancount-style with single amount field).
|
||||
|
||||
Checks:
|
||||
1. Entry must have at least 2 lines (double-entry requirement)
|
||||
2. Entry must be balanced (sum of debits = sum of credits)
|
||||
3. All lines must have valid amounts (non-negative)
|
||||
4. All lines must have account_id
|
||||
2. Entry must be balanced (sum of amounts = 0)
|
||||
3. All lines must have account_id
|
||||
4. No line should have amount = 0 (would serve no purpose)
|
||||
|
||||
Args:
|
||||
entry: Journal entry dict with keys:
|
||||
|
|
@ -38,8 +38,7 @@ def validate_journal_entry(
|
|||
- entry_date: datetime
|
||||
entry_lines: List of entry line dicts with keys:
|
||||
- account_id: str
|
||||
- debit: int
|
||||
- credit: int
|
||||
- amount: int (positive = debit, negative = credit)
|
||||
|
||||
Raises:
|
||||
ValidationError: If validation fails
|
||||
|
|
@ -66,64 +65,30 @@ def validate_journal_entry(
|
|||
}
|
||||
)
|
||||
|
||||
# Check amounts are non-negative
|
||||
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:
|
||||
# Check that amount is non-zero (zero amounts serve no purpose)
|
||||
if amount == 0:
|
||||
raise ValidationError(
|
||||
f"Entry line {i + 1} has negative debit: {debit}",
|
||||
{
|
||||
"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",
|
||||
f"Entry line {i + 1} has amount = 0 (serves no purpose)",
|
||||
{
|
||||
"entry_id": entry.get("id"),
|
||||
"line_index": i,
|
||||
}
|
||||
)
|
||||
|
||||
# Check entry is balanced
|
||||
total_debits = sum(line.get("debit", 0) for line in entry_lines)
|
||||
total_credits = sum(line.get("credit", 0) for line in entry_lines)
|
||||
# Check entry is balanced (sum of amounts must equal 0)
|
||||
# Beancount-style: positive amounts cancel out negative amounts
|
||||
total_amount = sum(line.get("amount", 0) for line in entry_lines)
|
||||
|
||||
if total_debits != total_credits:
|
||||
if total_amount != 0:
|
||||
raise ValidationError(
|
||||
"Journal entry is not balanced",
|
||||
"Journal entry is not balanced (sum of amounts must equal 0)",
|
||||
{
|
||||
"entry_id": entry.get("id"),
|
||||
"total_debits": total_debits,
|
||||
"total_credits": total_credits,
|
||||
"difference": total_debits - total_credits,
|
||||
"total_amount": total_amount,
|
||||
"line_count": len(entry_lines),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
51
crud.py
51
crud.py
|
|
@ -142,13 +142,13 @@ async def create_journal_entry(
|
|||
) -> JournalEntry:
|
||||
entry_id = urlsafe_short_hash()
|
||||
|
||||
# Validate that debits equal credits
|
||||
total_debits = sum(line.debit for line in data.lines)
|
||||
total_credits = sum(line.credit for line in data.lines)
|
||||
# Validate that entry balances (sum of all amounts = 0)
|
||||
# Beancount-style: positive amounts cancel out negative amounts
|
||||
total_amount = sum(line.amount for line in data.lines)
|
||||
|
||||
if total_debits != total_credits:
|
||||
if total_amount != 0:
|
||||
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()
|
||||
|
|
@ -191,23 +191,21 @@ async def create_journal_entry(
|
|||
id=line_id,
|
||||
journal_entry_id=entry_id,
|
||||
account_id=line_data.account_id,
|
||||
debit=line_data.debit,
|
||||
credit=line_data.credit,
|
||||
amount=line_data.amount,
|
||||
description=line_data.description,
|
||||
metadata=line_data.metadata,
|
||||
)
|
||||
# Insert with metadata as JSON string
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO entry_lines (id, journal_entry_id, account_id, debit, credit, description, metadata)
|
||||
VALUES (: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, :amount, :description, :metadata)
|
||||
""",
|
||||
{
|
||||
"id": line.id,
|
||||
"journal_entry_id": line.journal_entry_id,
|
||||
"account_id": line.account_id,
|
||||
"debit": line.debit,
|
||||
"credit": line.credit,
|
||||
"amount": line.amount,
|
||||
"description": line.description,
|
||||
"metadata": json.dumps(line.metadata),
|
||||
},
|
||||
|
|
@ -259,8 +257,7 @@ async def get_entry_lines(journal_entry_id: str) -> list[EntryLine]:
|
|||
id=row.id,
|
||||
journal_entry_id=row.journal_entry_id,
|
||||
account_id=row.account_id,
|
||||
debit=row.debit,
|
||||
credit=row.credit,
|
||||
amount=row.amount,
|
||||
description=row.description,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
|
@ -364,13 +361,21 @@ async def get_journal_entries_by_user(
|
|||
|
||||
|
||||
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(
|
||||
"""
|
||||
SELECT
|
||||
COALESCE(SUM(el.debit), 0) as total_debit,
|
||||
COALESCE(SUM(el.credit), 0) as total_credit
|
||||
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
|
||||
|
|
@ -386,13 +391,12 @@ async def get_account_balance(account_id: str) -> int:
|
|||
if not account:
|
||||
return 0
|
||||
|
||||
total_debit = result["total_debit"]
|
||||
total_credit = result["total_credit"]
|
||||
total_amount = result["total_amount"]
|
||||
|
||||
# Use core BalanceCalculator for consistent logic
|
||||
core_account_type = CoreAccountType(account.account_type.value)
|
||||
return BalanceCalculator.calculate_account_balance(
|
||||
total_debit, total_credit, core_account_type
|
||||
return BalanceCalculator.calculate_account_balance_from_amount(
|
||||
total_amount, core_account_type
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -500,8 +504,7 @@ async def get_account_transactions(
|
|||
id=row.id,
|
||||
journal_entry_id=row.journal_entry_id,
|
||||
account_id=row.account_id,
|
||||
debit=row.debit,
|
||||
credit=row.credit,
|
||||
amount=row.amount,
|
||||
description=row.description,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -541,3 +541,90 @@ async def m014_remove_legacy_equity_accounts(db):
|
|||
"DELETE FROM accounts WHERE name = :name",
|
||||
{"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
|
||||
journal_entry_id: str
|
||||
account_id: str
|
||||
debit: int = 0 # in satoshis
|
||||
credit: int = 0 # in satoshis
|
||||
amount: int # in satoshis; positive = debit, negative = credit
|
||||
description: Optional[str] = None
|
||||
metadata: dict = {} # Stores currency info: fiat_currency, fiat_amount, fiat_rate, etc.
|
||||
|
||||
|
||||
class CreateEntryLine(BaseModel):
|
||||
account_id: str
|
||||
debit: int = 0
|
||||
credit: int = 0
|
||||
amount: int # in satoshis; positive = debit, negative = credit
|
||||
description: Optional[str] = None
|
||||
metadata: dict = {} # Stores currency info
|
||||
|
||||
|
|
|
|||
6
tasks.py
6
tasks.py
|
|
@ -207,15 +207,13 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=lightning_account.id,
|
||||
debit=amount_sats,
|
||||
credit=0,
|
||||
amount=amount_sats, # Positive = debit (asset increase)
|
||||
description="Lightning payment received",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=user_receivable.id,
|
||||
debit=0,
|
||||
credit=amount_sats,
|
||||
amount=-amount_sats, # Negative = credit (asset decrease - receivable settled)
|
||||
description="Payment applied to balance",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
|
|
|
|||
48
views_api.py
48
views_api.py
|
|
@ -439,15 +439,13 @@ async def api_create_expense_entry(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=expense_account.id,
|
||||
debit=amount_sats,
|
||||
credit=0,
|
||||
amount=amount_sats, # Positive = debit (expense increase)
|
||||
description=f"Expense paid by user {wallet.wallet.user[:8]}",
|
||||
metadata=metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=user_account.id,
|
||||
debit=0,
|
||||
credit=amount_sats,
|
||||
amount=-amount_sats, # Negative = credit (liability/equity increase)
|
||||
description=f"{'Equity contribution' if data.is_equity else 'Amount owed to user'}",
|
||||
metadata=metadata,
|
||||
),
|
||||
|
|
@ -525,15 +523,13 @@ async def api_create_receivable_entry(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=user_receivable.id,
|
||||
debit=amount_sats,
|
||||
credit=0,
|
||||
amount=amount_sats, # Positive = debit (asset increase - user owes castle)
|
||||
description=f"Amount owed by user {data.user_id[:8]}",
|
||||
metadata=metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=revenue_account.id,
|
||||
debit=0,
|
||||
credit=amount_sats,
|
||||
amount=-amount_sats, # Negative = credit (revenue increase)
|
||||
description="Revenue earned",
|
||||
metadata=metadata,
|
||||
),
|
||||
|
|
@ -580,14 +576,12 @@ async def api_create_revenue_entry(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=payment_account.id,
|
||||
debit=data.amount,
|
||||
credit=0,
|
||||
amount=data.amount, # Positive = debit (asset increase)
|
||||
description="Payment received",
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=revenue_account.id,
|
||||
debit=0,
|
||||
credit=data.amount,
|
||||
amount=-data.amount, # Negative = credit (revenue increase)
|
||||
description="Revenue earned",
|
||||
),
|
||||
],
|
||||
|
|
@ -871,15 +865,13 @@ async def api_record_payment(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=lightning_account.id,
|
||||
debit=amount_sats,
|
||||
credit=0,
|
||||
amount=amount_sats, # Positive = debit (asset increase)
|
||||
description="Lightning payment received",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=user_receivable.id,
|
||||
debit=0,
|
||||
credit=amount_sats,
|
||||
amount=-amount_sats, # Negative = credit (asset decrease - receivable settled)
|
||||
description="Payment applied to balance",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
|
|
@ -927,14 +919,12 @@ async def api_pay_user(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=user_payable.id,
|
||||
debit=amount,
|
||||
credit=0,
|
||||
amount=amount, # Positive = debit (liability decrease)
|
||||
description="Payment made to user",
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=lightning_account.id,
|
||||
debit=0,
|
||||
credit=amount,
|
||||
amount=-amount, # Negative = credit (asset decrease)
|
||||
description="Lightning payment sent",
|
||||
),
|
||||
],
|
||||
|
|
@ -1070,15 +1060,13 @@ async def api_settle_receivable(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=payment_account.id,
|
||||
debit=amount_in_sats,
|
||||
credit=0,
|
||||
amount=amount_in_sats, # Positive = debit (asset increase)
|
||||
description=f"Payment received via {data.payment_method}",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=user_receivable.id,
|
||||
debit=0,
|
||||
credit=amount_in_sats,
|
||||
amount=-amount_in_sats, # Negative = credit (asset decrease - receivable settled)
|
||||
description="Receivable settled",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
|
|
@ -1216,15 +1204,13 @@ async def api_pay_user(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=user_payable.id,
|
||||
debit=amount_in_sats,
|
||||
credit=0,
|
||||
amount=amount_in_sats, # Positive = debit (liability decrease)
|
||||
description="Payable settled",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=payment_account.id,
|
||||
debit=0,
|
||||
credit=amount_in_sats,
|
||||
amount=-amount_in_sats, # Negative = credit (asset decrease)
|
||||
description=f"Payment sent via {data.payment_method}",
|
||||
metadata=line_metadata,
|
||||
),
|
||||
|
|
@ -1510,14 +1496,12 @@ async def api_approve_manual_payment_request(
|
|||
lines=[
|
||||
CreateEntryLine(
|
||||
account_id=liability_account.id,
|
||||
debit=request.amount, # Decrease liability (castle owes less)
|
||||
credit=0,
|
||||
amount=request.amount, # Positive = debit (liability decrease - castle owes less)
|
||||
description="Payment to user",
|
||||
),
|
||||
CreateEntryLine(
|
||||
account_id=lightning_account.id,
|
||||
debit=0,
|
||||
credit=request.amount, # Decrease asset (lightning balance reduced)
|
||||
amount=-request.amount, # Negative = credit (asset decrease)
|
||||
description="Payment from castle",
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue