Balance assertions view using libdrcr
This commit is contained in:
parent
bbcb3cee6f
commit
ffef2d16dc
@ -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,
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<!--
|
||||
DrCr: Web-based 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
|
||||
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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user