From e791fb2a8a3dabddd50c578ce4ff480d91deff46 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Fri, 22 Nov 2024 18:12:40 +1100 Subject: [PATCH] Implement balance assertions view --- src/db.ts | 28 +++++- src/main.ts | 1 + src/pages/BalanceAssertionsView.vue | 132 ++++++++++++++++++++++++++++ src/pages/HomeView.vue | 2 +- src/reporting.ts | 56 ++++++++---- 5 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 src/pages/BalanceAssertionsView.vue diff --git a/src/db.ts b/src/db.ts index 84ddd34..f4688e4 100644 --- a/src/db.ts +++ b/src/db.ts @@ -68,7 +68,10 @@ export async function totalBalances(session: ExtendedDatabase): Promise [x.account, x.quantity])); } +export async function totalBalancesAtDate(session: ExtendedDatabase, dt: string): Promise> { + await updateRunningBalances(session); + + const resultsRaw: {account: string, quantity: number}[] = await session.select( + `SELECT p3.account AS account, running_balance AS quantity FROM + ( + SELECT p1.account, max(p2.transaction_id) AS max_tid FROM + ( + SELECT account, max(dt) AS max_dt + FROM postings + JOIN transactions ON postings.transaction_id = transactions.id + WHERE DATE(dt) <= DATE($1) + GROUP BY account + ) p1 + JOIN postings p2 ON p1.account = p2.account AND p1.max_dt = transactions.dt JOIN transactions ON p2.transaction_id = transactions.id GROUP BY p2.account + ) p3 + JOIN postings p4 ON p3.account = p4.account AND p3.max_tid = p4.transaction_id ORDER BY account`, + [dt] + ); + + return new Map(resultsRaw.map((x) => [x.account, x.quantity])); +} + export async function updateRunningBalances(session: ExtendedDatabase) { // TODO: This is very slow - it would be faster to do this in Rust diff --git a/src/main.ts b/src/main.ts index 6e2de04..824f9fb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,6 +30,7 @@ async function initApp() { // Init router const routes = [ { path: '/', name: 'index', component: () => import('./pages/HomeView.vue') }, + { path: '/balance-assertions', name: 'balance-assertions', component: () => import('./pages/BalanceAssertionsView.vue') }, { path: '/chart-of-accounts', name: 'chart-of-accounts', component: () => import('./pages/ChartOfAccountsView.vue') }, { path: '/general-ledger', name: 'general-ledger', component: () => import('./pages/GeneralLedgerView.vue') }, { path: '/journal', name: 'journal', component: () => import('./pages/JournalView.vue') }, diff --git a/src/pages/BalanceAssertionsView.vue b/src/pages/BalanceAssertionsView.vue new file mode 100644 index 0000000..5343bc3 --- /dev/null +++ b/src/pages/BalanceAssertionsView.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/pages/HomeView.vue b/src/pages/HomeView.vue index 40131e0..7f07c06 100644 --- a/src/pages/HomeView.vue +++ b/src/pages/HomeView.vue @@ -23,7 +23,7 @@
  • Journal
  • Statement lines
  • - +
  • Balance assertions
  • Chart of accounts
diff --git a/src/reporting.ts b/src/reporting.ts index b533bd9..65685fd 100644 --- a/src/reporting.ts +++ b/src/reporting.ts @@ -17,7 +17,7 @@ */ import { asCost } from './amounts.ts'; -import { JoinedTransactionPosting, StatementLine, Transaction, joinedToTransactions, totalBalances } from './db.ts'; +import { JoinedTransactionPosting, StatementLine, Transaction, joinedToTransactions, totalBalances, totalBalancesAtDate } from './db.ts'; import { ExtendedDatabase } from './dbutil.ts'; export enum ReportingStage { @@ -32,7 +32,7 @@ export class ReportingWorkflow { transactionsForStage: Map = new Map(); reportsForStage: Map = new Map(); - async generate(session: ExtendedDatabase) { + async generate(session: ExtendedDatabase, dt?: string) { // ------------------------ // TransactionsFromDatabase @@ -40,16 +40,32 @@ export class ReportingWorkflow { { // Load balances from database - balances = await totalBalances(session); + if (dt) { + balances = await totalBalancesAtDate(session, dt); + } else { + balances = await totalBalances(session); + } this.reportsForStage.set(ReportingStage.TransactionsFromDatabase, [new TrialBalanceReport(balances)]); // Load transactions from database - const joinedTransactionPostings: JoinedTransactionPosting[] = await session.select( - `SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity, running_balance - FROM transactions - JOIN postings ON transactions.id = postings.transaction_id - ORDER BY dt, transaction_id, postings.id` - ); + let joinedTransactionPostings: JoinedTransactionPosting[]; + if (dt) { + joinedTransactionPostings = await session.select( + `SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity, running_balance + FROM transactions + JOIN postings ON transactions.id = postings.transaction_id + WHERE DATE(dt) <= DATE($1) + ORDER BY dt, transaction_id, postings.id`, + [dt] + ); + } else { + joinedTransactionPostings = await session.select( + `SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity, running_balance + FROM transactions + JOIN postings ON transactions.id = postings.transaction_id + ORDER BY dt, transaction_id, postings.id` + ); + } const transactions = joinedToTransactions(joinedTransactionPostings); this.transactionsForStage.set(ReportingStage.TransactionsFromDatabase, transactions); } @@ -59,12 +75,22 @@ export class ReportingWorkflow { { // Get unreconciled statement lines - const unreconciledStatementLines: StatementLine[] = await session.select( - // On testing, JOIN is much faster than WHERE NOT EXISTS - `SELECT statement_lines.* FROM statement_lines - LEFT JOIN statement_line_reconciliations ON statement_lines.id = statement_line_reconciliations.statement_line_id - WHERE statement_line_reconciliations.id IS NULL` - ); + let unreconciledStatementLines: StatementLine[]; + if (dt) { + unreconciledStatementLines = await session.select( + // On testing, JOIN is much faster than WHERE NOT EXISTS + `SELECT statement_lines.* FROM statement_lines + LEFT JOIN statement_line_reconciliations ON statement_lines.id = statement_line_reconciliations.statement_line_id + WHERE statement_line_reconciliations.id IS NULL AND DATE(dt) <= DATE($1)`, + [dt] + ); + } else { + unreconciledStatementLines = await session.select( + `SELECT statement_lines.* FROM statement_lines + LEFT JOIN statement_line_reconciliations ON statement_lines.id = statement_line_reconciliations.statement_line_id + WHERE statement_line_reconciliations.id IS NULL` + ); + } const transactions = []; for (const line of unreconciledStatementLines) {