Updates default chart of accounts
Expands the default chart of accounts with a more detailed hierarchical structure. This includes new accounts for fixed assets, livestock, equity contributions, and detailed expense categories. The migration script only adds accounts that don't already exist, ensuring a smooth update process.
This commit is contained in:
parent
6f62c52c68
commit
88aaf0e28e
3 changed files with 89 additions and 29 deletions
|
|
@ -190,26 +190,51 @@ def migrate_account_name(old_name: str, account_type: AccountType) -> str:
|
||||||
# Default chart of accounts with hierarchical names
|
# Default chart of accounts with hierarchical names
|
||||||
DEFAULT_HIERARCHICAL_ACCOUNTS = [
|
DEFAULT_HIERARCHICAL_ACCOUNTS = [
|
||||||
# Assets
|
# Assets
|
||||||
("Assets:Cash", AccountType.ASSET, "Cash on hand"),
|
|
||||||
("Assets:Bank", AccountType.ASSET, "Bank account"),
|
("Assets:Bank", AccountType.ASSET, "Bank account"),
|
||||||
("Assets:Lightning:Balance", AccountType.ASSET, "Lightning Network balance"),
|
("Assets:Bitcoin", AccountType.ASSET, "Bitcoin holdings"),
|
||||||
|
("Assets:Bitcoin:Lightning", AccountType.ASSET, "Lightning Network balance"),
|
||||||
|
("Assets:Bitcoin:OnChain", AccountType.ASSET, "On-chain Bitcoin wallet"),
|
||||||
|
("Assets:Cash", AccountType.ASSET, "Cash on hand"),
|
||||||
|
("Assets:FixedAssets:Equipment", AccountType.ASSET, "Equipment and machinery"),
|
||||||
|
("Assets:FixedAssets:FarmEquipment", AccountType.ASSET, "Farm equipment"),
|
||||||
|
("Assets:FixedAssets:Network", AccountType.ASSET, "Network infrastructure"),
|
||||||
|
("Assets:FixedAssets:ProductionFacility", AccountType.ASSET, "Production facilities"),
|
||||||
|
("Assets:Inventory", AccountType.ASSET, "Inventory and stock"),
|
||||||
|
("Assets:Livestock", AccountType.ASSET, "Livestock and animals"),
|
||||||
("Assets:Receivable", AccountType.ASSET, "Money owed to the Castle"),
|
("Assets:Receivable", AccountType.ASSET, "Money owed to the Castle"),
|
||||||
|
("Assets:Tools", AccountType.ASSET, "Tools and hand equipment"),
|
||||||
|
|
||||||
# Liabilities
|
# Liabilities
|
||||||
("Liabilities:Payable", AccountType.LIABILITY, "Money owed by the Castle"),
|
("Liabilities:Payable", AccountType.LIABILITY, "Money owed by the Castle"),
|
||||||
|
|
||||||
# Equity
|
# Equity
|
||||||
("Equity:MemberEquity", AccountType.EQUITY, "Member contributions"),
|
("Equity", AccountType.EQUITY, "Owner's equity and member contributions"),
|
||||||
("Equity:RetainedEarnings", AccountType.EQUITY, "Accumulated profits"),
|
|
||||||
|
|
||||||
# Revenue (Income in Beancount terminology)
|
# Revenue (Income in Beancount terminology)
|
||||||
("Income:Accommodation", AccountType.REVENUE, "Revenue from stays"),
|
("Income:Accommodation:Guests", AccountType.REVENUE, "Revenue from guest accommodation"),
|
||||||
("Income:Service", AccountType.REVENUE, "Revenue from services"),
|
("Income:Service", AccountType.REVENUE, "Revenue from services"),
|
||||||
("Income:Other", AccountType.REVENUE, "Other revenue"),
|
("Income:Other", AccountType.REVENUE, "Other revenue"),
|
||||||
|
|
||||||
# Expenses
|
# Expenses
|
||||||
("Expenses:Utilities", AccountType.EXPENSE, "Electricity, water, internet"),
|
("Expenses:Administrative", AccountType.EXPENSE, "Administrative expenses"),
|
||||||
("Expenses:Food:Supplies", AccountType.EXPENSE, "Food and supplies"),
|
("Expenses:Construction:Materials", AccountType.EXPENSE, "Construction materials"),
|
||||||
("Expenses:Maintenance", AccountType.EXPENSE, "Repairs and maintenance"),
|
("Expenses:Furniture", AccountType.EXPENSE, "Furniture and furnishings"),
|
||||||
("Expenses:Other", AccountType.EXPENSE, "Miscellaneous expenses"),
|
("Expenses:Garden", AccountType.EXPENSE, "Garden supplies and materials"),
|
||||||
|
("Expenses:Gas:Kitchen", AccountType.EXPENSE, "Kitchen gas"),
|
||||||
|
("Expenses:Gas:Vehicle", AccountType.EXPENSE, "Vehicle gas and fuel"),
|
||||||
|
("Expenses:Groceries", AccountType.EXPENSE, "Groceries and food"),
|
||||||
|
("Expenses:Hardware", AccountType.EXPENSE, "Hardware and tools"),
|
||||||
|
("Expenses:Housewares", AccountType.EXPENSE, "Housewares and household items"),
|
||||||
|
("Expenses:Insurance", AccountType.EXPENSE, "Insurance premiums"),
|
||||||
|
("Expenses:Kitchen", AccountType.EXPENSE, "Kitchen supplies and equipment"),
|
||||||
|
("Expenses:Maintenance:Car", AccountType.EXPENSE, "Car maintenance and repairs"),
|
||||||
|
("Expenses:Maintenance:Garden", AccountType.EXPENSE, "Garden maintenance"),
|
||||||
|
("Expenses:Maintenance:Property", AccountType.EXPENSE, "Property maintenance and repairs"),
|
||||||
|
("Expenses:Membership", AccountType.EXPENSE, "Membership fees"),
|
||||||
|
("Expenses:Supplies", AccountType.EXPENSE, "General supplies"),
|
||||||
|
("Expenses:Tools", AccountType.EXPENSE, "Tools and equipment"),
|
||||||
|
("Expenses:Utilities:Electric", AccountType.EXPENSE, "Electricity"),
|
||||||
|
("Expenses:Utilities:Internet", AccountType.EXPENSE, "Internet service"),
|
||||||
|
("Expenses:WebHosting:Domain", AccountType.EXPENSE, "Domain registration"),
|
||||||
|
("Expenses:WebHosting:Wix", AccountType.EXPENSE, "Wix hosting service"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -452,3 +452,37 @@ async def m011_account_permissions(db):
|
||||||
WHERE expires_at IS NOT NULL;
|
WHERE expires_at IS NOT NULL;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m012_update_default_accounts(db):
|
||||||
|
"""
|
||||||
|
Update default chart of accounts to include more detailed hierarchical structure.
|
||||||
|
Adds new accounts for fixed assets, livestock, equity contributions, and detailed expenses.
|
||||||
|
Only adds accounts that don't already exist.
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
from .account_utils import DEFAULT_HIERARCHICAL_ACCOUNTS
|
||||||
|
|
||||||
|
for name, account_type, description in DEFAULT_HIERARCHICAL_ACCOUNTS:
|
||||||
|
# Check if account already exists
|
||||||
|
existing = await db.fetchone(
|
||||||
|
"""
|
||||||
|
SELECT id FROM accounts WHERE name = :name
|
||||||
|
""",
|
||||||
|
{"name": name}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not existing:
|
||||||
|
# Create new account
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO accounts (id, name, account_type, description, created_at)
|
||||||
|
VALUES (:id, :name, :type, :description, {db.timestamp_now})
|
||||||
|
""",
|
||||||
|
{
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"name": name,
|
||||||
|
"type": account_type.value,
|
||||||
|
"description": description
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% from "macros.jinja" import window_vars with context %}
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
|
|
||||||
|
|
@ -41,21 +42,21 @@
|
||||||
|
|
||||||
<!-- Tabs for different views -->
|
<!-- Tabs for different views -->
|
||||||
<q-tabs v-model="activeTab" class="text-primary" dense>
|
<q-tabs v-model="activeTab" class="text-primary" dense>
|
||||||
<q-tab name="by-user" icon="people" label="By User" />
|
<q-tab name="by-user" icon="people" label="By User"></q-tab>
|
||||||
<q-tab name="by-account" icon="account_balance" label="By Account" />
|
<q-tab name="by-account" icon="account_balance" label="By Account"></q-tab>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
|
|
||||||
<q-separator />
|
<q-separator></q-separator>
|
||||||
|
|
||||||
<q-tab-panels v-model="activeTab" animated>
|
<q-tab-panels v-model="activeTab" animated>
|
||||||
<!-- By User View -->
|
<!-- By User View -->
|
||||||
<q-tab-panel name="by-user">
|
<q-tab-panel name="by-user">
|
||||||
<div v-if="loading" class="row justify-center q-pa-md">
|
<div v-if="loading" class="row justify-center q-pa-md">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em"></q-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="permissionsByUser.size === 0" class="text-center q-pa-md">
|
<div v-else-if="permissionsByUser.size === 0" class="text-center q-pa-md">
|
||||||
<q-icon name="info" size="3em" color="grey-5" />
|
<q-icon name="info" size="3em" color="grey-5"></q-icon>
|
||||||
<p class="text-grey-6">No permissions granted yet</p>
|
<p class="text-grey-6">No permissions granted yet</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -63,14 +64,14 @@
|
||||||
<q-card v-for="[userId, userPerms] in permissionsByUser" :key="userId" flat bordered>
|
<q-card v-for="[userId, userPerms] in permissionsByUser" :key="userId" flat bordered>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6 q-mb-sm">
|
<div class="text-h6 q-mb-sm">
|
||||||
<q-icon name="person" class="q-mr-sm" />
|
<q-icon name="person" class="q-mr-sm"></q-icon>
|
||||||
User: {% raw %}{{ userId }}{% endraw %}
|
User: {% raw %}{{ userId }}{% endraw %}
|
||||||
</div>
|
</div>
|
||||||
<q-list separator>
|
<q-list separator>
|
||||||
<q-item v-for="perm in userPerms" :key="perm.id">
|
<q-item v-for="perm in userPerms" :key="perm.id">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar :color="getPermissionColor(perm.permission_type)" text-color="white" size="sm">
|
<q-avatar :color="getPermissionColor(perm.permission_type)" text-color="white" size="sm">
|
||||||
<q-icon :name="getPermissionIcon(perm.permission_type)" />
|
<q-icon :name="getPermissionIcon(perm.permission_type)"></q-icon>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
|
@ -112,11 +113,11 @@
|
||||||
<!-- By Account View -->
|
<!-- By Account View -->
|
||||||
<q-tab-panel name="by-account">
|
<q-tab-panel name="by-account">
|
||||||
<div v-if="loading" class="row justify-center q-pa-md">
|
<div v-if="loading" class="row justify-center q-pa-md">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em"></q-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="permissionsByAccount.size === 0" class="text-center q-pa-md">
|
<div v-else-if="permissionsByAccount.size === 0" class="text-center q-pa-md">
|
||||||
<q-icon name="info" size="3em" color="grey-5" />
|
<q-icon name="info" size="3em" color="grey-5"></q-icon>
|
||||||
<p class="text-grey-6">No permissions granted yet</p>
|
<p class="text-grey-6">No permissions granted yet</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -124,14 +125,14 @@
|
||||||
<q-card v-for="[accountId, accountPerms] in permissionsByAccount" :key="accountId" flat bordered>
|
<q-card v-for="[accountId, accountPerms] in permissionsByAccount" :key="accountId" flat bordered>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6 q-mb-sm">
|
<div class="text-h6 q-mb-sm">
|
||||||
<q-icon name="account_balance" class="q-mr-sm" />
|
<q-icon name="account_balance" class="q-mr-sm"></q-icon>
|
||||||
{% raw %}{{ getAccountName(accountId) }}{% endraw %}
|
{% raw %}{{ getAccountName(accountId) }}{% endraw %}
|
||||||
</div>
|
</div>
|
||||||
<q-list separator>
|
<q-list separator>
|
||||||
<q-item v-for="perm in accountPerms" :key="perm.id">
|
<q-item v-for="perm in accountPerms" :key="perm.id">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar :color="getPermissionColor(perm.permission_type)" text-color="white" size="sm">
|
<q-avatar :color="getPermissionColor(perm.permission_type)" text-color="white" size="sm">
|
||||||
<q-icon :name="getPermissionIcon(perm.permission_type)" />
|
<q-icon :name="getPermissionIcon(perm.permission_type)"></q-icon>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
|
@ -196,7 +197,7 @@
|
||||||
:rules="[val => !!val || 'User ID is required']"
|
:rules="[val => !!val || 'User ID is required']"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="person" />
|
<q-icon name="person"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
|
|
@ -215,7 +216,7 @@
|
||||||
:rules="[val => !!val || 'Account is required']"
|
:rules="[val => !!val || 'Account is required']"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="account_balance" />
|
<q-icon name="account_balance"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
|
|
@ -234,7 +235,7 @@
|
||||||
:rules="[val => !!val || 'Permission type is required']"
|
:rules="[val => !!val || 'Permission type is required']"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="security" />
|
<q-icon name="security"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:option="scope">
|
<template v-slot:option="scope">
|
||||||
<q-item v-bind="scope.itemProps">
|
<q-item v-bind="scope.itemProps">
|
||||||
|
|
@ -256,7 +257,7 @@
|
||||||
dense
|
dense
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="event" />
|
<q-icon name="event"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
|
|
@ -271,13 +272,13 @@
|
||||||
rows="3"
|
rows="3"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="note" />
|
<q-icon name="note"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="grey" v-close-popup />
|
<q-btn flat label="Cancel" color="grey" v-close-popup></q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
label="Grant Permission"
|
label="Grant Permission"
|
||||||
|
|
@ -285,7 +286,7 @@
|
||||||
@click="grantPermission"
|
@click="grantPermission"
|
||||||
:loading="granting"
|
:loading="granting"
|
||||||
:disable="!isGrantFormValid"
|
:disable="!isGrantFormValid"
|
||||||
/>
|
></q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
@ -322,14 +323,14 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="grey" v-close-popup />
|
<q-btn flat label="Cancel" color="grey" v-close-popup></q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
label="Revoke"
|
label="Revoke"
|
||||||
color="negative"
|
color="negative"
|
||||||
@click="revokePermission"
|
@click="revokePermission"
|
||||||
:loading="revoking"
|
:loading="revoking"
|
||||||
/>
|
></q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue