Balance assertions view using libdrcr

This commit is contained in:
RunasSudo 2025-05-28 00:16:09 +10:00
parent bbcb3cee6f
commit ffef2d16dc
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
3 changed files with 99 additions and 43 deletions

View File

@ -94,6 +94,7 @@ pub fn run() {
libdrcr_bridge::get_balance_sheet,
libdrcr_bridge::get_income_statement,
libdrcr_bridge::get_trial_balance,
libdrcr_bridge::get_validated_balance_assertions,
sql::sql_transaction_begin,
sql::sql_transaction_execute,
sql::sql_transaction_select,

View File

@ -16,19 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::collections::HashSet;
use std::sync::Arc;
use chrono::NaiveDate;
use libdrcr::db::DbConnection;
use libdrcr::model::assertions::BalanceAssertion;
use libdrcr::reporting::builders::register_dynamic_builders;
use libdrcr::reporting::dynamic_report::DynamicReport;
use libdrcr::reporting::generate_report;
use libdrcr::reporting::steps::register_lookup_fns;
use libdrcr::reporting::types::{
DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
BalancesAt, DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
ReportingContext, ReportingProduct, ReportingProductId, ReportingProductKind, Transactions,
VoidArgs,
};
use serde::{Deserialize, Serialize};
use tauri::State;
use tokio::sync::Mutex;
@ -163,3 +166,88 @@ pub(crate) async fn get_trial_balance(
.unwrap()
.to_json())
}
#[derive(Deserialize, Serialize)]
struct ValidatedBalanceAssertion {
#[serde(flatten)]
assertion: BalanceAssertion,
is_valid: bool,
}
#[tauri::command]
pub(crate) async fn get_validated_balance_assertions(
state: State<'_, Mutex<AppState>>,
) -> Result<String, ()> {
let state = state.lock().await;
let db_filename = state.db_filename.clone().unwrap();
// Connect to database
let db_connection =
DbConnection::new(format!("sqlite:{}", db_filename.as_str()).as_str()).await;
let reporting_commodity = db_connection.metadata().reporting_commodity.clone(); // Needed later
// First get balance assertions from database
let balance_assertions = db_connection.get_balance_assertions().await;
// Get dates of balance assertions
let dates = balance_assertions
.iter()
.map(|b| b.dt)
.collect::<HashSet<_>>();
// Initialise ReportingContext
let eofy_date = db_connection.metadata().eofy_date;
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
register_lookup_fns(&mut context);
register_dynamic_builders(&mut context);
// Get report targets
let mut targets = vec![ReportingProductId {
name: "CalculateIncomeTax",
kind: ReportingProductKind::Transactions,
args: Box::new(VoidArgs {}),
}];
for dt in dates {
// Request ordinary transaction balances at each balance assertion date
targets.push(ReportingProductId {
name: "CombineOrdinaryTransactions",
kind: ReportingProductKind::BalancesAt,
args: Box::new(DateArgs { date: dt.date() }),
});
}
// Run report
let products = generate_report(targets, Arc::new(context)).await.unwrap();
// Validate each balance assertion
let mut validated_assertions = Vec::new();
for balance_assertion in balance_assertions {
let balances_at_date = products
.get_or_err(&ReportingProductId {
name: "CombineOrdinaryTransactions",
kind: ReportingProductKind::BalancesAt,
args: Box::new(DateArgs {
date: balance_assertion.dt.date(),
}),
})
.unwrap()
.downcast_ref::<BalancesAt>()
.unwrap();
let account_balance = *balances_at_date
.balances
.get(&balance_assertion.account)
.unwrap_or(&0);
let is_valid = balance_assertion.quantity == account_balance
&& balance_assertion.commodity == reporting_commodity;
validated_assertions.push(ValidatedBalanceAssertion {
assertion: balance_assertion,
is_valid,
});
}
Ok(serde_json::to_string(&validated_assertions).unwrap())
}

View File

@ -1,6 +1,6 @@
<!--
DrCr: Web-based double-entry bookkeeping framework
Copyright (C) 20222025 Lee Yingtong Li (RunasSudo)
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
@ -48,8 +48,8 @@
<td class="py-0.5 px-1 text-gray-900 text-end">{{ pp(Math.abs(assertion.quantity)) }}</td>
<td class="py-0.5 pr-1 text-gray-900">{{ assertion.quantity >= 0 ? 'Dr' : 'Cr' }}</td>
<td class="py-0.5 px-1 text-gray-900">
<CheckIcon class="w-4 h-4" v-if="assertion.isValid" />
<XMarkIcon class="w-4 h-4 text-red-500" v-if="!assertion.isValid" />
<CheckIcon class="w-4 h-4" v-if="assertion.is_valid" />
<XMarkIcon class="w-4 h-4 text-red-500" v-if="!assertion.is_valid" />
</td>
<td class="py-0.5 pl-1 text-gray-900 text-end">
<a :href="'/balance-assertions/edit/' + assertion.id" class="text-gray-500 hover:text-gray-700" onclick="return openLinkInNewWindow(this);">
@ -63,14 +63,12 @@
<script setup lang="ts">
import dayjs from 'dayjs';
import { ref } from 'vue';
import { db } from '../db.ts';
import { pp } from '../display.ts';
import { ReportingStage, ReportingWorkflow } from '../reporting.ts';
import { CheckIcon, PencilIcon, XMarkIcon } from '@heroicons/vue/24/outline';
import { PlusIcon } from '@heroicons/vue/16/solid';
import { invoke } from '@tauri-apps/api/core';
import { ref } from 'vue';
import { pp } from '../display.ts';
const balanceAssertions = ref([] as ValidatedBalanceAssertion[]);
@ -81,42 +79,11 @@
account: string,
quantity: number,
commodity: string,
isValid: boolean,
is_valid: boolean,
}
async function load() {
const session = await db.load();
const rawBalanceAssertions: any[] = await session.select(
`SELECT *
FROM balance_assertions
ORDER BY dt DESC, id DESC`
);
// Get transactions
const reportingWorkflow = new ReportingWorkflow();
await reportingWorkflow.generate(session);
const transactions = reportingWorkflow.getTransactionsAtStage(ReportingStage.OrdinaryAPITransactions);
for (const balanceAssertion of rawBalanceAssertions) {
// Check assertion status
const balanceAssertionDt = dayjs(balanceAssertion.dt);
let accountBalance = 0;
for (const transaction of transactions) {
if (dayjs(transaction.dt) <= balanceAssertionDt) {
for (const posting of transaction.postings) {
if (posting.account === balanceAssertion.account) {
accountBalance += posting.quantity_ascost!;
}
}
}
}
balanceAssertion.isValid = balanceAssertion.quantity === accountBalance && balanceAssertion.commodity === db.metadata.reporting_commodity;
}
balanceAssertions.value = rawBalanceAssertions as ValidatedBalanceAssertion[];
balanceAssertions.value = JSON.parse(await invoke('get_validated_balance_assertions'));
}
load();