Standardizes lightning account name to "Assets:Bitcoin:Lightning" for consistency. Updates the database and code to reflect this change, ensuring that payment processing and account management use the new name. Prevents polling of receivable dialog after settlement.
334 lines
9.6 KiB
Python
334 lines
9.6 KiB
Python
async def m001_initial(db):
|
|
"""
|
|
Initial migration for Castle accounting extension.
|
|
Creates tables for double-entry bookkeeping system.
|
|
"""
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE accounts (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
account_type TEXT NOT NULL,
|
|
description TEXT,
|
|
user_id TEXT,
|
|
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_accounts_user_id ON accounts (user_id);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_accounts_type ON accounts (account_type);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE journal_entries (
|
|
id TEXT PRIMARY KEY,
|
|
description TEXT NOT NULL,
|
|
entry_date TIMESTAMP NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
|
reference TEXT
|
|
);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_journal_entries_created_by ON journal_entries (created_by);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_journal_entries_date ON journal_entries (entry_date);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE entry_lines (
|
|
id TEXT PRIMARY KEY,
|
|
journal_entry_id TEXT NOT NULL,
|
|
account_id TEXT NOT NULL,
|
|
debit INTEGER NOT NULL DEFAULT 0,
|
|
credit INTEGER NOT NULL DEFAULT 0,
|
|
description TEXT,
|
|
metadata TEXT DEFAULT '{{}}'
|
|
);
|
|
"""
|
|
)
|
|
|
|
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);
|
|
"""
|
|
)
|
|
|
|
# Insert default chart of accounts
|
|
default_accounts = [
|
|
# Assets
|
|
("cash", "Cash", "asset", "Cash on hand"),
|
|
("bank", "Bank Account", "asset", "Bank account"),
|
|
("lightning", "Lightning Balance", "asset", "Lightning Network balance"),
|
|
("accounts_receivable", "Accounts Receivable", "asset", "Money owed to the Castle"),
|
|
|
|
# Liabilities
|
|
("accounts_payable", "Accounts Payable", "liability", "Money owed by the Castle"),
|
|
|
|
# Equity
|
|
("member_equity", "Member Equity", "equity", "Member contributions"),
|
|
("retained_earnings", "Retained Earnings", "equity", "Accumulated profits"),
|
|
|
|
# Revenue
|
|
("accommodation_revenue", "Accommodation Revenue", "revenue", "Revenue from stays"),
|
|
("service_revenue", "Service Revenue", "revenue", "Revenue from services"),
|
|
("other_revenue", "Other Revenue", "revenue", "Other revenue"),
|
|
|
|
# Expenses
|
|
("utilities", "Utilities", "expense", "Electricity, water, internet"),
|
|
("food", "Food & Supplies", "expense", "Food and supplies"),
|
|
("maintenance", "Maintenance", "expense", "Repairs and maintenance"),
|
|
("other_expense", "Other Expenses", "expense", "Miscellaneous expenses"),
|
|
]
|
|
|
|
for acc_id, name, acc_type, desc in default_accounts:
|
|
await db.execute(
|
|
"""
|
|
INSERT INTO accounts (id, name, account_type, description)
|
|
VALUES (:id, :name, :type, :description)
|
|
""",
|
|
{"id": acc_id, "name": name, "type": acc_type, "description": desc}
|
|
)
|
|
|
|
|
|
async def m002_extension_settings(db):
|
|
"""
|
|
Create extension_settings table for Castle configuration.
|
|
"""
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE extension_settings (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
castle_wallet_id TEXT,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
);
|
|
"""
|
|
)
|
|
|
|
|
|
async def m003_user_wallet_settings(db):
|
|
"""
|
|
Create user_wallet_settings table for per-user wallet configuration.
|
|
"""
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE user_wallet_settings (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
user_wallet_id TEXT,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
);
|
|
"""
|
|
)
|
|
|
|
|
|
async def m004_manual_payment_requests(db):
|
|
"""
|
|
Create manual_payment_requests table for user payment requests to Castle.
|
|
"""
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE manual_payment_requests (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
amount INTEGER NOT NULL,
|
|
description TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
|
reviewed_at TIMESTAMP,
|
|
reviewed_by TEXT,
|
|
journal_entry_id TEXT
|
|
);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_manual_payment_requests_user_id ON manual_payment_requests (user_id);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_manual_payment_requests_status ON manual_payment_requests (status);
|
|
"""
|
|
)
|
|
|
|
|
|
async def m005_add_flag_and_meta(db):
|
|
"""
|
|
Add flag and meta columns to journal_entries table.
|
|
- flag: Transaction status (* = cleared, ! = pending, # = flagged, x = void)
|
|
- meta: JSON metadata for audit trail (source, tags, links, notes)
|
|
"""
|
|
await db.execute(
|
|
"""
|
|
ALTER TABLE journal_entries ADD COLUMN flag TEXT DEFAULT '*';
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
ALTER TABLE journal_entries ADD COLUMN meta TEXT DEFAULT '{}';
|
|
"""
|
|
)
|
|
|
|
|
|
async def m006_hierarchical_account_names(db):
|
|
"""
|
|
Migrate account names to hierarchical Beancount-style format.
|
|
- "Cash" → "Assets:Cash"
|
|
- "Accounts Receivable" → "Assets:Receivable"
|
|
- "Food & Supplies" → "Expenses:Food:Supplies"
|
|
- "Accounts Receivable - af983632" → "Assets:Receivable:User-af983632"
|
|
"""
|
|
from .account_utils import migrate_account_name
|
|
from .models import AccountType
|
|
|
|
# Get all existing accounts
|
|
accounts = await db.fetchall("SELECT * FROM accounts")
|
|
|
|
# Mapping of old names to new names
|
|
name_mappings = {
|
|
# Assets
|
|
"cash": "Assets:Cash",
|
|
"bank": "Assets:Bank",
|
|
"lightning": "Assets:Bitcoin:Lightning",
|
|
"accounts_receivable": "Assets:Receivable",
|
|
|
|
# Liabilities
|
|
"accounts_payable": "Liabilities:Payable",
|
|
|
|
# Equity
|
|
"member_equity": "Equity:MemberEquity",
|
|
"retained_earnings": "Equity:RetainedEarnings",
|
|
|
|
# Revenue → Income
|
|
"accommodation_revenue": "Income:Accommodation",
|
|
"service_revenue": "Income:Service",
|
|
"other_revenue": "Income:Other",
|
|
|
|
# Expenses
|
|
"utilities": "Expenses:Utilities",
|
|
"food": "Expenses:Food:Supplies",
|
|
"maintenance": "Expenses:Maintenance",
|
|
"other_expense": "Expenses:Other",
|
|
}
|
|
|
|
# Update default accounts using ID-based mapping
|
|
for old_id, new_name in name_mappings.items():
|
|
await db.execute(
|
|
"""
|
|
UPDATE accounts
|
|
SET name = :new_name
|
|
WHERE id = :old_id
|
|
""",
|
|
{"new_name": new_name, "old_id": old_id}
|
|
)
|
|
|
|
# Update user-specific accounts (those with user_id set)
|
|
user_accounts = await db.fetchall(
|
|
"SELECT * FROM accounts WHERE user_id IS NOT NULL"
|
|
)
|
|
|
|
for account in user_accounts:
|
|
# Parse account type
|
|
account_type = AccountType(account["account_type"])
|
|
|
|
# Migrate name
|
|
new_name = migrate_account_name(account["name"], account_type)
|
|
|
|
await db.execute(
|
|
"""
|
|
UPDATE accounts
|
|
SET name = :new_name
|
|
WHERE id = :id
|
|
""",
|
|
{"new_name": new_name, "id": account["id"]}
|
|
)
|
|
|
|
|
|
async def m007_balance_assertions(db):
|
|
"""
|
|
Create balance_assertions table for reconciliation.
|
|
Allows admins to assert expected balances at specific dates.
|
|
"""
|
|
await db.execute(
|
|
f"""
|
|
CREATE TABLE balance_assertions (
|
|
id TEXT PRIMARY KEY,
|
|
date TIMESTAMP NOT NULL,
|
|
account_id TEXT NOT NULL,
|
|
expected_balance_sats INTEGER NOT NULL,
|
|
expected_balance_fiat TEXT,
|
|
fiat_currency TEXT,
|
|
tolerance_sats INTEGER DEFAULT 0,
|
|
tolerance_fiat TEXT DEFAULT '0',
|
|
checked_balance_sats INTEGER,
|
|
checked_balance_fiat TEXT,
|
|
difference_sats INTEGER,
|
|
difference_fiat TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
created_by TEXT NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
|
checked_at TIMESTAMP,
|
|
FOREIGN KEY (account_id) REFERENCES accounts (id)
|
|
);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_balance_assertions_account_id ON balance_assertions (account_id);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_balance_assertions_status ON balance_assertions (status);
|
|
"""
|
|
)
|
|
|
|
await db.execute(
|
|
"""
|
|
CREATE INDEX idx_balance_assertions_date ON balance_assertions (date);
|
|
"""
|
|
)
|
|
|
|
|
|
async def m008_rename_lightning_account(db):
|
|
"""
|
|
Rename Lightning account from Assets:Lightning:Balance to Assets:Bitcoin:Lightning
|
|
for better naming consistency.
|
|
"""
|
|
await db.execute(
|
|
"""
|
|
UPDATE accounts
|
|
SET name = 'Assets:Bitcoin:Lightning'
|
|
WHERE name = 'Assets:Lightning:Balance'
|
|
"""
|
|
)
|