Add virtual parent accounts for permission inheritance
Implements metadata-only accounts (e.g., "Expenses", "Assets") that exist solely in Castle DB for hierarchical permission management. These accounts don't exist in Beancount but cascade permissions to all child accounts. Changes: **Migration (m003)**: - Add `is_virtual` BOOLEAN field to accounts table - Create index idx_accounts_is_virtual - Insert 5 default virtual parents: Assets, Liabilities, Equity, Income, Expenses **Models**: - Add `is_virtual: bool = False` to Account, CreateAccount, AccountWithPermissions **CRUD**: - Update create_account() to pass is_virtual to Account constructor **Account Sync**: - Skip deactivating virtual accounts (they're intentionally metadata-only) - Virtual accounts never get marked as inactive by sync **Use Case**: Admin grants permission on virtual "Expenses" account → user automatically gets access to ALL real expense accounts: - Expenses:Groceries - Expenses:Gas:Kitchen - Expenses:Maintenance:Property - (and all other Expenses:* children) This solves the limitation where Beancount doesn't allow single-level accounts (e.g., bare "Expenses" can't exist in ledger), but admins need a way to grant broad access without manually selecting dozens of accounts. Hierarchical permission inheritance already works via account_name.startswith() check - virtual accounts simply provide the parent nodes to grant permissions on. 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
217fee6664
commit
79849f5fb2
4 changed files with 64 additions and 0 deletions
|
|
@ -352,3 +352,58 @@ async def m002_add_account_is_active(db):
|
|||
CREATE INDEX idx_accounts_is_active ON accounts (is_active)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m003_add_account_is_virtual(db):
|
||||
"""
|
||||
Add is_virtual field to accounts table for virtual parent accounts.
|
||||
|
||||
Virtual parent accounts:
|
||||
- Exist only in Castle DB (metadata-only, not in Beancount)
|
||||
- Used solely for permission inheritance
|
||||
- Allow granting permissions on top-level accounts like "Expenses", "Assets"
|
||||
- Are not synced to/from Beancount
|
||||
- Cannot be deactivated by account sync (they're intentionally metadata-only)
|
||||
|
||||
Use case: Grant permission on "Expenses" → user gets access to all Expenses:* children
|
||||
|
||||
Default: All existing accounts are real (is_virtual = FALSE).
|
||||
"""
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE accounts
|
||||
ADD COLUMN is_virtual BOOLEAN NOT NULL DEFAULT FALSE
|
||||
"""
|
||||
)
|
||||
|
||||
# Create index for faster queries filtering by is_virtual
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE INDEX idx_accounts_is_virtual ON accounts (is_virtual)
|
||||
"""
|
||||
)
|
||||
|
||||
# Insert default virtual parent accounts for permission management
|
||||
import uuid
|
||||
|
||||
virtual_parents = [
|
||||
("Assets", "asset", "All asset accounts"),
|
||||
("Liabilities", "liability", "All liability accounts"),
|
||||
("Equity", "equity", "All equity accounts"),
|
||||
("Income", "revenue", "All income accounts"),
|
||||
("Expenses", "expense", "All expense accounts"),
|
||||
]
|
||||
|
||||
for name, account_type, description in virtual_parents:
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO accounts (id, name, account_type, description, is_active, is_virtual, created_at)
|
||||
VALUES (:id, :name, :type, :description, TRUE, TRUE, {db.timestamp_now})
|
||||
""",
|
||||
{
|
||||
"id": str(uuid.uuid4()),
|
||||
"name": name,
|
||||
"type": account_type,
|
||||
"description": description,
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue