Compare commits

...

5 Commits

17 changed files with 385 additions and 65 deletions

162
docs/demo.sql Normal file
View File

@ -0,0 +1,162 @@
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE account_configurations (
id INTEGER NOT NULL,
account VARCHAR,
kind VARCHAR,
data JSON,
PRIMARY KEY(id)
);
INSERT INTO account_configurations VALUES(1,'Business Loan','drcr.liability',NULL);
INSERT INTO account_configurations VALUES(2,'Cash at Bank','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(3,'Cash on Hand','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(4,'Cost of Goods Sold','drcr.expense',NULL);
INSERT INTO account_configurations VALUES(5,'Depreciation','drcr.expense',NULL);
INSERT INTO account_configurations VALUES(6,'Forex Gains','drcr.income',NULL);
INSERT INTO account_configurations VALUES(7,'Interest','drcr.expense',NULL);
INSERT INTO account_configurations VALUES(8,'International Account','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(9,'Inventory','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(10,'Opening Balances','drcr.equity',NULL);
INSERT INTO account_configurations VALUES(11,'Plant','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(12,'Plant:Less Accumulated Depreciation','drcr.asset',NULL);
INSERT INTO account_configurations VALUES(13,'Sales','drcr.income',NULL);
CREATE TABLE balance_assertions (
id INTEGER NOT NULL,
dt DATETIME,
description VARCHAR,
account VARCHAR,
quantity INTEGER,
commodity VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE metadata (
id INTEGER NOT NULL,
key VARCHAR,
value VARCHAR,
PRIMARY KEY(id)
);
INSERT INTO metadata VALUES(1,'version','3');
INSERT INTO metadata VALUES(2,'eofy_date','2025-06-30');
INSERT INTO metadata VALUES(3,'reporting_commodity','$');
INSERT INTO metadata VALUES(4,'amount_dps','2');
INSERT INTO metadata VALUES(5,'plugins','');
CREATE TABLE postings (
id INTEGER NOT NULL,
transaction_id INTEGER,
description VARCHAR,
account VARCHAR,
quantity INTEGER,
commodity VARCHAR,
PRIMARY KEY(id),
FOREIGN KEY(transaction_id) REFERENCES transactions(id)
);
INSERT INTO postings VALUES(1,1,NULL,'Cash at Bank',100000,'$');
INSERT INTO postings VALUES(2,1,NULL,'Opening Balances',-100000,'$');
INSERT INTO postings VALUES(3,2,NULL,'Cash on Hand',5000,'$');
INSERT INTO postings VALUES(4,2,NULL,'Opening Balances',-5000,'$');
INSERT INTO postings VALUES(5,3,NULL,'Inventory',10000,'Widgets {5.00}');
INSERT INTO postings VALUES(6,3,NULL,'Opening Balances',-10000,'Widgets {5.00}');
INSERT INTO postings VALUES(7,4,NULL,'Plant',500000,'$');
INSERT INTO postings VALUES(8,4,NULL,'Opening Balances',-500000,'$');
INSERT INTO postings VALUES(9,5,NULL,'Cash at Bank',50000,'$');
INSERT INTO postings VALUES(10,5,NULL,'Business Loan',-50000,'$');
INSERT INTO postings VALUES(11,6,NULL,'International Account',10000,'EUR {1.75}');
INSERT INTO postings VALUES(12,6,NULL,'Cash at Bank',-17500,'$');
INSERT INTO postings VALUES(13,7,NULL,'Inventory',5000,'Widgets {7.00}');
INSERT INTO postings VALUES(14,7,NULL,'Cash at Bank',-35000,'$');
INSERT INTO postings VALUES(15,8,NULL,'Cash at Bank',10000,'$');
INSERT INTO postings VALUES(16,8,NULL,'Sales',-10000,'$');
INSERT INTO postings VALUES(17,9,NULL,'Cost of Goods Sold',5000,'$');
INSERT INTO postings VALUES(18,9,NULL,'Inventory',-1000,'Widgets {5.00}');
INSERT INTO postings VALUES(19,10,NULL,'International Account',10000,'EUR {1.70}');
INSERT INTO postings VALUES(20,10,NULL,'Cash at Bank',-17000,'$');
INSERT INTO postings VALUES(21,11,NULL,'Cash at Bank',9000,'$');
INSERT INTO postings VALUES(22,11,NULL,'International Account',-5000,'EUR {1.75}');
INSERT INTO postings VALUES(23,11,NULL,'Forex Gains',-250,'$');
INSERT INTO postings VALUES(24,12,NULL,'Interest',10000,'$');
INSERT INTO postings VALUES(25,12,NULL,'Business Loan',-10000,'$');
INSERT INTO postings VALUES(26,13,NULL,'Depreciation',50000,'$');
INSERT INTO postings VALUES(27,13,NULL,'Plant:Less Accumulated Depreciation',-50000,'$');
CREATE TABLE statement_line_reconciliations (
id INTEGER NOT NULL,
statement_line_id INTEGER,
posting_id INTEGER,
PRIMARY KEY(id),
FOREIGN KEY(statement_line_id) REFERENCES statement_lines(id),
FOREIGN KEY(posting_id) REFERENCES postings(id)
);
CREATE TABLE statement_lines (
id INTEGER NOT NULL,
source_account VARCHAR,
dt DATETIME,
description VARCHAR,
quantity INTEGER,
balance INTEGER,
commodity VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE transactions (
id INTEGER NOT NULL,
dt DATETIME,
description VARCHAR,
PRIMARY KEY(id)
);
INSERT INTO transactions VALUES(1,'2024-06-30 00:00:00.000000','Conversion balances');
INSERT INTO transactions VALUES(2,'2024-06-30 00:00:00.000000','Conversion balances');
INSERT INTO transactions VALUES(3,'2024-06-30 00:00:00.000000','Conversion balances');
INSERT INTO transactions VALUES(4,'2024-06-30 00:00:00.000000','Opening balances');
INSERT INTO transactions VALUES(5,'2024-07-01 00:00:00.000000','Loan');
INSERT INTO transactions VALUES(6,'2024-07-02 00:00:00.000000','Application');
INSERT INTO transactions VALUES(7,'2024-07-03 00:00:00.000000','Inventory purchases');
INSERT INTO transactions VALUES(8,'2024-07-04 00:00:00.000000','Sale');
INSERT INTO transactions VALUES(9,'2024-07-04 00:00:00.000000','Sale');
INSERT INTO transactions VALUES(10,'2024-08-01 00:00:00.000000','Application');
INSERT INTO transactions VALUES(11,'2024-09-01 00:00:00.000000','Redemption');
INSERT INTO transactions VALUES(12,'2025-06-30 00:00:00.000000','Interest on business loan');
INSERT INTO transactions VALUES(13,'2025-06-30 00:00:00.000000','Depreciation');
CREATE TABLE austax_cgt_cost_adjustments (
id INTEGER NOT NULL,
quantity INTEGER,
commodity VARCHAR,
account VARCHAR,
acquisition_dt DATETIME,
dt DATETIME,
description VARCHAR,
cost_adjustment INTEGER,
PRIMARY KEY (id)
);
CREATE VIEW joined_transactions AS
SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity
FROM transactions
JOIN postings ON transactions.id = postings.transaction_id
ORDER BY dt, transaction_id, postings.id;
CREATE VIEW transactions_with_quantity_ascost AS
SELECT
*,
CAST(ROUND(
-- If already in reporting commodity
IIF(
commodity = '$',
quantity,
-- Else if specified as total cost
IIF(
commodity LIKE '% {{%}}',
substr(commodity, instr(commodity, ' {{') + 3, length(commodity) - instr(commodity, ' {{') - 4) * sign(quantity) * 100,
-- Else if specified as unit cost
IIF(
commodity LIKE '% {%}',
substr(commodity, instr(commodity, ' {') + 2, length(commodity) - instr(commodity, ' {') - 2) * quantity,
-- Unexpected
NULL
)
)
)
) AS INTEGER) AS quantity_ascost
FROM joined_transactions
;
CREATE VIEW transactions_with_running_balances AS
SELECT
*,
SUM(quantity_ascost) OVER (PARTITION BY account ROWS UNBOUNDED PRECEDING) AS running_balance
FROM transactions_with_quantity_ascost;
COMMIT;

View File

@ -1,5 +1,5 @@
--!strict --!strict
-- DrCr: Web-based double-entry bookkeeping framework -- DrCr: Double-entry bookkeeping framework
-- Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) -- Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
-- --
-- This program is free software: you can redistribute it and/or modify -- This program is free software: you can redistribute it and/or modify
@ -15,8 +15,8 @@
-- You should have received a copy of the GNU Affero General Public License -- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>. -- along with this program. If not, see <https://www.gnu.org/licenses/>.
local libdrcr = require('../libdrcr') local libdrcr = require('./libdrcr')
local reporting = require('../austax/reporting') local reporting = require('./austax/reporting')
local plugin: libdrcr.Plugin = { local plugin: libdrcr.Plugin = {
name = 'austax', name = 'austax',

View File

@ -1,5 +1,5 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -216,6 +216,7 @@ pub struct DbMetadata {
pub eofy_date: NaiveDate, pub eofy_date: NaiveDate,
pub reporting_commodity: String, pub reporting_commodity: String,
pub dps: u32, pub dps: u32,
pub plugins: Vec<String>,
} }
impl DbMetadata { impl DbMetadata {
@ -256,11 +257,27 @@ impl DbMetadata {
.await .await
.expect("SQL error"); .expect("SQL error");
let plugins_joined = sqlx::query("SELECT value FROM metadata WHERE key = 'plugins'")
.map(|r: SqliteRow| r.get::<String, _>(0))
.fetch_one(&mut *connection)
.await
.expect("SQL error");
let plugins = if plugins_joined.len() > 0 {
plugins_joined
.split(';')
.map(String::from)
.collect::<Vec<_>>()
} else {
vec![]
};
DbMetadata { DbMetadata {
version, version,
eofy_date, eofy_date,
reporting_commodity, reporting_commodity,
dps, dps,
plugins,
} }
} }
} }

View File

@ -39,7 +39,7 @@ async fn main() {
let mut context = ReportingContext::new( let mut context = ReportingContext::new(
db_connection, db_connection,
"plugins".to_string(), "plugins".to_string(),
vec!["austax.plugin".to_string()], vec!["austax".to_string()],
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(), NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
"$".to_string(), "$".to_string(),
); );

View File

@ -1,5 +1,5 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -41,7 +41,7 @@ fn load_plugin(plugin_dir: &str, plugin_name: &str) -> (Lua, Plugin) {
// Init Lua environment // Init Lua environment
let package = lua.globals().get::<Table>("package").unwrap(); let package = lua.globals().get::<Table>("package").unwrap();
package package
.set("path", format!("{}/?.luau", plugin_dir)) .set("path", format!("{0}/?.luau;{0}/?/init.luau", plugin_dir))
.unwrap(); .unwrap();
// Require and call the plugin // Require and call the plugin

View File

@ -1,5 +1,5 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -42,11 +42,6 @@ fn prepare_reporting_context(context: &mut ReportingContext) {
libdrcr::plugin::register_lookup_fns(context); libdrcr::plugin::register_lookup_fns(context);
} }
fn get_plugins() -> Vec<String> {
// FIXME: Dynamically get this
vec!["austax.plugin".to_string()]
}
pub(crate) async fn get_report( pub(crate) async fn get_report(
app: AppHandle, app: AppHandle,
state: State<'_, Mutex<AppState>>, state: State<'_, Mutex<AppState>>,
@ -61,6 +56,7 @@ pub(crate) async fn get_report(
// Initialise ReportingContext // Initialise ReportingContext
let eofy_date = db_connection.metadata().eofy_date; let eofy_date = db_connection.metadata().eofy_date;
let plugin_names = db_connection.metadata().plugins.clone();
let mut context = ReportingContext::new( let mut context = ReportingContext::new(
db_connection, db_connection,
app.path() app.path()
@ -69,22 +65,25 @@ pub(crate) async fn get_report(
.to_str() .to_str()
.unwrap() .unwrap()
.to_string(), .to_string(),
get_plugins(), plugin_names,
eofy_date, eofy_date,
"$".to_string(), "$".to_string(),
); );
prepare_reporting_context(&mut context); prepare_reporting_context(&mut context);
// Get dynamic report // Get dynamic report
let targets = vec![ let mut targets = vec![target.clone()];
// FIXME: Make this configurable
ReportingProductId { // Add plugin targets
// FIXME: Detect this robustly
if context.plugin_names.contains(&"austax".to_string()) {
targets.push(ReportingProductId {
name: "CalculateIncomeTax".to_string(), name: "CalculateIncomeTax".to_string(),
kind: ReportingProductKind::Transactions, kind: ReportingProductKind::Transactions,
args: ReportingStepArgs::VoidArgs, args: ReportingStepArgs::VoidArgs,
}, });
target.clone(), }
];
let products = generate_report(targets, Arc::new(context)).await.unwrap(); let products = generate_report(targets, Arc::new(context)).await.unwrap();
let result = products.get_owned_or_err(&target).unwrap(); let result = products.get_owned_or_err(&target).unwrap();
@ -262,6 +261,7 @@ pub(crate) async fn get_validated_balance_assertions(
// Initialise ReportingContext // Initialise ReportingContext
let eofy_date = db_connection.metadata().eofy_date; let eofy_date = db_connection.metadata().eofy_date;
let plugin_names = db_connection.metadata().plugins.clone();
let mut context = ReportingContext::new( let mut context = ReportingContext::new(
db_connection, db_connection,
app.path() app.path()
@ -270,7 +270,7 @@ pub(crate) async fn get_validated_balance_assertions(
.to_str() .to_str()
.unwrap() .unwrap()
.to_string(), .to_string(),
get_plugins(), plugin_names,
eofy_date, eofy_date,
"$".to_string(), "$".to_string(),
); );

View File

@ -339,7 +339,7 @@
async function onAmountChange(posting: EditingPosting) { async function onAmountChange(posting: EditingPosting) {
// Synchronise the amounts if only two postings // Synchronise the amounts if only two postings
if (transaction.postings.length == 2) { if (transaction.postings.length === 2 && posting === transaction.postings[0]) {
for (const otherPosting of transaction.postings) { for (const otherPosting of transaction.postings) {
if (otherPosting !== posting) { if (otherPosting !== posting) {
otherPosting.amount_abs = posting.amount_abs; otherPosting.amount_abs = posting.amount_abs;

View File

@ -1,5 +1,5 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -23,7 +23,7 @@ import { readTextFile } from '@tauri-apps/plugin-fs';
import Database from '@tauri-apps/plugin-sql'; import Database from '@tauri-apps/plugin-sql';
import { reactive } from 'vue'; import { reactive } from 'vue';
import { Balance } from './amounts.ts'; import { asCost } from './amounts.ts';
import { ExtendedDatabase } from './dbutil.ts'; import { ExtendedDatabase } from './dbutil.ts';
import { CriticalError } from './error.ts'; import { CriticalError } from './error.ts';
@ -39,6 +39,7 @@ export const db = reactive({
eofy_date: null! as string, eofy_date: null! as string,
reporting_commodity: null! as string, reporting_commodity: null! as string,
dps: null! as number, dps: null! as number,
plugins: null! as string[],
}, },
init: async function(filename: string | null): Promise<void> { init: async function(filename: string | null): Promise<void> {
@ -77,6 +78,7 @@ export const db = reactive({
this.metadata.eofy_date = metadataObject.eofy_date; this.metadata.eofy_date = metadataObject.eofy_date;
this.metadata.reporting_commodity = metadataObject.reporting_commodity; this.metadata.reporting_commodity = metadataObject.reporting_commodity;
this.metadata.dps = parseInt(metadataObject.amount_dps); this.metadata.dps = parseInt(metadataObject.amount_dps);
this.metadata.plugins = metadataObject.plugins.length > 0 ? metadataObject.plugins.split(';') : [];
} }
}, },
@ -114,6 +116,10 @@ export async function createNewDatabase(filename: string, eofy_date: string, rep
`INSERT INTO metadata (key, value) VALUES (?, ?)`, `INSERT INTO metadata (key, value) VALUES (?, ?)`,
['amount_dps', dps.toString()] // Manually call .toString() to format as int, otherwise sqlx formats as float ['amount_dps', dps.toString()] // Manually call .toString() to format as int, otherwise sqlx formats as float
); );
await transaction.execute(
`INSERT INTO metadata (key, value) VALUES (?, ?)`,
['plugins', '']
);
await transaction.commit(); await transaction.commit();
} }
@ -274,12 +280,11 @@ export class Transaction {
) {} ) {}
doesBalance(): boolean { doesBalance(): boolean {
const balance = new Balance(); let total = 0;
for (const posting of this.postings) { for (const posting of this.postings) {
balance.add(posting.quantity, posting.commodity); total += asCost(posting.quantity, posting.commodity);
} }
balance.clean(); return total === 0;
return balance.amounts.length === 0;
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -20,16 +20,26 @@ import { invoke } from '@tauri-apps/api/core';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router'; import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
import App from './App.vue'; import App from './App.vue';
import { db } from './db.ts'; import { db } from './db.ts';
import { handleCriticalError } from './error.ts'; import { handleCriticalError } from './error.ts';
import austax from './plugins/austax/plugin.ts';
async function initApp() { async function initApp() {
// Init state
const dbFilename: string = await invoke('get_open_filename');
if (dbFilename !== null) {
try {
await db.init(dbFilename); // Ensure all metadata cached before loading Vue
} catch (err) {
handleCriticalError(err);
}
}
// Init router // Init router
const routes = [ let routes: RouteRecordRaw[] = [
{ path: '/', name: 'index', component: () => import('./pages/HomeView.vue') }, { path: '/', name: 'index', component: () => import('./pages/HomeView.vue') },
{ path: '/balance-assertions', name: 'balance-assertions', component: () => import('./pages/BalanceAssertionsView.vue') }, { path: '/balance-assertions', name: 'balance-assertions', component: () => import('./pages/BalanceAssertionsView.vue') },
{ path: '/balance-assertions/edit/:id', name: 'balance-assertions-edit', component: () => import('./pages/EditBalanceAssertionView.vue') }, { path: '/balance-assertions/edit/:id', name: 'balance-assertions-edit', component: () => import('./pages/EditBalanceAssertionView.vue') },
@ -46,29 +56,18 @@ async function initApp() {
{ path: '/statement-lines/import', name: 'import-statement', component: () => import('./pages/ImportStatementView.vue') }, { path: '/statement-lines/import', name: 'import-statement', component: () => import('./pages/ImportStatementView.vue') },
{ path: '/transactions/:account', name: 'transactions', component: () => import('./pages/TransactionsView.vue') }, { path: '/transactions/:account', name: 'transactions', component: () => import('./pages/TransactionsView.vue') },
{ path: '/trial-balance', name: 'trial-balance', component: () => import('./reports/TrialBalanceReport.vue') }, { path: '/trial-balance', name: 'trial-balance', component: () => import('./reports/TrialBalanceReport.vue') },
// TODO: Generate this list dynamically
{ path: '/austax/cgt-adjustments', name: 'cgt-adjustments', component: () => import('./plugins/austax/CGTAdjustmentsView.vue') },
{ path: '/austax/cgt-adjustments/edit/:id', name: 'cgt-adjustments-edit', component: () => import('./plugins/austax/EditCGTAdjustmentView.vue') },
{ path: '/austax/cgt-adjustments/new', name: 'cgt-adjustments-new', component: () => import('./plugins/austax/NewCGTAdjustmentView.vue') },
{ path: '/austax/cgt-adjustments/multinew', name: 'cgt-adjustments-multinew', component: () => import('./plugins/austax/MultiNewCGTAdjustmentView.vue') },
{ path: '/austax/cgt-assets', name: 'cgt-assets', component: () => import('./plugins/austax/CGTAssetsView.vue') },
{ path: '/austax/tax-summary', name: 'tax-summary', component: () => import('./plugins/austax/TaxSummaryReport.vue') },
]; ];
// Init plugin routes
if (db.metadata.plugins.indexOf('austax') >= 0) {
routes.push(...austax.getRoutes());
}
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes, routes,
}); });
// Init state
const dbFilename: string = await invoke('get_open_filename');
if (dbFilename !== null) {
try {
await db.init(dbFilename); // Ensure all metadata cached before loading Vue
} catch (err) {
handleCriticalError(err);
}
}
// Create Vue app // Create Vue app
createApp(App).use(router).mount('#app'); createApp(App).use(router).mount('#app');
} }

View File

@ -1,5 +1,5 @@
<!-- <!--
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -17,7 +17,7 @@
--> -->
<template> <template>
<div class="grid grid-cols-3 divide-x divide-gray-200"> <div :class="{'grid divide-x divide-gray-200': true, 'grid-cols-2': db.metadata.plugins.indexOf('austax') < 0, 'grid-cols-3': db.metadata.plugins.indexOf('austax') >= 0}">
<div class="pr-4"> <div class="pr-4">
<h2 class="font-medium text-gray-700 mb-2">Data sources</h2> <h2 class="font-medium text-gray-700 mb-2">Data sources</h2>
<ul class="list-disc ml-6"> <ul class="list-disc ml-6">
@ -26,8 +26,7 @@
<li><RouterLink :to="{ name: 'balance-assertions' }" class="text-gray-900 hover:text-blue-700 hover:underline">Balance assertions</RouterLink></li> <li><RouterLink :to="{ name: 'balance-assertions' }" class="text-gray-900 hover:text-blue-700 hover:underline">Balance assertions</RouterLink></li>
<li><RouterLink :to="{ name: 'chart-of-accounts' }" class="text-gray-900 hover:text-blue-700 hover:underline">Chart of accounts</RouterLink></li> <li><RouterLink :to="{ name: 'chart-of-accounts' }" class="text-gray-900 hover:text-blue-700 hover:underline">Chart of accounts</RouterLink></li>
<!-- Plugin reports --> <!-- Plugin reports -->
<!-- TODO: Generate this list dynamically --> <component :is="austax.getDataSourcesLinks()" v-if="db.metadata.plugins.indexOf('austax') >= 0"></component>
<li><RouterLink :to="{ name: 'cgt-adjustments' }" class="text-gray-900 hover:text-blue-700 hover:underline">CGT adjustments</RouterLink></li>
</ul> </ul>
</div> </div>
<div class="px-4"> <div class="px-4">
@ -37,15 +36,21 @@
<li><RouterLink :to="{ name: 'trial-balance' }" class="text-gray-900 hover:text-blue-700 hover:underline">Trial balance</RouterLink></li> <li><RouterLink :to="{ name: 'trial-balance' }" class="text-gray-900 hover:text-blue-700 hover:underline">Trial balance</RouterLink></li>
<li><RouterLink :to="{ name: 'balance-sheet' }" class="text-gray-900 hover:text-blue-700 hover:underline">Balance sheet</RouterLink></li> <li><RouterLink :to="{ name: 'balance-sheet' }" class="text-gray-900 hover:text-blue-700 hover:underline">Balance sheet</RouterLink></li>
<li><RouterLink :to="{ name: 'income-statement' }" class="text-gray-900 hover:text-blue-700 hover:underline">Income statement</RouterLink></li> <li><RouterLink :to="{ name: 'income-statement' }" class="text-gray-900 hover:text-blue-700 hover:underline">Income statement</RouterLink></li>
<!-- Plugin reports -->
<component :is="austax.getGeneralReportsLinks()" v-if="db.metadata.plugins.indexOf('austax') >= 0"></component>
</ul> </ul>
</div> </div>
<div class="pl-4"> <div class="pl-4" v-if="db.metadata.plugins.indexOf('austax') >= 0">
<h2 class="font-medium text-gray-700 mb-2">Advanced reports</h2> <h2 class="font-medium text-gray-700 mb-2">Advanced reports</h2>
<ul class="list-disc ml-6"> <ul class="list-disc ml-6">
<!-- TODO: Generate this list dynamically --> <!-- Plugin reports -->
<li><RouterLink :to="{ name: 'cgt-assets' }" class="text-gray-900 hover:text-blue-700 hover:underline">CGT assets</RouterLink></li> <component :is="austax.getAdvancedReportsLinks()" v-if="db.metadata.plugins.indexOf('austax') >= 0"></component>
<li><RouterLink :to="{ name: 'tax-summary' }" class="text-gray-900 hover:text-blue-700 hover:underline">Tax summary</RouterLink></li>
</ul> </ul>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts">
import { db } from '../db.ts';
import austax from '../plugins/austax/plugin.ts';
</script>

28
src/plugin.ts Normal file
View File

@ -0,0 +1,28 @@
/*
DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Component } from 'vue';
import { RouteRecordRaw } from 'vue-router';
export interface Plugin {
getAccountKinds: () => Promise<[string, string][]>,
getAdvancedReportsLinks: () => Component,
getDataSourcesLinks: () => Component,
getGeneralReportsLinks: () => Component,
getRoutes: () => RouteRecordRaw[],
}

View File

@ -0,0 +1,22 @@
<!--
DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<li><RouterLink :to="{ name: 'cgt-assets' }" class="text-gray-900 hover:text-blue-700 hover:underline">CGT assets</RouterLink></li>
<li><RouterLink :to="{ name: 'tax-summary' }" class="text-gray-900 hover:text-blue-700 hover:underline">Tax summary</RouterLink></li>
</template>

View File

@ -1,5 +1,5 @@
<!-- <!--
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -90,8 +90,6 @@
const session = await db.load(); const session = await db.load();
cgtAssets.value = await getCGTAssets(session); cgtAssets.value = await getCGTAssets(session);
eofyDate.value = dayjs(db.metadata.eofy_date); eofyDate.value = dayjs(db.metadata.eofy_date);
console.log(cgtAssets.value);
} }
load(); load();

View File

@ -0,0 +1,21 @@
<!--
DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<li><RouterLink :to="{ name: 'cgt-adjustments' }" class="text-gray-900 hover:text-blue-700 hover:underline">CGT adjustments</RouterLink></li>
</template>

View File

@ -0,0 +1,20 @@
<!--
DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
</template>

View File

@ -0,0 +1,40 @@
/*
DrCr: Double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getAccountKinds } from './account_kinds.ts';
import DataSourcesLinks from './DataSourcesLinks.vue';
import GeneralReportsLinks from './GeneralReportsLinks.vue';
import AdvancedReportsLinks from './AdvancedReportsLinks.vue';
import { Plugin } from '../../plugin.ts';
export default {
'getAccountKinds': getAccountKinds,
getDataSourcesLinks: () => DataSourcesLinks,
getGeneralReportsLinks: () => GeneralReportsLinks,
getAdvancedReportsLinks: () => AdvancedReportsLinks,
getRoutes: () => [
{ path: '/austax/cgt-adjustments', name: 'cgt-adjustments', component: () => import('./CGTAdjustmentsView.vue') },
{ path: '/austax/cgt-adjustments/edit/:id', name: 'cgt-adjustments-edit', component: () => import('./EditCGTAdjustmentView.vue') },
{ path: '/austax/cgt-adjustments/new', name: 'cgt-adjustments-new', component: () => import('./NewCGTAdjustmentView.vue') },
{ path: '/austax/cgt-adjustments/multinew', name: 'cgt-adjustments-multinew', component: () => import('./MultiNewCGTAdjustmentView.vue') },
{ path: '/austax/cgt-assets', name: 'cgt-assets', component: () => import('./CGTAssetsView.vue') },
{ path: '/austax/tax-summary', name: 'tax-summary', component: () => import('./TaxSummaryReport.vue') },
],
} as Plugin;

View File

@ -1,6 +1,6 @@
/* /*
DrCr: Web-based double-entry bookkeeping framework DrCr: Double-entry bookkeeping framework
Copyright (C) 20222024 Lee Yingtong Li (RunasSudo) Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -16,7 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as austax from './plugins/austax/account_kinds.ts'; import { db } from './db.ts';
import austax from './plugins/austax/plugin.ts';
export const drcrAccountKinds: [string, string][] = [ export const drcrAccountKinds: [string, string][] = [
['drcr.asset', 'Asset'], ['drcr.asset', 'Asset'],
@ -29,8 +30,10 @@ export const drcrAccountKinds: [string, string][] = [
export async function getAccountKinds() { export async function getAccountKinds() {
const accountKinds = [...drcrAccountKinds]; const accountKinds = [...drcrAccountKinds];
// FIXME: Make this customisable // Add plugin account kinds
accountKinds.push(...await austax.getAccountKinds()); if (db.metadata.plugins.indexOf('austax') >= 0) {
accountKinds.push(...await austax.getAccountKinds());
}
return accountKinds; return accountKinds;
} }