Compare commits
7 Commits
4f845eaaea
...
25924d2a0a
Author | SHA1 | Date | |
---|---|---|---|
25924d2a0a | |||
97644042a3 | |||
c422b53f16 | |||
d147f1a569 | |||
5d573ac421 | |||
4e4baf0320 | |||
aefe5a351c |
116
libdrcr/Cargo.lock
generated
116
libdrcr/Cargo.lock
generated
@ -109,6 +109,16 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.17.0"
|
version = "3.17.0"
|
||||||
@ -278,18 +288,6 @@ version = "1.0.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dyn-eq"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dyn-hash"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@ -305,6 +303,16 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "erased-serde"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"typeid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etcetera"
|
name = "etcetera"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -691,15 +699,24 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"dyn-eq",
|
|
||||||
"dyn-hash",
|
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"mlua",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@ -739,6 +756,15 @@ version = "0.4.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "luau0-src"
|
||||||
|
version = "0.12.3+luau663"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ae337c644bbf86a8d8e9ce3ee023311833d41741baf5e51acc31b37843aba1"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -775,6 +801,37 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mlua"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"either",
|
||||||
|
"erased-serde",
|
||||||
|
"libloading",
|
||||||
|
"mlua-sys",
|
||||||
|
"num-traits",
|
||||||
|
"parking_lot",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mlua-sys"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"luau0-src",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@ -837,6 +894,15 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@ -1021,6 +1087,12 @@ version = "0.1.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
@ -1048,6 +1120,16 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-value"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@ -1527,6 +1609,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typeid"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
|
@ -8,9 +8,8 @@ async-trait = "0.1.88"
|
|||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
downcast-rs = "2.0.1"
|
downcast-rs = "2.0.1"
|
||||||
dyn-clone = "1.0.19"
|
dyn-clone = "1.0.19"
|
||||||
dyn-eq = "0.1.3"
|
|
||||||
dyn-hash = "0.2.2"
|
|
||||||
indexmap = "2.9.0"
|
indexmap = "2.9.0"
|
||||||
|
mlua = { version = "0.10", features = ["luau", "serialize"] }
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] }
|
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] }
|
||||||
|
43
libdrcr/plugins/austax/austax.luau
Normal file
43
libdrcr/plugins/austax/austax.luau
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
--!strict
|
||||||
|
-- DrCr: Web-based 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/>.
|
||||||
|
|
||||||
|
local libdrcr = require("../libdrcr")
|
||||||
|
|
||||||
|
function requires(step_name, product_kinds, args)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function execute(step_name, product_kinds, args)
|
||||||
|
print("Stub: Lua plugin execute")
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local plugin: libdrcr.Plugin = {
|
||||||
|
spec = {
|
||||||
|
name = "austax",
|
||||||
|
reporting_steps = {
|
||||||
|
{
|
||||||
|
name = "CalculateIncomeTax",
|
||||||
|
product_kinds = {"DynamicReport", "Transactions"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requires = requires,
|
||||||
|
execute = execute,
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin
|
57
libdrcr/plugins/libdrcr.luau
Normal file
57
libdrcr/plugins/libdrcr.luau
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
--!strict
|
||||||
|
-- DrCr: Web-based 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/>.
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
-- Plugin specific types
|
||||||
|
|
||||||
|
-- Represents a libdrcr plugin specification and implementation
|
||||||
|
export type Plugin = {
|
||||||
|
spec: PluginSpec,
|
||||||
|
requires: (string, {ReportingProductKind}, ReportingStepArgs) -> {ReportingProductId},
|
||||||
|
execute: (string, {ReportingProductKind}, ReportingStepArgs) -> {ReportingProduct},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Represents a libdrcr plugin specification
|
||||||
|
export type PluginSpec = {
|
||||||
|
name: string,
|
||||||
|
reporting_steps: {ReportingStepSpec},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Specifies a ReportingStep provided by the plugin
|
||||||
|
export type ReportingStepSpec = {
|
||||||
|
name: string,
|
||||||
|
product_kinds: {ReportingProductKind}
|
||||||
|
}
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
-- libdrcr internal types
|
||||||
|
|
||||||
|
export type ReportingProduct = any
|
||||||
|
|
||||||
|
export type ReportingProductId = {
|
||||||
|
name: string,
|
||||||
|
kind: ReportingProductKind,
|
||||||
|
args: ReportingStepArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReportingProductKind = string
|
||||||
|
|
||||||
|
-- TODO: Currently only VoidArgs is supported
|
||||||
|
export type ReportingStepArgs = string
|
||||||
|
|
||||||
|
local libdrcr = {}
|
||||||
|
return libdrcr
|
@ -30,8 +30,7 @@ use crate::account_config::kinds_for_account;
|
|||||||
use crate::model::transaction::{Posting, Transaction, TransactionWithPostings};
|
use crate::model::transaction::{Posting, Transaction, TransactionWithPostings};
|
||||||
use crate::reporting::calculator::ReportingGraphDependencies;
|
use crate::reporting::calculator::ReportingGraphDependencies;
|
||||||
use crate::reporting::dynamic_report::{
|
use crate::reporting::dynamic_report::{
|
||||||
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
entries_for_kind, DynamicReport, DynamicReportEntry, Row, Section,
|
||||||
CalculatableSection, CalculatedRow, DynamicReport, LiteralRow,
|
|
||||||
};
|
};
|
||||||
use crate::reporting::executor::ReportingExecutionError;
|
use crate::reporting::executor::ReportingExecutionError;
|
||||||
use crate::reporting::steps::AllTransactionsExceptEarningsToEquityBalances;
|
use crate::reporting::steps::AllTransactionsExceptEarningsToEquityBalances;
|
||||||
@ -44,6 +43,53 @@ use crate::util::sofy_from_eofy;
|
|||||||
use crate::{QuantityInt, INCOME_TAX, INCOME_TAX_CONTROL};
|
use crate::{QuantityInt, INCOME_TAX, INCOME_TAX_CONTROL};
|
||||||
|
|
||||||
// Constants and tax calculations
|
// Constants and tax calculations
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const INCOME_TYPES: &[(&str, &str, &str)] = &[
|
||||||
|
("income1", "Salary or wages", "1"),
|
||||||
|
("income2", "Allowances, earnings, tips, director's fees etc.", "2"),
|
||||||
|
("income3", "Employer lump sum payments", "3"),
|
||||||
|
("income4", "Employment termination payments", "4"),
|
||||||
|
("income5", "Australian Government allowances and payments", "5"),
|
||||||
|
("income6", "Australian Government pensions and allowances", "6"),
|
||||||
|
("income7", "Australian annuities and superannuation income streams", "7"),
|
||||||
|
("income8", "Australian superannuation lump sum payments", "8"),
|
||||||
|
("income9", "Attributed personal services income", "9"),
|
||||||
|
("income10", "Gross interest", "10"),
|
||||||
|
("income11", "Dividends", "11"),
|
||||||
|
("income12", "Employee share schemes", "12"),
|
||||||
|
("income13", "Partnerships and trusts", "13"),
|
||||||
|
("income14", "Personal services income", "14"),
|
||||||
|
("income15", "Net income or loss from business", "15"),
|
||||||
|
("income16", "Deferred non-commercial business losses", "16"),
|
||||||
|
("income17", "Net farm management deposits or repayments", "17"),
|
||||||
|
("income18", "Capital gains", "18"),
|
||||||
|
("income19", "Foreign entities", "19"),
|
||||||
|
("income20", "Foreign source income and foreign assets or property", "20"),
|
||||||
|
("income21", "Rent", "21"),
|
||||||
|
("income22", "Bonuses from life insurance companies and friendly societies", "22"),
|
||||||
|
("income23", "Forestry managed investment scheme income", "23"),
|
||||||
|
("income24", "Other income", "24"),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DEDUCTION_TYPES: &[(&str, &str, &str)] = &[
|
||||||
|
("d1", "Work-related car expenses", "D1"),
|
||||||
|
("d2", "Work-related travel expenses", "D2"),
|
||||||
|
("d3", "Work-related clothing, laundry and dry cleaning expenses", "D3"),
|
||||||
|
("d4", "Work-related self-education expenses", "D4"),
|
||||||
|
("d5", "Other work-related expenses", "D5"),
|
||||||
|
("d6", "Low value pool deduction", "D6"),
|
||||||
|
("d7", "Interest deductions", "D7"),
|
||||||
|
("d8", "Dividend deductions", "D8"),
|
||||||
|
("d9", "Gifts or donations", "D9"),
|
||||||
|
("d10", "Cost of managing tax affairs", "D10"),
|
||||||
|
("d11", "Deductible amount of undeducted purchase price of a foreign pension or annuity", "D11"),
|
||||||
|
("d12", "Personal superannuation contributions", "D12"),
|
||||||
|
("d13", "Deduction for project pool", "D13"),
|
||||||
|
("d14", "Forestry managed investment scheme deduction", "D14"),
|
||||||
|
("d15", "Other deductions", "D15"),
|
||||||
|
];
|
||||||
|
|
||||||
fn get_grossedup_rfb(taxable_value: QuantityInt) -> QuantityInt {
|
fn get_grossedup_rfb(taxable_value: QuantityInt) -> QuantityInt {
|
||||||
// FIXME: May vary from year to year
|
// FIXME: May vary from year to year
|
||||||
((taxable_value as f64) * 2.0802) as QuantityInt
|
((taxable_value as f64) * 2.0802) as QuantityInt
|
||||||
@ -90,8 +136,8 @@ pub struct CalculateIncomeTax {}
|
|||||||
impl CalculateIncomeTax {
|
impl CalculateIncomeTax {
|
||||||
fn register_lookup_fn(context: &mut ReportingContext) {
|
fn register_lookup_fn(context: &mut ReportingContext) {
|
||||||
context.register_lookup_fn(
|
context.register_lookup_fn(
|
||||||
"CalculateIncomeTax",
|
"CalculateIncomeTax".to_string(),
|
||||||
&[ReportingProductKind::Transactions],
|
vec![ReportingProductKind::Transactions],
|
||||||
Self::takes_args,
|
Self::takes_args,
|
||||||
Self::from_args,
|
Self::from_args,
|
||||||
);
|
);
|
||||||
@ -116,8 +162,8 @@ impl Display for CalculateIncomeTax {
|
|||||||
impl ReportingStep for CalculateIncomeTax {
|
impl ReportingStep for CalculateIncomeTax {
|
||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
product_kinds: &[
|
product_kinds: vec![
|
||||||
ReportingProductKind::DynamicReport,
|
ReportingProductKind::DynamicReport,
|
||||||
ReportingProductKind::Transactions,
|
ReportingProductKind::Transactions,
|
||||||
],
|
],
|
||||||
@ -128,7 +174,7 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
||||||
// CalculateIncomeTax depends on CombineOrdinaryTransactions
|
// CalculateIncomeTax depends on CombineOrdinaryTransactions
|
||||||
vec![ReportingProductId {
|
vec![ReportingProductId {
|
||||||
name: "CombineOrdinaryTransactions",
|
name: "CombineOrdinaryTransactions".to_string(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(DateStartDateEndArgs {
|
args: Box::new(DateStartDateEndArgs {
|
||||||
date_start: sofy_from_eofy(context.eofy_date),
|
date_start: sofy_from_eofy(context.eofy_date),
|
||||||
@ -152,8 +198,8 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
other.id(),
|
other.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.id().name,
|
name: self.id().name,
|
||||||
kind: other.product_kinds[0],
|
kind: other.product_kind,
|
||||||
args: if other.product_kinds[0] == ReportingProductKind::Transactions {
|
args: if other.product_kind == ReportingProductKind::Transactions {
|
||||||
Box::new(VoidArgs {})
|
Box::new(VoidArgs {})
|
||||||
} else {
|
} else {
|
||||||
other.id().args
|
other.id().args
|
||||||
@ -176,7 +222,7 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
// Get balances for current year
|
// Get balances for current year
|
||||||
let balances = &products
|
let balances = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "CombineOrdinaryTransactions",
|
name: "CombineOrdinaryTransactions".to_string(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(DateStartDateEndArgs {
|
args: Box::new(DateStartDateEndArgs {
|
||||||
date_start: sofy_from_eofy(context.eofy_date),
|
date_start: sofy_from_eofy(context.eofy_date),
|
||||||
@ -191,7 +237,156 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
let kinds_for_account =
|
let kinds_for_account =
|
||||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||||
|
|
||||||
// Pre-compute taxable value of reportable fringe benefits (required for MLS)
|
// Generate tax summary report
|
||||||
|
let mut report = DynamicReport {
|
||||||
|
title: "Tax summary".to_string(),
|
||||||
|
columns: vec!["$".to_string()],
|
||||||
|
entries: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add income entries
|
||||||
|
let mut total_income: QuantityInt = 0;
|
||||||
|
|
||||||
|
for (code, label, number) in INCOME_TYPES {
|
||||||
|
let entries;
|
||||||
|
if *code == "income1" {
|
||||||
|
// Special case for salary or wages - round each separately
|
||||||
|
entries = entries_for_kind_floor(
|
||||||
|
&format!("austax.{}", code),
|
||||||
|
true,
|
||||||
|
&vec![balances],
|
||||||
|
&kinds_for_account,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
entries = entries_for_kind(
|
||||||
|
&format!("austax.{}", code),
|
||||||
|
true,
|
||||||
|
&vec![balances],
|
||||||
|
&kinds_for_account,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if entries.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut section = Section {
|
||||||
|
text: Some(format!("{} ({})", label, number)),
|
||||||
|
id: None,
|
||||||
|
visible: true,
|
||||||
|
entries,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add subtotal row
|
||||||
|
let subtotal = floor_quantity(section.subtotal(&report), 100);
|
||||||
|
total_income += subtotal[0];
|
||||||
|
|
||||||
|
section.entries.push(
|
||||||
|
Row {
|
||||||
|
text: format!("Total item {}", number),
|
||||||
|
quantity: subtotal,
|
||||||
|
id: Some(format!("total_{}", code)),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
report.entries.push(section.into());
|
||||||
|
report.entries.push(DynamicReportEntry::Spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total assessable income
|
||||||
|
report.entries.push(
|
||||||
|
Row {
|
||||||
|
text: "Total assessable income".to_string(),
|
||||||
|
quantity: vec![total_income],
|
||||||
|
id: Some("total_income".to_string()),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
report.entries.push(DynamicReportEntry::Spacer);
|
||||||
|
|
||||||
|
// Add deduction entries
|
||||||
|
let mut total_deductions: QuantityInt = 0;
|
||||||
|
|
||||||
|
for (code, label, number) in DEDUCTION_TYPES {
|
||||||
|
let entries = entries_for_kind(
|
||||||
|
&format!("austax.{}", code),
|
||||||
|
false,
|
||||||
|
&vec![balances],
|
||||||
|
&kinds_for_account,
|
||||||
|
);
|
||||||
|
|
||||||
|
if entries.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut section = Section {
|
||||||
|
text: Some(format!("{} ({})", label, number)),
|
||||||
|
id: None,
|
||||||
|
visible: true,
|
||||||
|
entries,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add subtotal row
|
||||||
|
let subtotal = floor_quantity(section.subtotal(&report), 100);
|
||||||
|
total_deductions += subtotal[0];
|
||||||
|
|
||||||
|
section.entries.push(
|
||||||
|
Row {
|
||||||
|
text: format!("Total item {}", number),
|
||||||
|
quantity: subtotal,
|
||||||
|
id: Some(format!("total_{}", code)),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
report.entries.push(section.into());
|
||||||
|
report.entries.push(DynamicReportEntry::Spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total deductions
|
||||||
|
report.entries.push(
|
||||||
|
Row {
|
||||||
|
text: "Total deductions".to_string(),
|
||||||
|
quantity: vec![total_deductions],
|
||||||
|
id: Some("total_deductions".to_string()),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
report.entries.push(DynamicReportEntry::Spacer);
|
||||||
|
|
||||||
|
// Net taxable income
|
||||||
|
let net_taxable = total_income - total_deductions;
|
||||||
|
report.entries.push(
|
||||||
|
Row {
|
||||||
|
text: "Net taxable income".to_string(),
|
||||||
|
quantity: vec![net_taxable],
|
||||||
|
id: Some("net_taxable".to_string()),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
report.entries.push(DynamicReportEntry::Spacer);
|
||||||
|
|
||||||
|
// Precompute RFB amount as this is required for MLS
|
||||||
let rfb_taxable = balances
|
let rfb_taxable = balances
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(acc, _)| {
|
.filter(|(acc, _)| {
|
||||||
@ -202,543 +397,37 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
})
|
})
|
||||||
.map(|(_, bal)| *bal)
|
.map(|(_, bal)| *bal)
|
||||||
.sum();
|
.sum();
|
||||||
|
let _rfb_grossedup = get_grossedup_rfb(rfb_taxable);
|
||||||
|
|
||||||
// Generate tax summary report
|
// Base income tax row
|
||||||
let report = CalculatableDynamicReport::new(
|
let tax_base = get_base_income_tax(net_taxable);
|
||||||
"Tax summary".to_string(),
|
report.entries.push(
|
||||||
vec!["$".to_string()],
|
Row {
|
||||||
vec![
|
text: "Base income tax".to_string(),
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
quantity: vec![tax_base],
|
||||||
"Salary or wages (1)".to_string(),
|
id: Some("tax_base".to_string()),
|
||||||
Some("income1".to_string()),
|
visible: true,
|
||||||
true,
|
link: None,
|
||||||
true,
|
heading: false,
|
||||||
{
|
bordered: false,
|
||||||
let mut entries = entries_for_kind_floor(
|
}
|
||||||
"austax.income1",
|
.into(),
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
100,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 1".to_string(),
|
|
||||||
quantity: report.subtotal_for_id("income1").unwrap(),
|
|
||||||
id: Some("total_income1".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
// Add spacer as child of the Section so it is hidden if the Section is hidden
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Australian Government allowances and payments (5)".to_string(),
|
|
||||||
Some("income5".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.income5",
|
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 5".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("income5").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_income5".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Gross interest (10)".to_string(),
|
|
||||||
Some("income10".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.income10",
|
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 10".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("income10").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_income10".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Partnerships and trusts (13)".to_string(),
|
|
||||||
Some("income13".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.income13",
|
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 13".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("income13").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_income13".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Foreign source income and foreign assets or property (20)".to_string(),
|
|
||||||
Some("income20".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.income20",
|
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 20".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("income20").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_income20".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Other income (24)".to_string(),
|
|
||||||
Some("income24".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.income24",
|
|
||||||
true,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item 24".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("income24").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_income24".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total assessable income".to_string(),
|
|
||||||
quantity: vec![
|
|
||||||
report
|
|
||||||
.quantity_for_id("total_income1")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_income5")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_income10")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_income13")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_income20")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_income24")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0),
|
|
||||||
],
|
|
||||||
id: Some("total_income".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CalculatableDynamicReportEntry::Spacer,
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Work-related travel expenses (D2)".to_string(),
|
|
||||||
Some("d2".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.d2",
|
|
||||||
false,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item D2".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("d2").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_d2".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Work-related self-education expenses (D4)".to_string(),
|
|
||||||
Some("d4".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.d4",
|
|
||||||
false,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item D4".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("d4").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_d4".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Other work-related expenses (D5)".to_string(),
|
|
||||||
Some("d5".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.d5",
|
|
||||||
false,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item D5".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("d5").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_d5".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Gifts or donations (D9)".to_string(),
|
|
||||||
Some("d9".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.d9",
|
|
||||||
false,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item D9".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("d9").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_d9".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
|
||||||
"Other deductions (D15)".to_string(),
|
|
||||||
Some("d15".to_string()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
let mut entries = entries_for_kind(
|
|
||||||
"austax.d15",
|
|
||||||
false,
|
|
||||||
&vec![balances],
|
|
||||||
&kinds_for_account,
|
|
||||||
);
|
|
||||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
|
||||||
CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total item D15".to_string(),
|
|
||||||
quantity: floor_quantity(
|
|
||||||
report.subtotal_for_id("d15").unwrap(),
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
id: Some("total_d15".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: true,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
entries.push(CalculatableDynamicReportEntry::Spacer);
|
|
||||||
entries
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total deductions".to_string(),
|
|
||||||
quantity: vec![
|
|
||||||
report
|
|
||||||
.quantity_for_id("total_d2")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_d4")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_d5")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_d9")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0) + report
|
|
||||||
.quantity_for_id("total_d15")
|
|
||||||
.map(|v| v[0])
|
|
||||||
.unwrap_or(0),
|
|
||||||
],
|
|
||||||
id: Some("total_deductions".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CalculatableDynamicReportEntry::Spacer,
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Net taxable income".to_string(),
|
|
||||||
quantity: vec![
|
|
||||||
report.quantity_for_id("total_income").unwrap()[0]
|
|
||||||
- report.quantity_for_id("total_deductions").unwrap()[0],
|
|
||||||
],
|
|
||||||
id: Some("net_taxable".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Precompute RFB amount as this is required for MLS
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(LiteralRow {
|
|
||||||
text: "Taxable value of reportable fringe benefits".to_string(),
|
|
||||||
quantity: vec![rfb_taxable],
|
|
||||||
id: Some("rfb_taxable".to_string()),
|
|
||||||
visible: false,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: false,
|
|
||||||
bordered: false,
|
|
||||||
}),
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Grossed-up value".to_string(),
|
|
||||||
quantity: vec![get_grossedup_rfb(
|
|
||||||
report.quantity_for_id("rfb_taxable").unwrap()[0],
|
|
||||||
)],
|
|
||||||
id: Some("rfb_grossedup".to_string()),
|
|
||||||
visible: false,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: false,
|
|
||||||
bordered: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CalculatableDynamicReportEntry::Spacer,
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Base income tax".to_string(),
|
|
||||||
quantity: vec![get_base_income_tax(
|
|
||||||
report.quantity_for_id("net_taxable").unwrap()[0],
|
|
||||||
)],
|
|
||||||
id: Some("tax_base".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: false,
|
|
||||||
bordered: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
// calculate_fn: |report| LiteralRow {
|
|
||||||
// text: "Medicare levy".to_string(),
|
|
||||||
// quantity: vec![get_medicare_levy(
|
|
||||||
// report.quantity_for_id("net_taxable").unwrap()[0],
|
|
||||||
// )],
|
|
||||||
// id: Some("tax_ml".to_string()),
|
|
||||||
// visible: true,
|
|
||||||
// auto_hide: true,
|
|
||||||
// link: None,
|
|
||||||
// heading: false,
|
|
||||||
// bordered: false,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
// calculate_fn: |report| LiteralRow {
|
|
||||||
// text: "Medicare levy".to_string(),
|
|
||||||
// quantity: vec![get_medicare_levy_surcharge(
|
|
||||||
// report.quantity_for_id("net_taxable").unwrap()[0],
|
|
||||||
// report.quantity_for_id("rfb_grossedup").unwrap()[0],
|
|
||||||
// )],
|
|
||||||
// id: Some("tax_mls".to_string()),
|
|
||||||
// visible: true,
|
|
||||||
// auto_hide: true,
|
|
||||||
// link: None,
|
|
||||||
// heading: false,
|
|
||||||
// bordered: false,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
|
||||||
calculate_fn: |report| LiteralRow {
|
|
||||||
text: "Total income tax".to_string(),
|
|
||||||
quantity: vec![
|
|
||||||
report.quantity_for_id("tax_base").unwrap()[0], // + report.quantity_for_id("tax_ml").map(|v| v[0]).unwrap_or(0)
|
|
||||||
// + report.quantity_for_id("tax_mls").map(|v| v[0]).unwrap_or(0),
|
|
||||||
],
|
|
||||||
id: Some("total_tax".to_string()),
|
|
||||||
visible: true,
|
|
||||||
auto_hide: false,
|
|
||||||
link: None,
|
|
||||||
heading: true,
|
|
||||||
bordered: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut report: DynamicReport = report.calculate();
|
// Total income tax row
|
||||||
report.auto_hide();
|
let tax_total = tax_base;
|
||||||
|
report.entries.push(
|
||||||
let total_tax = report.quantity_for_id("total_tax").unwrap()[0];
|
Row {
|
||||||
|
text: "Total income tax".to_string(),
|
||||||
|
quantity: vec![tax_total],
|
||||||
|
id: Some("tax_total".to_string()),
|
||||||
|
visible: true,
|
||||||
|
link: None,
|
||||||
|
heading: true,
|
||||||
|
bordered: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
// Generate income tax transaction
|
// Generate income tax transaction
|
||||||
let transactions = Transactions {
|
let transactions = Transactions {
|
||||||
@ -759,18 +448,18 @@ impl ReportingStep for CalculateIncomeTax {
|
|||||||
transaction_id: None,
|
transaction_id: None,
|
||||||
description: None,
|
description: None,
|
||||||
account: INCOME_TAX.to_string(),
|
account: INCOME_TAX.to_string(),
|
||||||
quantity: total_tax,
|
quantity: tax_total,
|
||||||
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||||
quantity_ascost: Some(total_tax),
|
quantity_ascost: Some(tax_total),
|
||||||
},
|
},
|
||||||
Posting {
|
Posting {
|
||||||
id: None,
|
id: None,
|
||||||
transaction_id: None,
|
transaction_id: None,
|
||||||
description: None,
|
description: None,
|
||||||
account: INCOME_TAX_CONTROL.to_string(),
|
account: INCOME_TAX_CONTROL.to_string(),
|
||||||
quantity: -total_tax,
|
quantity: -tax_total,
|
||||||
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||||
quantity_ascost: Some(total_tax),
|
quantity_ascost: Some(tax_total),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
@ -805,10 +494,10 @@ fn entries_for_kind_floor(
|
|||||||
balances: &Vec<&HashMap<String, QuantityInt>>,
|
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||||
kinds_for_account: &HashMap<String, Vec<String>>,
|
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||||
floor: QuantityInt,
|
floor: QuantityInt,
|
||||||
) -> Vec<CalculatableDynamicReportEntry> {
|
) -> Vec<DynamicReportEntry> {
|
||||||
let mut entries_for_kind = entries_for_kind(kind, invert, balances, kinds_for_account);
|
let mut entries_for_kind = entries_for_kind(kind, invert, balances, kinds_for_account);
|
||||||
entries_for_kind.iter_mut().for_each(|e| match e {
|
entries_for_kind.iter_mut().for_each(|e| match e {
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => row
|
DynamicReportEntry::Row(row) => row
|
||||||
.quantity
|
.quantity
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|v| *v = (*v / floor) * floor),
|
.for_each(|v| *v = (*v / floor) * floor),
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
pub mod account_config;
|
pub mod account_config;
|
||||||
pub mod austax;
|
//pub mod austax;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
pub mod plugin;
|
||||||
pub mod reporting;
|
pub mod reporting;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -25,7 +25,7 @@ use libdrcr::reporting::dynamic_report::DynamicReport;
|
|||||||
use libdrcr::reporting::generate_report;
|
use libdrcr::reporting::generate_report;
|
||||||
use libdrcr::reporting::types::{
|
use libdrcr::reporting::types::{
|
||||||
DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
||||||
ReportingContext, ReportingProductId, ReportingProductKind, VoidArgs,
|
ReportingContext, ReportingProductId, ReportingProductKind, ReportingStepArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -38,12 +38,14 @@ async fn main() {
|
|||||||
// Initialise ReportingContext
|
// Initialise ReportingContext
|
||||||
let mut context = ReportingContext::new(
|
let mut context = ReportingContext::new(
|
||||||
db_connection,
|
db_connection,
|
||||||
|
"plugins".to_string(),
|
||||||
|
vec!["austax.austax".to_string()],
|
||||||
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||||
"$".to_string(),
|
"$".to_string(),
|
||||||
);
|
);
|
||||||
|
libdrcr::plugin::register_lookup_fns(&mut context);
|
||||||
libdrcr::reporting::steps::register_lookup_fns(&mut context);
|
libdrcr::reporting::steps::register_lookup_fns(&mut context);
|
||||||
libdrcr::reporting::builders::register_dynamic_builders(&mut context);
|
libdrcr::reporting::builders::register_dynamic_builders(&mut context);
|
||||||
libdrcr::austax::register_lookup_fns(&mut context);
|
|
||||||
|
|
||||||
let context = Arc::new(context);
|
let context = Arc::new(context);
|
||||||
|
|
||||||
@ -51,9 +53,9 @@ async fn main() {
|
|||||||
|
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
// ReportingProductId {
|
// ReportingProductId {
|
||||||
// name: "AllTransactionsExceptEarningsToEquity",
|
// name: "AllTransactionsExceptEarningsToEquity",
|
||||||
@ -63,18 +65,18 @@ async fn main() {
|
|||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: ReportingStepArgs::MultipleDateArgs(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateStartDateEndArgs {
|
args: ReportingStepArgs::MultipleDateStartDateEndArgs(MultipleDateStartDateEndArgs {
|
||||||
dates: vec![DateStartDateEndArgs {
|
dates: vec![DateStartDateEndArgs {
|
||||||
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
||||||
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
@ -91,14 +93,14 @@ async fn main() {
|
|||||||
|
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "AllTransactionsExceptEarningsToEquity",
|
name: "AllTransactionsExceptEarningsToEquity".to_string(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(DateStartDateEndArgs {
|
args: ReportingStepArgs::DateStartDateEndArgs(DateStartDateEndArgs {
|
||||||
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
||||||
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
@ -109,22 +111,22 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let result = products
|
// let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
// .get_or_err(&ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
// name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
// kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(VoidArgs {}),
|
// args: ReportingStepArgs::VoidArgs,
|
||||||
})
|
// })
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
println!("Tax summary:");
|
// println!("Tax summary:");
|
||||||
println!("{:?}", result);
|
// println!("{:?}", result);
|
||||||
|
|
||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "AllTransactionsExceptEarningsToEquity",
|
name: "AllTransactionsExceptEarningsToEquity".to_string(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(DateStartDateEndArgs {
|
args: ReportingStepArgs::DateStartDateEndArgs(DateStartDateEndArgs {
|
||||||
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
||||||
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
@ -138,14 +140,14 @@ async fn main() {
|
|||||||
|
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: ReportingStepArgs::MultipleDateArgs(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}],
|
}],
|
||||||
@ -158,9 +160,9 @@ async fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: ReportingStepArgs::MultipleDateArgs(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}],
|
}],
|
||||||
@ -178,14 +180,14 @@ async fn main() {
|
|||||||
|
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -196,9 +198,9 @@ async fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
216
libdrcr/src/plugin.rs
Normal file
216
libdrcr/src/plugin.rs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
DrCr: Web-based 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use mlua::{Function, Lua, LuaSerdeExt, Table, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::reporting::calculator::ReportingGraphDependencies;
|
||||||
|
use crate::reporting::executor::ReportingExecutionError;
|
||||||
|
use crate::reporting::types::{
|
||||||
|
ReportingContext, ReportingProductId, ReportingProductKind, ReportingProducts, ReportingStep,
|
||||||
|
ReportingStepArgs, ReportingStepId,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn load_plugin(plugin_dir: &str, plugin_name: &str) -> (Lua, Plugin) {
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
// Init Lua environment
|
||||||
|
let package = lua.globals().get::<Table>("package").unwrap();
|
||||||
|
package
|
||||||
|
.set("path", format!("{}/?.luau", plugin_dir))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Require and call the plugin
|
||||||
|
let require = lua.load("require").eval::<Function>().unwrap();
|
||||||
|
let plugin_table = require.call::<Table>(plugin_name).expect("Lua error");
|
||||||
|
|
||||||
|
// Convert plugin to Rust struct
|
||||||
|
let plugin = Plugin {
|
||||||
|
spec: lua
|
||||||
|
.from_value(
|
||||||
|
plugin_table
|
||||||
|
.get("spec")
|
||||||
|
.expect("Error parsing Plugin definition"),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
requires: plugin_table
|
||||||
|
.get("requires")
|
||||||
|
.expect("Error parsing Plugin definition"),
|
||||||
|
execute: plugin_table
|
||||||
|
.get("execute")
|
||||||
|
.expect("Error parsing Plugin definition"),
|
||||||
|
};
|
||||||
|
|
||||||
|
(lua, plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call [ReportingContext::register_lookup_fn] for all steps provided by this module
|
||||||
|
pub fn register_lookup_fns(context: &mut ReportingContext) {
|
||||||
|
for plugin_path in context.plugin_names.clone().iter() {
|
||||||
|
let (_, plugin) = load_plugin(&context.plugin_dir, plugin_path);
|
||||||
|
|
||||||
|
for reporting_step in plugin.spec.reporting_steps.iter() {
|
||||||
|
context.register_lookup_fn(
|
||||||
|
reporting_step.name.clone(),
|
||||||
|
reporting_step.product_kinds.clone(),
|
||||||
|
PluginReportingStep::takes_args,
|
||||||
|
PluginReportingStep::from_args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
context
|
||||||
|
.plugin_specs
|
||||||
|
.insert(plugin_path.clone(), plugin.spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Plugin {
|
||||||
|
spec: PluginSpec,
|
||||||
|
requires: Function,
|
||||||
|
execute: Function,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a libdrcr plugin
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PluginSpec {
|
||||||
|
name: String,
|
||||||
|
reporting_steps: Vec<ReportingStepSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ReportingStepSpec {
|
||||||
|
name: String,
|
||||||
|
product_kinds: Vec<ReportingProductKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic reporting step which is implemented by a plugin
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PluginReportingStep {
|
||||||
|
pub plugin_path: String,
|
||||||
|
pub step_name: String,
|
||||||
|
pub product_kinds: Vec<ReportingProductKind>,
|
||||||
|
pub args: ReportingStepArgs, // Currently only VoidArgs is supported
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginReportingStep {
|
||||||
|
fn takes_args(_name: &str, args: &ReportingStepArgs, _context: &ReportingContext) -> bool {
|
||||||
|
*args == ReportingStepArgs::VoidArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_args(
|
||||||
|
name: &str,
|
||||||
|
args: ReportingStepArgs,
|
||||||
|
context: &ReportingContext,
|
||||||
|
) -> Box<dyn ReportingStep> {
|
||||||
|
// Look up plugin
|
||||||
|
for (plugin_path, plugin_spec) in context.plugin_specs.iter() {
|
||||||
|
if let Some(reporting_step_spec) =
|
||||||
|
plugin_spec.reporting_steps.iter().find(|s| s.name == name)
|
||||||
|
{
|
||||||
|
return Box::new(Self {
|
||||||
|
plugin_path: plugin_path.to_string(),
|
||||||
|
step_name: name.to_string(),
|
||||||
|
product_kinds: reporting_step_spec.product_kinds.clone(),
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("No plugin provides step {}", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PluginReportingStep {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{} {{PluginReportingStep}}", self.id()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ReportingStep for PluginReportingStep {
|
||||||
|
fn id(&self) -> ReportingStepId {
|
||||||
|
ReportingStepId {
|
||||||
|
name: self.step_name.clone(),
|
||||||
|
product_kinds: self.product_kinds.clone(),
|
||||||
|
args: self.args.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
||||||
|
// Call to plugin
|
||||||
|
let (lua, plugin) = load_plugin(&context.plugin_dir, &self.plugin_path);
|
||||||
|
let result_table = plugin
|
||||||
|
.requires
|
||||||
|
.call::<Table>((
|
||||||
|
lua.to_value(&self.step_name).unwrap(),
|
||||||
|
lua.to_value(&self.product_kinds).unwrap(),
|
||||||
|
lua.to_value(&self.args).unwrap(),
|
||||||
|
))
|
||||||
|
.expect("Lua error");
|
||||||
|
|
||||||
|
// Convert result to Rust
|
||||||
|
let result = result_table
|
||||||
|
.sequence_values()
|
||||||
|
.map(|s| s.expect("Lua error"))
|
||||||
|
.map(|v| lua.from_value(v).expect("Deserialise error"))
|
||||||
|
.collect::<Vec<ReportingProductId>>();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
context: &ReportingContext,
|
||||||
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
_dependencies: &ReportingGraphDependencies,
|
||||||
|
_products: &RwLock<ReportingProducts>,
|
||||||
|
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||||
|
// Call to plugin
|
||||||
|
let (lua, plugin) = load_plugin(&context.plugin_dir, &self.plugin_path);
|
||||||
|
let _result_table = plugin
|
||||||
|
.execute
|
||||||
|
.call::<Table>((
|
||||||
|
lua.to_value(&self.step_name).unwrap(),
|
||||||
|
lua.to_value(&self.product_kinds).unwrap(),
|
||||||
|
lua.to_value(&self.args).unwrap(),
|
||||||
|
))
|
||||||
|
.expect("Lua error");
|
||||||
|
|
||||||
|
eprintln!("Stub: Lua plugin execute");
|
||||||
|
|
||||||
|
Ok(ReportingProducts::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format the [Table] as a string
|
||||||
|
fn _dbg_table(table: Table) -> String {
|
||||||
|
format!(
|
||||||
|
"{{{}}}",
|
||||||
|
table
|
||||||
|
.pairs::<Value, Value>()
|
||||||
|
.map(|p| p.expect("Lua error"))
|
||||||
|
.map(|(k, v)| format!("{:?}: {:?}", k, v))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
@ -33,7 +33,7 @@ use super::executor::ReportingExecutionError;
|
|||||||
use super::types::{
|
use super::types::{
|
||||||
BalancesAt, BalancesBetween, DateArgs, DateStartDateEndArgs, ReportingContext,
|
BalancesAt, BalancesBetween, DateArgs, DateStartDateEndArgs, ReportingContext,
|
||||||
ReportingProductId, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs,
|
ReportingProductId, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs,
|
||||||
ReportingStepDynamicBuilder, ReportingStepId, Transactions, VoidArgs,
|
ReportingStepDynamicBuilder, ReportingStepId, Transactions,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Call [ReportingContext::register_dynamic_builder] for all dynamic builders provided by this module
|
/// Call [ReportingContext::register_dynamic_builder] for all dynamic builders provided by this module
|
||||||
@ -49,7 +49,7 @@ pub fn register_dynamic_builders(context: &mut ReportingContext) {
|
|||||||
/// This dynamic builder automatically generates a [BalancesBetween] by subtracting [BalancesAt] between two dates
|
/// This dynamic builder automatically generates a [BalancesBetween] by subtracting [BalancesAt] between two dates
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BalancesAtToBalancesBetween {
|
pub struct BalancesAtToBalancesBetween {
|
||||||
step_name: &'static str,
|
step_name: String,
|
||||||
args: DateStartDateEndArgs,
|
args: DateStartDateEndArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,55 +65,53 @@ impl BalancesAtToBalancesBetween {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_build(
|
fn can_build(
|
||||||
name: &'static str,
|
name: &str,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
args: &Box<dyn ReportingStepArgs>,
|
args: &ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Check for BalancesAt, BalancesAt -> BalancesBetween
|
// Check for BalancesAt, BalancesAt -> BalancesBetween
|
||||||
if kind == ReportingProductKind::BalancesBetween {
|
if kind == ReportingProductKind::BalancesBetween {
|
||||||
if !args.is::<DateStartDateEndArgs>() {
|
if let ReportingStepArgs::DateStartDateEndArgs(args) = args {
|
||||||
return false;
|
match has_step_or_can_build(
|
||||||
}
|
&ReportingProductId {
|
||||||
|
name: name.to_string(),
|
||||||
let args = args.downcast_ref::<DateStartDateEndArgs>().unwrap();
|
kind: ReportingProductKind::BalancesAt,
|
||||||
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
match has_step_or_can_build(
|
date: args.date_start.clone(),
|
||||||
&ReportingProductId {
|
}),
|
||||||
name,
|
},
|
||||||
kind: ReportingProductKind::BalancesAt,
|
steps,
|
||||||
args: Box::new(DateArgs {
|
dependencies,
|
||||||
date: args.date_start.clone(),
|
context,
|
||||||
}),
|
) {
|
||||||
},
|
HasStepOrCanBuild::HasStep(_)
|
||||||
steps,
|
| HasStepOrCanBuild::CanLookup(_)
|
||||||
dependencies,
|
| HasStepOrCanBuild::CanBuild(_) => {
|
||||||
context,
|
return true;
|
||||||
) {
|
}
|
||||||
HasStepOrCanBuild::HasStep(_)
|
HasStepOrCanBuild::None => {}
|
||||||
| HasStepOrCanBuild::CanLookup(_)
|
|
||||||
| HasStepOrCanBuild::CanBuild(_) => {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
HasStepOrCanBuild::None => {}
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
name: &'static str,
|
name: String,
|
||||||
_kind: ReportingProductKind,
|
_kind: ReportingProductKind,
|
||||||
args: Box<dyn ReportingStepArgs>,
|
args: ReportingStepArgs,
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
_dependencies: &ReportingGraphDependencies,
|
_dependencies: &ReportingGraphDependencies,
|
||||||
_context: &ReportingContext,
|
_context: &ReportingContext,
|
||||||
) -> Box<dyn ReportingStep> {
|
) -> Box<dyn ReportingStep> {
|
||||||
Box::new(BalancesAtToBalancesBetween {
|
Box::new(BalancesAtToBalancesBetween {
|
||||||
step_name: name,
|
step_name: name,
|
||||||
args: *args.downcast().unwrap(),
|
args: args.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,9 +129,9 @@ impl Display for BalancesAtToBalancesBetween {
|
|||||||
impl ReportingStep for BalancesAtToBalancesBetween {
|
impl ReportingStep for BalancesAtToBalancesBetween {
|
||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
product_kinds: &[ReportingProductKind::BalancesBetween],
|
product_kinds: vec![ReportingProductKind::BalancesBetween],
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,16 +139,16 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
|||||||
// BalancesAtToBalancesBetween depends on BalancesAt at both time points
|
// BalancesAtToBalancesBetween depends on BalancesAt at both time points
|
||||||
vec![
|
vec![
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date_start.pred_opt().unwrap(), // Opening balance is the closing balance of the preceding day
|
date: self.args.date_start.pred_opt().unwrap(), // Opening balance is the closing balance of the preceding day
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date_end,
|
date: self.args.date_end,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -169,9 +167,9 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
|||||||
// Get balances at dates
|
// Get balances at dates
|
||||||
let balances_start = &products
|
let balances_start = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date_start.pred_opt().unwrap(), // Opening balance is the closing balance of the preceding day
|
date: self.args.date_start.pred_opt().unwrap(), // Opening balance is the closing balance of the preceding day
|
||||||
}),
|
}),
|
||||||
})?
|
})?
|
||||||
@ -181,9 +179,9 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
|||||||
|
|
||||||
let balances_end = &products
|
let balances_end = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date_end,
|
date: self.args.date_end,
|
||||||
}),
|
}),
|
||||||
})?
|
})?
|
||||||
@ -207,7 +205,7 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
|||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.id().name,
|
name: self.id().name,
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(balances),
|
Box::new(balances),
|
||||||
);
|
);
|
||||||
@ -218,7 +216,7 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
|||||||
/// This dynamic builder automatically generates a [BalancesAt] from a step which has no dependencies and generates [Transactions] (e.g. [PostUnreconciledStatementLines][super::steps::PostUnreconciledStatementLines])
|
/// This dynamic builder automatically generates a [BalancesAt] from a step which has no dependencies and generates [Transactions] (e.g. [PostUnreconciledStatementLines][super::steps::PostUnreconciledStatementLines])
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GenerateBalances {
|
pub struct GenerateBalances {
|
||||||
step_name: &'static str,
|
step_name: String,
|
||||||
args: DateArgs,
|
args: DateArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,9 +230,9 @@ impl GenerateBalances {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_build(
|
fn can_build(
|
||||||
name: &'static str,
|
name: &str,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
args: &Box<dyn ReportingStepArgs>,
|
args: &ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
@ -244,7 +242,7 @@ impl GenerateBalances {
|
|||||||
// Try DateArgs
|
// Try DateArgs
|
||||||
match has_step_or_can_build(
|
match has_step_or_can_build(
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name,
|
name: name.to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: args.clone(),
|
args: args.clone(),
|
||||||
},
|
},
|
||||||
@ -260,7 +258,7 @@ impl GenerateBalances {
|
|||||||
}
|
}
|
||||||
HasStepOrCanBuild::CanLookup(lookup_fn) => {
|
HasStepOrCanBuild::CanLookup(lookup_fn) => {
|
||||||
// Check for () -> Transactions
|
// Check for () -> Transactions
|
||||||
let step = lookup_fn(args.clone());
|
let step = lookup_fn(name, args.clone(), context);
|
||||||
if step.requires(context).len() == 0 {
|
if step.requires(context).len() == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -271,9 +269,9 @@ impl GenerateBalances {
|
|||||||
// Try VoidArgs
|
// Try VoidArgs
|
||||||
match has_step_or_can_build(
|
match has_step_or_can_build(
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name,
|
name: name.to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
steps,
|
steps,
|
||||||
dependencies,
|
dependencies,
|
||||||
@ -287,7 +285,7 @@ impl GenerateBalances {
|
|||||||
}
|
}
|
||||||
HasStepOrCanBuild::CanLookup(lookup_fn) => {
|
HasStepOrCanBuild::CanLookup(lookup_fn) => {
|
||||||
// Check for () -> Transactions
|
// Check for () -> Transactions
|
||||||
let step = lookup_fn(args.clone());
|
let step = lookup_fn(name, args.clone(), context);
|
||||||
if step.requires(context).len() == 0 {
|
if step.requires(context).len() == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -299,16 +297,16 @@ impl GenerateBalances {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
name: &'static str,
|
name: String,
|
||||||
_kind: ReportingProductKind,
|
_kind: ReportingProductKind,
|
||||||
args: Box<dyn ReportingStepArgs>,
|
args: ReportingStepArgs,
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
_dependencies: &ReportingGraphDependencies,
|
_dependencies: &ReportingGraphDependencies,
|
||||||
_context: &ReportingContext,
|
_context: &ReportingContext,
|
||||||
) -> Box<dyn ReportingStep> {
|
) -> Box<dyn ReportingStep> {
|
||||||
Box::new(GenerateBalances {
|
Box::new(GenerateBalances {
|
||||||
step_name: name,
|
step_name: name,
|
||||||
args: *args.downcast().unwrap(),
|
args: args.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,9 +321,9 @@ impl Display for GenerateBalances {
|
|||||||
impl ReportingStep for GenerateBalances {
|
impl ReportingStep for GenerateBalances {
|
||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
product_kinds: &[ReportingProductKind::BalancesAt],
|
product_kinds: vec![ReportingProductKind::BalancesAt],
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,9 +339,9 @@ impl ReportingStep for GenerateBalances {
|
|||||||
// Try DateArgs
|
// Try DateArgs
|
||||||
match has_step_or_can_build(
|
match has_step_or_can_build(
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
steps,
|
steps,
|
||||||
dependencies,
|
dependencies,
|
||||||
@ -355,9 +353,9 @@ impl ReportingStep for GenerateBalances {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -369,9 +367,9 @@ impl ReportingStep for GenerateBalances {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -408,9 +406,9 @@ impl ReportingStep for GenerateBalances {
|
|||||||
let mut result = ReportingProducts::new();
|
let mut result = ReportingProducts::new();
|
||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(balances),
|
Box::new(balances),
|
||||||
);
|
);
|
||||||
@ -423,7 +421,7 @@ impl ReportingStep for GenerateBalances {
|
|||||||
/// - a step which generates [Transactions] from [BalancesBetween], and for which a [BalancesAt] is also available
|
/// - a step which generates [Transactions] from [BalancesBetween], and for which a [BalancesAt] is also available
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UpdateBalancesAt {
|
pub struct UpdateBalancesAt {
|
||||||
step_name: &'static str,
|
step_name: String,
|
||||||
args: DateArgs,
|
args: DateArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,75 +437,74 @@ impl UpdateBalancesAt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_build(
|
fn can_build(
|
||||||
name: &'static str,
|
name: &str,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
args: &Box<dyn ReportingStepArgs>,
|
args: &ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !args.is::<DateArgs>() {
|
if let ReportingStepArgs::DateArgs(args) = args {
|
||||||
return false;
|
// Check for Transactions -> BalancesAt
|
||||||
}
|
if kind == ReportingProductKind::BalancesAt {
|
||||||
|
// Initially no need to check args
|
||||||
|
if let Some(step) = steps.iter().find(|s| {
|
||||||
|
s.id().name == name
|
||||||
|
&& s.id()
|
||||||
|
.product_kinds
|
||||||
|
.contains(&ReportingProductKind::Transactions)
|
||||||
|
}) {
|
||||||
|
// Check for BalancesAt -> Transactions
|
||||||
|
let dependencies_for_step = dependencies.dependencies_for_step(&step.id());
|
||||||
|
if dependencies_for_step.len() == 1
|
||||||
|
&& dependencies_for_step[0].product.kind == ReportingProductKind::BalancesAt
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for Transactions -> BalancesAt
|
// Check if BalancesBetween -> Transactions and BalancesAt is available
|
||||||
if kind == ReportingProductKind::BalancesAt {
|
if dependencies_for_step.len() == 1
|
||||||
// Initially no need to check args
|
&& dependencies_for_step[0].product.kind
|
||||||
if let Some(step) = steps.iter().find(|s| {
|
== ReportingProductKind::BalancesBetween
|
||||||
s.id().name == name
|
{
|
||||||
&& s.id()
|
match has_step_or_can_build(
|
||||||
.product_kinds
|
&ReportingProductId {
|
||||||
.contains(&ReportingProductKind::Transactions)
|
name: dependencies_for_step[0].product.name.clone(),
|
||||||
}) {
|
kind: ReportingProductKind::BalancesAt,
|
||||||
// Check for BalancesAt -> Transactions
|
args: ReportingStepArgs::DateArgs(DateArgs { date: args.date }),
|
||||||
let dependencies_for_step = dependencies.dependencies_for_step(&step.id());
|
},
|
||||||
if dependencies_for_step.len() == 1
|
steps,
|
||||||
&& dependencies_for_step[0].product.kind == ReportingProductKind::BalancesAt
|
dependencies,
|
||||||
{
|
context,
|
||||||
return true;
|
) {
|
||||||
}
|
HasStepOrCanBuild::HasStep(_)
|
||||||
|
| HasStepOrCanBuild::CanLookup(_)
|
||||||
// Check if BalancesBetween -> Transactions and BalancesAt is available
|
| HasStepOrCanBuild::CanBuild(_) => {
|
||||||
if dependencies_for_step.len() == 1
|
return true;
|
||||||
&& dependencies_for_step[0].product.kind
|
}
|
||||||
== ReportingProductKind::BalancesBetween
|
HasStepOrCanBuild::None => {}
|
||||||
{
|
|
||||||
match has_step_or_can_build(
|
|
||||||
&ReportingProductId {
|
|
||||||
name: dependencies_for_step[0].product.name,
|
|
||||||
kind: ReportingProductKind::BalancesAt,
|
|
||||||
args: Box::new(DateArgs {
|
|
||||||
date: args.downcast_ref::<DateArgs>().unwrap().date,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
steps,
|
|
||||||
dependencies,
|
|
||||||
context,
|
|
||||||
) {
|
|
||||||
HasStepOrCanBuild::HasStep(_)
|
|
||||||
| HasStepOrCanBuild::CanLookup(_)
|
|
||||||
| HasStepOrCanBuild::CanBuild(_) => {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
HasStepOrCanBuild::None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
name: &'static str,
|
name: String,
|
||||||
_kind: ReportingProductKind,
|
_kind: ReportingProductKind,
|
||||||
args: Box<dyn ReportingStepArgs>,
|
args: ReportingStepArgs,
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
_dependencies: &ReportingGraphDependencies,
|
_dependencies: &ReportingGraphDependencies,
|
||||||
_context: &ReportingContext,
|
_context: &ReportingContext,
|
||||||
) -> Box<dyn ReportingStep> {
|
) -> Box<dyn ReportingStep> {
|
||||||
Box::new(UpdateBalancesAt {
|
Box::new(UpdateBalancesAt {
|
||||||
step_name: name,
|
step_name: name,
|
||||||
args: *args.downcast().unwrap(),
|
args: args.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,9 +519,9 @@ impl Display for UpdateBalancesAt {
|
|||||||
impl ReportingStep for UpdateBalancesAt {
|
impl ReportingStep for UpdateBalancesAt {
|
||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
product_kinds: &[ReportingProductKind::BalancesAt],
|
product_kinds: vec![ReportingProductKind::BalancesAt],
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,7 +546,7 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: parent_step.id().args.clone(),
|
args: parent_step.id().args.clone(),
|
||||||
},
|
},
|
||||||
@ -567,9 +564,9 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: dependency.name,
|
name: dependency.name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date,
|
date: self.args.date,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -600,7 +597,7 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
// Get transactions
|
// Get transactions
|
||||||
let transactions = &products
|
let transactions = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: parent_step.id().args,
|
args: parent_step.id().args,
|
||||||
})?
|
})?
|
||||||
@ -624,9 +621,9 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
// As checked in can_build, must depend on BalancesBetween -> Transaction with a BalancesAt available
|
// As checked in can_build, must depend on BalancesBetween -> Transaction with a BalancesAt available
|
||||||
opening_balances_at = products
|
opening_balances_at = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: dependency.name,
|
name: dependency.name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: self.args.date,
|
date: self.args.date,
|
||||||
}),
|
}),
|
||||||
})?
|
})?
|
||||||
@ -649,9 +646,9 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
let mut result = ReportingProducts::new();
|
let mut result = ReportingProducts::new();
|
||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(balances),
|
Box::new(balances),
|
||||||
);
|
);
|
||||||
@ -662,7 +659,7 @@ impl ReportingStep for UpdateBalancesAt {
|
|||||||
/// This dynamic builder automatically generates a [BalancesBetween] from a step which generates [Transactions] from [BalancesBetween]
|
/// This dynamic builder automatically generates a [BalancesBetween] from a step which generates [Transactions] from [BalancesBetween]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UpdateBalancesBetween {
|
pub struct UpdateBalancesBetween {
|
||||||
step_name: &'static str,
|
step_name: String,
|
||||||
args: DateStartDateEndArgs,
|
args: DateStartDateEndArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,9 +673,9 @@ impl UpdateBalancesBetween {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_build(
|
fn can_build(
|
||||||
name: &'static str,
|
name: &str,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
_args: &Box<dyn ReportingStepArgs>,
|
_args: &ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
_context: &ReportingContext,
|
_context: &ReportingContext,
|
||||||
@ -706,16 +703,16 @@ impl UpdateBalancesBetween {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
name: &'static str,
|
name: String,
|
||||||
_kind: ReportingProductKind,
|
_kind: ReportingProductKind,
|
||||||
args: Box<dyn ReportingStepArgs>,
|
args: ReportingStepArgs,
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
_dependencies: &ReportingGraphDependencies,
|
_dependencies: &ReportingGraphDependencies,
|
||||||
_context: &ReportingContext,
|
_context: &ReportingContext,
|
||||||
) -> Box<dyn ReportingStep> {
|
) -> Box<dyn ReportingStep> {
|
||||||
Box::new(UpdateBalancesBetween {
|
Box::new(UpdateBalancesBetween {
|
||||||
step_name: name,
|
step_name: name,
|
||||||
args: *args.downcast().unwrap(),
|
args: args.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -730,9 +727,9 @@ impl Display for UpdateBalancesBetween {
|
|||||||
impl ReportingStep for UpdateBalancesBetween {
|
impl ReportingStep for UpdateBalancesBetween {
|
||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
product_kinds: &[ReportingProductKind::BalancesBetween],
|
product_kinds: vec![ReportingProductKind::BalancesBetween],
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +754,7 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: parent_step.id().args,
|
args: parent_step.id().args,
|
||||||
},
|
},
|
||||||
@ -767,11 +764,10 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
let dependencies_for_step = dependencies.dependencies_for_step(&parent_step.id());
|
let dependencies_for_step = dependencies.dependencies_for_step(&parent_step.id());
|
||||||
let balances_between_product = &dependencies_for_step[0].product; // Existence and uniqueness checked in can_build
|
let balances_between_product = &dependencies_for_step[0].product; // Existence and uniqueness checked in can_build
|
||||||
|
|
||||||
if *balances_between_product
|
if matches!(
|
||||||
.args
|
balances_between_product.args,
|
||||||
.downcast_ref::<DateStartDateEndArgs>()
|
ReportingStepArgs::DateStartDateEndArgs(_)
|
||||||
.unwrap() == self.args
|
) {
|
||||||
{
|
|
||||||
// Directly depends on BalanceBetween -> Transaction with appropriate date
|
// Directly depends on BalanceBetween -> Transaction with appropriate date
|
||||||
// Do not need to add extra dependencies
|
// Do not need to add extra dependencies
|
||||||
} else {
|
} else {
|
||||||
@ -779,9 +775,9 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
dependencies.add_dependency(
|
dependencies.add_dependency(
|
||||||
self.id(),
|
self.id(),
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: balances_between_product.name,
|
name: balances_between_product.name.clone(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -810,7 +806,7 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
// Get transactions
|
// Get transactions
|
||||||
let transactions = &products
|
let transactions = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: parent_step.id().args,
|
args: parent_step.id().args,
|
||||||
})?
|
})?
|
||||||
@ -825,9 +821,9 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
// Get opening balances
|
// Get opening balances
|
||||||
let opening_balances = &products
|
let opening_balances = &products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: balances_between_product.name,
|
name: balances_between_product.name.clone(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
})?
|
})?
|
||||||
.downcast_ref::<BalancesBetween>()
|
.downcast_ref::<BalancesBetween>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -849,9 +845,9 @@ impl ReportingStep for UpdateBalancesBetween {
|
|||||||
let mut result = ReportingProducts::new();
|
let mut result = ReportingProducts::new();
|
||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.step_name,
|
name: self.step_name.clone(),
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
args: Box::new(self.args.clone()),
|
args: ReportingStepArgs::DateStartDateEndArgs(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(balances),
|
Box::new(balances),
|
||||||
);
|
);
|
||||||
|
@ -96,7 +96,7 @@ pub fn has_step_or_can_build<'a, 'b>(
|
|||||||
.find(|(name, kinds)| *name == product.name && kinds.contains(&product.kind))
|
.find(|(name, kinds)| *name == product.name && kinds.contains(&product.kind))
|
||||||
{
|
{
|
||||||
let (takes_args_fn, from_args_fn) = context.step_lookup_fn.get(lookup_key).unwrap();
|
let (takes_args_fn, from_args_fn) = context.step_lookup_fn.get(lookup_key).unwrap();
|
||||||
if takes_args_fn(&product.args) {
|
if takes_args_fn(&product.name, &product.args, context) {
|
||||||
return HasStepOrCanBuild::CanLookup(*from_args_fn);
|
return HasStepOrCanBuild::CanLookup(*from_args_fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ pub fn has_step_or_can_build<'a, 'b>(
|
|||||||
// No explicit step for product - try builders
|
// No explicit step for product - try builders
|
||||||
for builder in context.step_dynamic_builders.iter() {
|
for builder in context.step_dynamic_builders.iter() {
|
||||||
if (builder.can_build)(
|
if (builder.can_build)(
|
||||||
product.name,
|
&product.name,
|
||||||
product.kind,
|
product.kind,
|
||||||
&product.args,
|
&product.args,
|
||||||
steps,
|
steps,
|
||||||
@ -133,7 +133,7 @@ fn build_step_for_product(
|
|||||||
panic!("Attempted to call build_step_for_product for already existing step")
|
panic!("Attempted to call build_step_for_product for already existing step")
|
||||||
}
|
}
|
||||||
HasStepOrCanBuild::CanLookup(from_args_fn) => {
|
HasStepOrCanBuild::CanLookup(from_args_fn) => {
|
||||||
new_step = from_args_fn(product.args.clone());
|
new_step = from_args_fn(&product.name, product.args.clone(), context);
|
||||||
|
|
||||||
// Check new step meets the dependency
|
// Check new step meets the dependency
|
||||||
if new_step.id().name != product.name {
|
if new_step.id().name != product.name {
|
||||||
@ -162,7 +162,7 @@ fn build_step_for_product(
|
|||||||
}
|
}
|
||||||
HasStepOrCanBuild::CanBuild(builder) => {
|
HasStepOrCanBuild::CanBuild(builder) => {
|
||||||
new_step = (builder.build)(
|
new_step = (builder.build)(
|
||||||
product.name,
|
product.name.clone(),
|
||||||
product.kind,
|
product.kind,
|
||||||
product.args.clone(),
|
product.args.clone(),
|
||||||
&steps,
|
&steps,
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// FIXME: Tidy up this file
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -27,161 +24,7 @@ use crate::QuantityInt;
|
|||||||
|
|
||||||
use super::types::ReportingProduct;
|
use super::types::ReportingProduct;
|
||||||
|
|
||||||
/// Represents a dynamically generated report composed of [CalculatableDynamicReportEntry]
|
/// Represents a dynamically generated report composed of [DynamicReportEntry]
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CalculatableDynamicReport {
|
|
||||||
pub title: String,
|
|
||||||
pub columns: Vec<String>,
|
|
||||||
// This must use RefCell as, during calculation, we iterate while mutating the report
|
|
||||||
pub entries: Vec<RefCell<CalculatableDynamicReportEntry>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CalculatableDynamicReport {
|
|
||||||
pub fn new(
|
|
||||||
title: String,
|
|
||||||
columns: Vec<String>,
|
|
||||||
entries: Vec<CalculatableDynamicReportEntry>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
title,
|
|
||||||
columns,
|
|
||||||
entries: entries.into_iter().map(|e| RefCell::new(e)).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively calculate all [CalculatedRow] entries
|
|
||||||
pub fn calculate(self) -> DynamicReport {
|
|
||||||
let mut calculated_entries = Vec::new();
|
|
||||||
|
|
||||||
for (entry_idx, entry) in self.entries.iter().enumerate() {
|
|
||||||
let entry_ref = entry.borrow();
|
|
||||||
|
|
||||||
match &*entry_ref {
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
|
||||||
// Clone first, in case calculation needs to take reference to the section
|
|
||||||
let updated_section = section.clone().calculate(&self);
|
|
||||||
|
|
||||||
drop(entry_ref); // Drop entry_ref so we can borrow mutably
|
|
||||||
let mut entry_mut = self.entries[entry_idx].borrow_mut();
|
|
||||||
*entry_mut = CalculatableDynamicReportEntry::Section(updated_section.clone());
|
|
||||||
|
|
||||||
calculated_entries.push(DynamicReportEntry::Section(updated_section));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Section(section) => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::Section(section.clone()));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::LiteralRow(row.clone()));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(row) => {
|
|
||||||
let updated_row = row.calculate(&self);
|
|
||||||
|
|
||||||
drop(entry_ref); // Drop entry_ref so we can borrow mutably
|
|
||||||
let mut entry_mut = self.entries[entry_idx].borrow_mut();
|
|
||||||
*entry_mut = CalculatableDynamicReportEntry::LiteralRow(updated_row.clone());
|
|
||||||
|
|
||||||
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Spacer => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::Spacer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicReport {
|
|
||||||
title: self.title,
|
|
||||||
columns: self.columns,
|
|
||||||
entries: calculated_entries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up [CalculatableDynamicReportEntry] by id
|
|
||||||
///
|
|
||||||
/// Returns a cloned copy of the [CalculatableDynamicReportEntry]. This is necessary because the entry may be within a [Section], and [RefCell] semantics cannot express this type of nested borrow.
|
|
||||||
pub fn by_id(&self, id: &str) -> Option<CalculatableDynamicReportEntry> {
|
|
||||||
// Manually iterate over self.entries rather than self.entries()
|
|
||||||
// To catch the situation where entry is already mutably borrowed
|
|
||||||
for entry in self.entries.iter() {
|
|
||||||
match entry.try_borrow() {
|
|
||||||
Ok(entry) => match &*entry {
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
|
||||||
if let Some(i) = §ion.id {
|
|
||||||
if i == id {
|
|
||||||
return Some(entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(e) = section.by_id(id) {
|
|
||||||
return Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Section(section) => {
|
|
||||||
if let Some(i) = §ion.id {
|
|
||||||
if i == id {
|
|
||||||
return Some(entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(e) = section.by_id(id) {
|
|
||||||
return Some(match e {
|
|
||||||
DynamicReportEntry::Section(section) => {
|
|
||||||
CalculatableDynamicReportEntry::Section(section.clone())
|
|
||||||
}
|
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row.clone())
|
|
||||||
}
|
|
||||||
DynamicReportEntry::Spacer => {
|
|
||||||
CalculatableDynamicReportEntry::Spacer
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
|
||||||
if let Some(i) = &row.id {
|
|
||||||
if i == id {
|
|
||||||
return Some(entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(_) => (),
|
|
||||||
CalculatableDynamicReportEntry::Spacer => (),
|
|
||||||
},
|
|
||||||
Err(err) => panic!(
|
|
||||||
"Attempt to call by_id on DynamicReportEntry which is mutably borrowed: {}",
|
|
||||||
err
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the subtotals for the [Section] with the given id
|
|
||||||
pub fn subtotal_for_id(&self, id: &str) -> Option<Vec<QuantityInt>> {
|
|
||||||
if let Some(entry) = self.by_id(id) {
|
|
||||||
if let CalculatableDynamicReportEntry::CalculatableSection(section) = entry {
|
|
||||||
Some(section.subtotal(&self))
|
|
||||||
} else {
|
|
||||||
panic!("Called subtotal_for_id on non-Section");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the quantities for the [LiteralRow] with the given id
|
|
||||||
pub fn quantity_for_id(&self, id: &str) -> Option<Vec<QuantityInt>> {
|
|
||||||
if let Some(entry) = self.by_id(id) {
|
|
||||||
if let CalculatableDynamicReportEntry::LiteralRow(row) = entry {
|
|
||||||
Some(row.quantity)
|
|
||||||
} else {
|
|
||||||
panic!("Called quantity_for_id on non-LiteralRow");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a dynamically generated report composed of [DynamicReportEntry], with no [CalculatedRow]s
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct DynamicReport {
|
pub struct DynamicReport {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@ -198,37 +41,13 @@ impl DynamicReport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all entries from the report where auto_hide is enabled and quantity is zero
|
|
||||||
pub fn auto_hide(&mut self) {
|
|
||||||
self.entries.retain_mut(|e| match e {
|
|
||||||
DynamicReportEntry::Section(section) => {
|
|
||||||
section.auto_hide_children();
|
|
||||||
if section.can_auto_hide_self() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
|
||||||
if row.can_auto_hide() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicReportEntry::Spacer => true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialise the report (as JSON) using serde
|
/// Serialise the report (as JSON) using serde
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
serde_json::to_string(self).unwrap()
|
serde_json::to_string(self).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up [DynamicReportEntry] by id
|
/// Look up [DynamicReportEntry] by id
|
||||||
///
|
pub fn by_id(&self, id: &str) -> Option<&DynamicReportEntry> {
|
||||||
/// Returns a cloned copy of the [DynamicReportEntry]. This is necessary because the entry may be within a [Section], and [RefCell] semantics cannot express this type of nested borrow.
|
|
||||||
pub fn by_id(&self, id: &str) -> Option<DynamicReportEntry> {
|
|
||||||
// Manually iterate over self.entries rather than self.entries()
|
// Manually iterate over self.entries rather than self.entries()
|
||||||
// To catch the situation where entry is already mutably borrowed
|
// To catch the situation where entry is already mutably borrowed
|
||||||
for entry in self.entries.iter() {
|
for entry in self.entries.iter() {
|
||||||
@ -236,25 +55,17 @@ impl DynamicReport {
|
|||||||
DynamicReportEntry::Section(section) => {
|
DynamicReportEntry::Section(section) => {
|
||||||
if let Some(i) = §ion.id {
|
if let Some(i) = §ion.id {
|
||||||
if i == id {
|
if i == id {
|
||||||
return Some(entry.clone());
|
return Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(e) = section.by_id(id) {
|
if let Some(e) = section.by_id(id) {
|
||||||
return Some(match e {
|
return Some(e);
|
||||||
DynamicReportEntry::Section(section) => {
|
|
||||||
DynamicReportEntry::Section(section.clone())
|
|
||||||
}
|
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
|
||||||
DynamicReportEntry::LiteralRow(row.clone())
|
|
||||||
}
|
|
||||||
DynamicReportEntry::Spacer => DynamicReportEntry::Spacer,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
DynamicReportEntry::Row(row) => {
|
||||||
if let Some(i) = &row.id {
|
if let Some(i) = &row.id {
|
||||||
if i == id {
|
if i == id {
|
||||||
return Some(entry.clone());
|
return Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,10 +77,10 @@ impl DynamicReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the quantities for the [LiteralRow] with the given id
|
// Return the quantities for the [LiteralRow] with the given id
|
||||||
pub fn quantity_for_id(&self, id: &str) -> Option<Vec<QuantityInt>> {
|
pub fn quantity_for_id(&self, id: &str) -> Option<&Vec<QuantityInt>> {
|
||||||
if let Some(entry) = self.by_id(id) {
|
if let Some(entry) = self.by_id(id) {
|
||||||
if let DynamicReportEntry::LiteralRow(row) = entry {
|
if let DynamicReportEntry::Row(row) = entry {
|
||||||
Some(row.quantity)
|
Some(&row.quantity)
|
||||||
} else {
|
} else {
|
||||||
panic!("Called quantity_for_id on non-LiteralRow");
|
panic!("Called quantity_for_id on non-LiteralRow");
|
||||||
}
|
}
|
||||||
@ -281,207 +92,36 @@ impl DynamicReport {
|
|||||||
|
|
||||||
impl ReportingProduct for DynamicReport {}
|
impl ReportingProduct for DynamicReport {}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum CalculatableDynamicReportEntry {
|
|
||||||
CalculatableSection(CalculatableSection),
|
|
||||||
Section(Section),
|
|
||||||
LiteralRow(LiteralRow),
|
|
||||||
CalculatedRow(CalculatedRow),
|
|
||||||
Spacer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum DynamicReportEntry {
|
pub enum DynamicReportEntry {
|
||||||
Section(Section),
|
Section(Section),
|
||||||
LiteralRow(LiteralRow),
|
Row(Row),
|
||||||
Spacer,
|
Spacer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl From<Section> for DynamicReportEntry {
|
||||||
pub struct CalculatableSection {
|
fn from(value: Section) -> Self {
|
||||||
pub text: String,
|
DynamicReportEntry::Section(value)
|
||||||
pub id: Option<String>,
|
}
|
||||||
pub visible: bool,
|
|
||||||
pub auto_hide: bool,
|
|
||||||
pub entries: Vec<RefCell<CalculatableDynamicReportEntry>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalculatableSection {
|
impl From<Row> for DynamicReportEntry {
|
||||||
pub fn new(
|
fn from(value: Row) -> Self {
|
||||||
text: String,
|
DynamicReportEntry::Row(value)
|
||||||
id: Option<String>,
|
|
||||||
visible: bool,
|
|
||||||
auto_hide: bool,
|
|
||||||
entries: Vec<CalculatableDynamicReportEntry>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
text,
|
|
||||||
id,
|
|
||||||
visible,
|
|
||||||
auto_hide,
|
|
||||||
entries: entries.into_iter().map(|e| RefCell::new(e)).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively calculate all [CalculatedRow] entries
|
|
||||||
pub fn calculate(&mut self, report: &CalculatableDynamicReport) -> Section {
|
|
||||||
let mut calculated_entries = Vec::new();
|
|
||||||
|
|
||||||
for (entry_idx, entry) in self.entries.iter().enumerate() {
|
|
||||||
let entry_ref = entry.borrow();
|
|
||||||
|
|
||||||
match &*entry_ref {
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
|
||||||
let updated_section = section.clone().calculate(&report);
|
|
||||||
|
|
||||||
drop(entry_ref); // Drop entry_ref so we can borrow mutably
|
|
||||||
let mut entry_mut = self.entries[entry_idx].borrow_mut();
|
|
||||||
*entry_mut = CalculatableDynamicReportEntry::Section(updated_section.clone());
|
|
||||||
|
|
||||||
calculated_entries.push(DynamicReportEntry::Section(updated_section));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Section(section) => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::Section(section.clone()));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::LiteralRow(row.clone()));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(row) => {
|
|
||||||
let updated_row = row.calculate(&report);
|
|
||||||
|
|
||||||
drop(entry_ref); // Drop entry_ref so we can borrow mutably
|
|
||||||
let mut entry_mut = self.entries[entry_idx].borrow_mut();
|
|
||||||
*entry_mut = CalculatableDynamicReportEntry::LiteralRow(updated_row.clone());
|
|
||||||
|
|
||||||
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Spacer => {
|
|
||||||
calculated_entries.push(DynamicReportEntry::Spacer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section {
|
|
||||||
text: self.text.clone(),
|
|
||||||
id: self.id.clone(),
|
|
||||||
visible: self.visible,
|
|
||||||
auto_hide: self.auto_hide,
|
|
||||||
entries: calculated_entries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up [CalculatableDynamicReportEntry] by id
|
|
||||||
///
|
|
||||||
/// Returns a cloned copy of the [CalculatableDynamicReportEntry].
|
|
||||||
pub fn by_id(&self, id: &str) -> Option<CalculatableDynamicReportEntry> {
|
|
||||||
// Manually iterate over self.entries rather than self.entries()
|
|
||||||
// To catch the situation where entry is already mutably borrowed
|
|
||||||
for entry in self.entries.iter() {
|
|
||||||
match entry.try_borrow() {
|
|
||||||
Ok(entry) => match &*entry {
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
|
||||||
if let Some(i) = §ion.id {
|
|
||||||
if i == id {
|
|
||||||
return Some(entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(e) = section.by_id(id) {
|
|
||||||
return Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Section(_) => todo!(),
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
|
||||||
if let Some(i) = &row.id {
|
|
||||||
if i == id {
|
|
||||||
return Some(entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(_) => (),
|
|
||||||
CalculatableDynamicReportEntry::Spacer => (),
|
|
||||||
},
|
|
||||||
Err(err) => panic!(
|
|
||||||
"Attempt to call by_id on DynamicReportEntry which is mutably borrowed: {}",
|
|
||||||
err
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the subtotals for this [CalculatableSection]
|
|
||||||
pub fn subtotal(&self, report: &CalculatableDynamicReport) -> Vec<QuantityInt> {
|
|
||||||
let mut subtotals = vec![0; report.columns.len()];
|
|
||||||
for entry in self.entries.iter() {
|
|
||||||
match &*entry.borrow() {
|
|
||||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
|
||||||
for (col_idx, subtotal) in section.subtotal(report).into_iter().enumerate() {
|
|
||||||
subtotals[col_idx] += subtotal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::Section(section) => {
|
|
||||||
for (col_idx, subtotal) in section.subtotal(report).into_iter().enumerate() {
|
|
||||||
subtotals[col_idx] += subtotal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
|
||||||
for (col_idx, subtotal) in row.quantity.iter().enumerate() {
|
|
||||||
subtotals[col_idx] += subtotal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CalculatableDynamicReportEntry::CalculatedRow(_) => (),
|
|
||||||
CalculatableDynamicReportEntry::Spacer => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subtotals
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Section {
|
pub struct Section {
|
||||||
pub text: String,
|
pub text: Option<String>,
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub auto_hide: bool,
|
|
||||||
pub entries: Vec<DynamicReportEntry>,
|
pub entries: Vec<DynamicReportEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Section {
|
impl Section {
|
||||||
fn auto_hide_children(&mut self) {
|
|
||||||
self.entries.retain_mut(|e| match e {
|
|
||||||
DynamicReportEntry::Section(section) => {
|
|
||||||
section.auto_hide_children();
|
|
||||||
if section.can_auto_hide_self() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
|
||||||
if row.can_auto_hide() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicReportEntry::Spacer => true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_auto_hide_self(&self) -> bool {
|
|
||||||
self.auto_hide
|
|
||||||
&& self.entries.iter().all(|e| match e {
|
|
||||||
DynamicReportEntry::Section(section) => section.can_auto_hide_self(),
|
|
||||||
DynamicReportEntry::LiteralRow(row) => row.can_auto_hide(),
|
|
||||||
DynamicReportEntry::Spacer => true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up [DynamicReportEntry] by id
|
/// Look up [DynamicReportEntry] by id
|
||||||
///
|
pub fn by_id(&self, id: &str) -> Option<&DynamicReportEntry> {
|
||||||
/// Returns a cloned copy of the [DynamicReportEntry].
|
|
||||||
pub fn by_id(&self, id: &str) -> Option<DynamicReportEntry> {
|
|
||||||
// Manually iterate over self.entries rather than self.entries()
|
// Manually iterate over self.entries rather than self.entries()
|
||||||
// To catch the situation where entry is already mutably borrowed
|
// To catch the situation where entry is already mutably borrowed
|
||||||
for entry in self.entries.iter() {
|
for entry in self.entries.iter() {
|
||||||
@ -489,17 +129,17 @@ impl Section {
|
|||||||
DynamicReportEntry::Section(section) => {
|
DynamicReportEntry::Section(section) => {
|
||||||
if let Some(i) = §ion.id {
|
if let Some(i) = §ion.id {
|
||||||
if i == id {
|
if i == id {
|
||||||
return Some(entry.clone());
|
return Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(e) = section.by_id(id) {
|
if let Some(e) = section.by_id(id) {
|
||||||
return Some(e);
|
return Some(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
DynamicReportEntry::Row(row) => {
|
||||||
if let Some(i) = &row.id {
|
if let Some(i) = &row.id {
|
||||||
if i == id {
|
if i == id {
|
||||||
return Some(entry.clone());
|
return Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,7 +151,7 @@ impl Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the subtotals for this [Section]
|
/// Calculate the subtotals for this [Section]
|
||||||
pub fn subtotal(&self, report: &CalculatableDynamicReport) -> Vec<QuantityInt> {
|
pub fn subtotal(&self, report: &DynamicReport) -> Vec<QuantityInt> {
|
||||||
let mut subtotals = vec![0; report.columns.len()];
|
let mut subtotals = vec![0; report.columns.len()];
|
||||||
for entry in self.entries.iter() {
|
for entry in self.entries.iter() {
|
||||||
match entry {
|
match entry {
|
||||||
@ -520,7 +160,7 @@ impl Section {
|
|||||||
subtotals[col_idx] += subtotal;
|
subtotals[col_idx] += subtotal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicReportEntry::LiteralRow(row) => {
|
DynamicReportEntry::Row(row) => {
|
||||||
for (col_idx, subtotal) in row.quantity.iter().enumerate() {
|
for (col_idx, subtotal) in row.quantity.iter().enumerate() {
|
||||||
subtotals[col_idx] += subtotal;
|
subtotals[col_idx] += subtotal;
|
||||||
}
|
}
|
||||||
@ -533,48 +173,22 @@ impl Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct LiteralRow {
|
pub struct Row {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub quantity: Vec<QuantityInt>,
|
pub quantity: Vec<QuantityInt>,
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub auto_hide: bool,
|
|
||||||
pub link: Option<String>,
|
pub link: Option<String>,
|
||||||
pub heading: bool,
|
pub heading: bool,
|
||||||
pub bordered: bool,
|
pub bordered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LiteralRow {
|
|
||||||
/// Returns whether the row has auto_hide enabled and all quantities are zero
|
|
||||||
fn can_auto_hide(&self) -> bool {
|
|
||||||
self.auto_hide && self.quantity.iter().all(|q| *q == 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CalculatedRow {
|
|
||||||
//pub text: String,
|
|
||||||
pub calculate_fn: fn(report: &CalculatableDynamicReport) -> LiteralRow,
|
|
||||||
//pub id: Option<String>,
|
|
||||||
//pub visible: bool,
|
|
||||||
//pub auto_hide: bool,
|
|
||||||
//pub link: Option<String>,
|
|
||||||
//pub heading: bool,
|
|
||||||
//pub bordered: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CalculatedRow {
|
|
||||||
fn calculate(&self, report: &CalculatableDynamicReport) -> LiteralRow {
|
|
||||||
(self.calculate_fn)(report)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn entries_for_kind(
|
pub fn entries_for_kind(
|
||||||
kind: &str,
|
kind: &str,
|
||||||
invert: bool,
|
invert: bool,
|
||||||
balances: &Vec<&HashMap<String, QuantityInt>>,
|
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||||
kinds_for_account: &HashMap<String, Vec<String>>,
|
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||||
) -> Vec<CalculatableDynamicReportEntry> {
|
) -> Vec<DynamicReportEntry> {
|
||||||
// Get accounts of specified kind
|
// Get accounts of specified kind
|
||||||
let mut accounts = kinds_for_account
|
let mut accounts = kinds_for_account
|
||||||
.iter()
|
.iter()
|
||||||
@ -596,6 +210,11 @@ pub fn entries_for_kind(
|
|||||||
.map(|b| b.get(account).unwrap_or(&0) * if invert { -1 } else { 1 })
|
.map(|b| b.get(account).unwrap_or(&0) * if invert { -1 } else { 1 })
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Do not show if all quantities are zero
|
||||||
|
if quantities.iter().all(|q| *q == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Some exceptions for the link
|
// Some exceptions for the link
|
||||||
let link;
|
let link;
|
||||||
if account == crate::CURRENT_YEAR_EARNINGS {
|
if account == crate::CURRENT_YEAR_EARNINGS {
|
||||||
@ -606,17 +225,16 @@ pub fn entries_for_kind(
|
|||||||
link = Some(format!("/transactions/{}", account));
|
link = Some(format!("/transactions/{}", account));
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = LiteralRow {
|
let entry = Row {
|
||||||
text: account.to_string(),
|
text: account.to_string(),
|
||||||
quantity: quantities,
|
quantity: quantities,
|
||||||
id: None,
|
id: None,
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: true,
|
|
||||||
link,
|
link,
|
||||||
heading: false,
|
heading: false,
|
||||||
bordered: false,
|
bordered: false,
|
||||||
};
|
};
|
||||||
entries.push(CalculatableDynamicReportEntry::LiteralRow(entry));
|
entries.push(entry.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
entries
|
entries
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -24,14 +24,13 @@ use async_trait::async_trait;
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
use dyn_eq::DynEq;
|
|
||||||
use dyn_hash::DynHash;
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::db::DbConnection;
|
use crate::db::DbConnection;
|
||||||
use crate::model::transaction::TransactionWithPostings;
|
use crate::model::transaction::TransactionWithPostings;
|
||||||
|
use crate::plugin::PluginSpec;
|
||||||
use crate::QuantityInt;
|
use crate::QuantityInt;
|
||||||
|
|
||||||
use super::calculator::ReportingGraphDependencies;
|
use super::calculator::ReportingGraphDependencies;
|
||||||
@ -44,30 +43,38 @@ use super::executor::ReportingExecutionError;
|
|||||||
pub struct ReportingContext {
|
pub struct ReportingContext {
|
||||||
// Configuration
|
// Configuration
|
||||||
pub db_connection: DbConnection,
|
pub db_connection: DbConnection,
|
||||||
|
pub plugin_dir: String,
|
||||||
|
pub plugin_names: Vec<String>,
|
||||||
pub eofy_date: NaiveDate,
|
pub eofy_date: NaiveDate,
|
||||||
pub reporting_commodity: String,
|
pub reporting_commodity: String,
|
||||||
|
|
||||||
// State
|
// State
|
||||||
pub(crate) step_lookup_fn: HashMap<
|
pub(crate) step_lookup_fn: HashMap<
|
||||||
(&'static str, &'static [ReportingProductKind]),
|
(String, Vec<ReportingProductKind>),
|
||||||
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
|
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
|
||||||
>,
|
>,
|
||||||
pub(crate) step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
|
pub(crate) step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
|
||||||
|
pub(crate) plugin_specs: HashMap<String, PluginSpec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingContext {
|
impl ReportingContext {
|
||||||
/// Initialise a new [ReportingContext]
|
/// Initialise a new [ReportingContext]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
db_connection: DbConnection,
|
db_connection: DbConnection,
|
||||||
|
plugin_dir: String,
|
||||||
|
plugin_names: Vec<String>,
|
||||||
eofy_date: NaiveDate,
|
eofy_date: NaiveDate,
|
||||||
reporting_commodity: String,
|
reporting_commodity: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db_connection,
|
db_connection,
|
||||||
|
plugin_dir,
|
||||||
|
plugin_names,
|
||||||
eofy_date,
|
eofy_date,
|
||||||
reporting_commodity,
|
reporting_commodity,
|
||||||
step_lookup_fn: HashMap::new(),
|
step_lookup_fn: HashMap::new(),
|
||||||
step_dynamic_builders: Vec::new(),
|
step_dynamic_builders: Vec::new(),
|
||||||
|
plugin_specs: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +83,8 @@ impl ReportingContext {
|
|||||||
/// A lookup function generates concrete [ReportingStep]s from a [ReportingStepId].
|
/// A lookup function generates concrete [ReportingStep]s from a [ReportingStepId].
|
||||||
pub fn register_lookup_fn(
|
pub fn register_lookup_fn(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &'static str,
|
name: String,
|
||||||
product_kinds: &'static [ReportingProductKind],
|
product_kinds: Vec<ReportingProductKind>,
|
||||||
takes_args_fn: ReportingStepTakesArgsFn,
|
takes_args_fn: ReportingStepTakesArgsFn,
|
||||||
from_args_fn: ReportingStepFromArgsFn,
|
from_args_fn: ReportingStepFromArgsFn,
|
||||||
) {
|
) {
|
||||||
@ -102,12 +109,14 @@ impl ReportingContext {
|
|||||||
/// Function which determines whether the [ReportingStepArgs] are valid arguments for a given [ReportingStep]
|
/// Function which determines whether the [ReportingStepArgs] are valid arguments for a given [ReportingStep]
|
||||||
///
|
///
|
||||||
/// See [ReportingContext::register_lookup_fn].
|
/// See [ReportingContext::register_lookup_fn].
|
||||||
pub type ReportingStepTakesArgsFn = fn(args: &Box<dyn ReportingStepArgs>) -> bool;
|
pub type ReportingStepTakesArgsFn =
|
||||||
|
fn(name: &str, args: &ReportingStepArgs, context: &ReportingContext) -> bool;
|
||||||
|
|
||||||
/// Function which builds a concrete [ReportingStep] from the given [ReportingStepArgs]
|
/// Function which builds a concrete [ReportingStep] from the given [ReportingStepArgs]
|
||||||
///
|
///
|
||||||
/// See [ReportingContext::register_lookup_fn].
|
/// See [ReportingContext::register_lookup_fn].
|
||||||
pub type ReportingStepFromArgsFn = fn(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep>;
|
pub type ReportingStepFromArgsFn =
|
||||||
|
fn(name: &str, args: ReportingStepArgs, context: &ReportingContext) -> Box<dyn ReportingStep>;
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// REPORTING STEP DYNAMIC BUILDERS
|
// REPORTING STEP DYNAMIC BUILDERS
|
||||||
@ -118,17 +127,17 @@ pub type ReportingStepFromArgsFn = fn(args: Box<dyn ReportingStepArgs>) -> Box<d
|
|||||||
pub struct ReportingStepDynamicBuilder {
|
pub struct ReportingStepDynamicBuilder {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub can_build: fn(
|
pub can_build: fn(
|
||||||
name: &'static str,
|
name: &str,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
args: &Box<dyn ReportingStepArgs>,
|
args: &ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
) -> bool,
|
) -> bool,
|
||||||
pub build: fn(
|
pub build: fn(
|
||||||
name: &'static str,
|
name: String,
|
||||||
kind: ReportingProductKind,
|
kind: ReportingProductKind,
|
||||||
args: Box<dyn ReportingStepArgs>,
|
args: ReportingStepArgs,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
@ -139,11 +148,11 @@ pub struct ReportingStepDynamicBuilder {
|
|||||||
// REPORTING PRODUCTS
|
// REPORTING PRODUCTS
|
||||||
|
|
||||||
/// Identifies a [ReportingProduct]
|
/// Identifies a [ReportingProduct]
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub struct ReportingProductId {
|
pub struct ReportingProductId {
|
||||||
pub name: &'static str,
|
pub name: String,
|
||||||
pub kind: ReportingProductKind,
|
pub kind: ReportingProductKind,
|
||||||
pub args: Box<dyn ReportingStepArgs>,
|
pub args: ReportingStepArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ReportingProductId {
|
impl Display for ReportingProductId {
|
||||||
@ -155,7 +164,7 @@ impl Display for ReportingProductId {
|
|||||||
/// Identifies a type of [Box]ed [ReportingProduct]
|
/// Identifies a type of [Box]ed [ReportingProduct]
|
||||||
///
|
///
|
||||||
/// See [Box::downcast].
|
/// See [Box::downcast].
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub enum ReportingProductKind {
|
pub enum ReportingProductKind {
|
||||||
/// The [Box]ed [ReportingProduct] is a [Transactions]
|
/// The [Box]ed [ReportingProduct] is a [Transactions]
|
||||||
Transactions,
|
Transactions,
|
||||||
@ -276,9 +285,9 @@ impl Display for ReportingProducts {
|
|||||||
/// Identifies a [ReportingStep]
|
/// Identifies a [ReportingStep]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct ReportingStepId {
|
pub struct ReportingStepId {
|
||||||
pub name: &'static str,
|
pub name: String,
|
||||||
pub product_kinds: &'static [ReportingProductKind],
|
pub product_kinds: Vec<ReportingProductKind>,
|
||||||
pub args: Box<dyn ReportingStepArgs>,
|
pub args: ReportingStepArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ReportingStepId {
|
impl Display for ReportingStepId {
|
||||||
@ -345,50 +354,68 @@ downcast_rs::impl_downcast!(ReportingStep);
|
|||||||
// REPORTING STEP ARGUMENTS
|
// REPORTING STEP ARGUMENTS
|
||||||
|
|
||||||
/// Represents arguments to a [ReportingStep]
|
/// Represents arguments to a [ReportingStep]
|
||||||
pub trait ReportingStepArgs:
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
Debug + Display + Downcast + DynClone + DynEq + DynHash + Send + Sync
|
pub enum ReportingStepArgs {
|
||||||
{
|
// This is an enum not a trait, to simply conversion to and from Lua
|
||||||
|
/// [ReportingStepArgs] implementation which takes no arguments
|
||||||
|
VoidArgs,
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes a single date
|
||||||
|
DateArgs(DateArgs),
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes a date range
|
||||||
|
DateStartDateEndArgs(DateStartDateEndArgs),
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes multiple [DateArgs]
|
||||||
|
MultipleDateArgs(MultipleDateArgs),
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes multiple [DateStartDateEndArgs]
|
||||||
|
MultipleDateStartDateEndArgs(MultipleDateStartDateEndArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(ReportingStepArgs);
|
impl Display for ReportingStepArgs {
|
||||||
dyn_clone::clone_trait_object!(ReportingStepArgs);
|
|
||||||
dyn_eq::eq_trait_object!(ReportingStepArgs);
|
|
||||||
dyn_hash::hash_trait_object!(ReportingStepArgs);
|
|
||||||
|
|
||||||
/// [ReportingStepArgs] implementation which takes no arguments
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct VoidArgs {}
|
|
||||||
|
|
||||||
impl ReportingStepArgs for VoidArgs {}
|
|
||||||
|
|
||||||
impl Display for VoidArgs {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!(""))
|
match self {
|
||||||
|
ReportingStepArgs::VoidArgs => f.write_str("void"),
|
||||||
|
ReportingStepArgs::DateArgs(args) => f.write_fmt(format_args!("{}", args)),
|
||||||
|
ReportingStepArgs::DateStartDateEndArgs(args) => f.write_fmt(format_args!("{}", args)),
|
||||||
|
ReportingStepArgs::MultipleDateArgs(args) => f.write_fmt(format_args!("{}", args)),
|
||||||
|
ReportingStepArgs::MultipleDateStartDateEndArgs(args) => {
|
||||||
|
f.write_fmt(format_args!("{}", args))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [ReportingStepArgs] implementation which takes a single date
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct DateArgs {
|
pub struct DateArgs {
|
||||||
|
#[serde(with = "crate::serde::naivedate_to_js")]
|
||||||
pub date: NaiveDate,
|
pub date: NaiveDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingStepArgs for DateArgs {}
|
|
||||||
|
|
||||||
impl Display for DateArgs {
|
impl Display for DateArgs {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!("{}", self.date))
|
f.write_fmt(format_args!("{}", self.date))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [ReportingStepArgs] implementation which takes a date range
|
impl Into<DateArgs> for ReportingStepArgs {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
fn into(self) -> DateArgs {
|
||||||
pub struct DateStartDateEndArgs {
|
if let ReportingStepArgs::DateArgs(args) = self {
|
||||||
pub date_start: NaiveDate,
|
args
|
||||||
pub date_end: NaiveDate,
|
} else {
|
||||||
|
panic!("Expected DateArgs")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingStepArgs for DateStartDateEndArgs {}
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
|
pub struct DateStartDateEndArgs {
|
||||||
|
#[serde(with = "crate::serde::naivedate_to_js")]
|
||||||
|
pub date_start: NaiveDate,
|
||||||
|
#[serde(with = "crate::serde::naivedate_to_js")]
|
||||||
|
pub date_end: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for DateStartDateEndArgs {
|
impl Display for DateStartDateEndArgs {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@ -396,14 +423,21 @@ impl Display for DateStartDateEndArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [ReportingStepArgs] implementation which takes multiple [DateArgs]
|
impl Into<DateStartDateEndArgs> for ReportingStepArgs {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
fn into(self) -> DateStartDateEndArgs {
|
||||||
|
if let ReportingStepArgs::DateStartDateEndArgs(args) = self {
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
panic!("Expected DateStartDateEndArgs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub struct MultipleDateArgs {
|
pub struct MultipleDateArgs {
|
||||||
pub dates: Vec<DateArgs>,
|
pub dates: Vec<DateArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingStepArgs for MultipleDateArgs {}
|
|
||||||
|
|
||||||
impl Display for MultipleDateArgs {
|
impl Display for MultipleDateArgs {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
@ -417,14 +451,21 @@ impl Display for MultipleDateArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [ReportingStepArgs] implementation which takes multiple [DateStartDateEndArgs]
|
impl Into<MultipleDateArgs> for ReportingStepArgs {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
fn into(self) -> MultipleDateArgs {
|
||||||
|
if let ReportingStepArgs::MultipleDateArgs(args) = self {
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
panic!("Expected MultipleDateArgs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub struct MultipleDateStartDateEndArgs {
|
pub struct MultipleDateStartDateEndArgs {
|
||||||
pub dates: Vec<DateStartDateEndArgs>,
|
pub dates: Vec<DateStartDateEndArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingStepArgs for MultipleDateStartDateEndArgs {}
|
|
||||||
|
|
||||||
impl Display for MultipleDateStartDateEndArgs {
|
impl Display for MultipleDateStartDateEndArgs {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
@ -437,3 +478,13 @@ impl Display for MultipleDateStartDateEndArgs {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<MultipleDateStartDateEndArgs> for ReportingStepArgs {
|
||||||
|
fn into(self) -> MultipleDateStartDateEndArgs {
|
||||||
|
if let ReportingStepArgs::MultipleDateStartDateEndArgs(args) = self {
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
panic!("Expected MultipleDateStartDateEndArgs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,6 +16,51 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// Serialises [chrono::NaiveDate] in database format
|
||||||
|
///
|
||||||
|
/// Use as `#[serde(with = "crate::serde::naivedate_to_js")]`, etc.
|
||||||
|
pub mod naivedate_to_js {
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use serde::{
|
||||||
|
de::{self, Unexpected, Visitor},
|
||||||
|
Deserializer, Serializer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn serialize<S: Serializer>(
|
||||||
|
dt: &NaiveDate,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(&dt.format("%Y-%m-%d").to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DateVisitor;
|
||||||
|
impl<'de> Visitor<'de> for DateVisitor {
|
||||||
|
type Value = NaiveDate;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a date string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
||||||
|
Ok(dt) => Ok(dt),
|
||||||
|
Err(_) => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<NaiveDate, D::Error> {
|
||||||
|
deserializer.deserialize_str(DateVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Serialises [chrono::NaiveDateTime] in database format
|
/// Serialises [chrono::NaiveDateTime] in database format
|
||||||
///
|
///
|
||||||
/// Use as `#[serde(with = "crate::serde::naivedatetime_to_js")]`, etc.
|
/// Use as `#[serde(with = "crate::serde::naivedatetime_to_js")]`, etc.
|
||||||
@ -40,7 +85,7 @@ pub mod naivedatetime_to_js {
|
|||||||
type Value = NaiveDateTime;
|
type Value = NaiveDateTime;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a date string")
|
write!(formatter, "a datetime string")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||||
|
110
src-tauri/Cargo.lock
generated
110
src-tauri/Cargo.lock
generated
@ -370,6 +370,16 @@ dependencies = [
|
|||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
@ -878,7 +888,7 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libloading",
|
"libloading 0.8.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -977,18 +987,6 @@ version = "1.0.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dyn-eq"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dyn-hash"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@ -2165,7 +2163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk-sys",
|
"gtk-sys",
|
||||||
"libloading",
|
"libloading 0.7.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2183,9 +2181,8 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"downcast-rs 2.0.1",
|
"downcast-rs 2.0.1",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"dyn-eq",
|
|
||||||
"dyn-hash",
|
|
||||||
"indexmap 2.9.0",
|
"indexmap 2.9.0",
|
||||||
|
"mlua",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
@ -2202,6 +2199,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@ -2257,6 +2264,15 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "luau0-src"
|
||||||
|
version = "0.12.3+luau663"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ae337c644bbf86a8d8e9ce3ee023311833d41741baf5e51acc31b37843aba1"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac"
|
name = "mac"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -2351,6 +2367,37 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mlua"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"either",
|
||||||
|
"erased-serde",
|
||||||
|
"libloading 0.8.8",
|
||||||
|
"mlua-sys",
|
||||||
|
"num-traits",
|
||||||
|
"parking_lot",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mlua-sys"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"luau0-src",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
@ -2769,6 +2816,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-stream"
|
name = "ordered-stream"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -3424,6 +3480,12 @@ version = "0.1.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -3446,6 +3508,12 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@ -3549,6 +3617,16 @@ dependencies = [
|
|||||||
"typeid",
|
"typeid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-value"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
@ -17,21 +17,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use libdrcr::reporting::dynamic_report::DynamicReport;
|
use libdrcr::reporting::dynamic_report::DynamicReport;
|
||||||
use libdrcr::reporting::types::{ReportingProductId, ReportingProductKind, VoidArgs};
|
use libdrcr::reporting::types::{ReportingProductId, ReportingProductKind, ReportingStepArgs};
|
||||||
use tauri::State;
|
use tauri::{AppHandle, State};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::libdrcr_bridge::get_report;
|
use crate::libdrcr_bridge::get_report;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_tax_summary(state: State<'_, Mutex<AppState>>) -> Result<String, ()> {
|
pub(crate) async fn get_tax_summary(
|
||||||
|
app: AppHandle,
|
||||||
|
state: State<'_, Mutex<AppState>>,
|
||||||
|
) -> Result<String, ()> {
|
||||||
Ok(get_report(
|
Ok(get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -26,22 +26,29 @@ use libdrcr::reporting::dynamic_report::DynamicReport;
|
|||||||
use libdrcr::reporting::generate_report;
|
use libdrcr::reporting::generate_report;
|
||||||
use libdrcr::reporting::types::{
|
use libdrcr::reporting::types::{
|
||||||
BalancesAt, DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
BalancesAt, DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
||||||
ReportingContext, ReportingProduct, ReportingProductId, ReportingProductKind, Transactions,
|
ReportingContext, ReportingProduct, ReportingProductId, ReportingProductKind,
|
||||||
VoidArgs,
|
ReportingStepArgs, Transactions,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::State;
|
use tauri::path::BaseDirectory;
|
||||||
|
use tauri::{AppHandle, Manager, State};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
fn prepare_reporting_context(context: &mut ReportingContext) {
|
fn prepare_reporting_context(context: &mut ReportingContext) {
|
||||||
libdrcr::austax::register_lookup_fns(context);
|
|
||||||
libdrcr::reporting::steps::register_lookup_fns(context);
|
libdrcr::reporting::steps::register_lookup_fns(context);
|
||||||
libdrcr::reporting::builders::register_dynamic_builders(context);
|
libdrcr::reporting::builders::register_dynamic_builders(context);
|
||||||
|
libdrcr::plugin::register_lookup_fns(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_plugins() -> Vec<String> {
|
||||||
|
// FIXME: Dynamically get this
|
||||||
|
vec!["austax.austax".to_string()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_report(
|
pub(crate) async fn get_report(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
target: &ReportingProductId,
|
target: &ReportingProductId,
|
||||||
) -> Box<dyn ReportingProduct> {
|
) -> Box<dyn ReportingProduct> {
|
||||||
@ -54,16 +61,27 @@ 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 mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
let mut context = ReportingContext::new(
|
||||||
|
db_connection,
|
||||||
|
app.path()
|
||||||
|
.resolve("plugins", BaseDirectory::Resource)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
get_plugins(),
|
||||||
|
eofy_date,
|
||||||
|
"$".to_string(),
|
||||||
|
);
|
||||||
prepare_reporting_context(&mut context);
|
prepare_reporting_context(&mut context);
|
||||||
|
|
||||||
// Get dynamic report
|
// Get dynamic report
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
// FIXME: Make this configurable
|
// FIXME: Make this configurable
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
},
|
},
|
||||||
target.clone(),
|
target.clone(),
|
||||||
];
|
];
|
||||||
@ -75,14 +93,16 @@ pub(crate) async fn get_report(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_all_transactions_except_earnings_to_equity(
|
pub(crate) async fn get_all_transactions_except_earnings_to_equity(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
let transactions = get_report(
|
let transactions = get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "AllTransactionsExceptEarningsToEquity",
|
name: "AllTransactionsExceptEarningsToEquity".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
|
date: NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -97,15 +117,17 @@ pub(crate) async fn get_all_transactions_except_earnings_to_equity(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_all_transactions_except_earnings_to_equity_for_account(
|
pub(crate) async fn get_all_transactions_except_earnings_to_equity_for_account(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
account: String,
|
account: String,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
let transactions = get_report(
|
let transactions = get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "AllTransactionsExceptEarningsToEquity",
|
name: "AllTransactionsExceptEarningsToEquity".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
|
date: NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -126,6 +148,7 @@ pub(crate) async fn get_all_transactions_except_earnings_to_equity_for_account(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_balance_sheet(
|
pub(crate) async fn get_balance_sheet(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
dates: Vec<String>,
|
dates: Vec<String>,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
@ -137,11 +160,12 @@ pub(crate) async fn get_balance_sheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_report(
|
Ok(get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: ReportingStepArgs::MultipleDateArgs(MultipleDateArgs {
|
||||||
dates: date_args.clone(),
|
dates: date_args.clone(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -154,6 +178,7 @@ pub(crate) async fn get_balance_sheet(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_income_statement(
|
pub(crate) async fn get_income_statement(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
dates: Vec<(String, String)>,
|
dates: Vec<(String, String)>,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
@ -166,11 +191,12 @@ pub(crate) async fn get_income_statement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_report(
|
Ok(get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateStartDateEndArgs {
|
args: ReportingStepArgs::MultipleDateStartDateEndArgs(MultipleDateStartDateEndArgs {
|
||||||
dates: date_args.clone(),
|
dates: date_args.clone(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -183,17 +209,19 @@ pub(crate) async fn get_income_statement(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_trial_balance(
|
pub(crate) async fn get_trial_balance(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
date: String,
|
date: String,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
let date = NaiveDate::parse_from_str(&date, "%Y-%m-%d").expect("Invalid date");
|
let date = NaiveDate::parse_from_str(&date, "%Y-%m-%d").expect("Invalid date");
|
||||||
|
|
||||||
Ok(get_report(
|
Ok(get_report(
|
||||||
|
app,
|
||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance".to_string(),
|
||||||
kind: ReportingProductKind::DynamicReport,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs { date }),
|
args: ReportingStepArgs::DateArgs(DateArgs { date }),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -211,6 +239,7 @@ struct ValidatedBalanceAssertion {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_validated_balance_assertions(
|
pub(crate) async fn get_validated_balance_assertions(
|
||||||
|
app: AppHandle,
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
) -> Result<String, ()> {
|
) -> Result<String, ()> {
|
||||||
let state = state.lock().await;
|
let state = state.lock().await;
|
||||||
@ -233,21 +262,32 @@ 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 mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
let mut context = ReportingContext::new(
|
||||||
|
db_connection,
|
||||||
|
app.path()
|
||||||
|
.resolve("plugins", BaseDirectory::Resource)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
get_plugins(),
|
||||||
|
eofy_date,
|
||||||
|
"$".to_string(),
|
||||||
|
);
|
||||||
prepare_reporting_context(&mut context);
|
prepare_reporting_context(&mut context);
|
||||||
|
|
||||||
// Get report targets
|
// Get report targets
|
||||||
let mut targets = vec![ReportingProductId {
|
let mut targets = vec![ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax".to_string(),
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
args: Box::new(VoidArgs {}),
|
args: ReportingStepArgs::VoidArgs,
|
||||||
}];
|
}];
|
||||||
for dt in dates {
|
for dt in dates {
|
||||||
// Request ordinary transaction balances at each balance assertion date
|
// Request ordinary transaction balances at each balance assertion date
|
||||||
targets.push(ReportingProductId {
|
targets.push(ReportingProductId {
|
||||||
name: "CombineOrdinaryTransactions",
|
name: "CombineOrdinaryTransactions".to_string(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs { date: dt.date() }),
|
args: ReportingStepArgs::DateArgs(DateArgs { date: dt.date() }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,9 +299,9 @@ pub(crate) async fn get_validated_balance_assertions(
|
|||||||
for balance_assertion in balance_assertions {
|
for balance_assertion in balance_assertions {
|
||||||
let balances_at_date = products
|
let balances_at_date = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "CombineOrdinaryTransactions",
|
name: "CombineOrdinaryTransactions".to_string(),
|
||||||
kind: ReportingProductKind::BalancesAt,
|
kind: ReportingProductKind::BalancesAt,
|
||||||
args: Box::new(DateArgs {
|
args: ReportingStepArgs::DateArgs(DateArgs {
|
||||||
date: balance_assertion.dt.date(),
|
date: balance_assertion.dt.date(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
"targets": "all",
|
"targets": "all",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/icon.png"
|
"icons/icon.png"
|
||||||
]
|
],
|
||||||
|
"resources": {
|
||||||
|
"../libdrcr/plugins/": "plugins/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="literalRow">
|
<template v-if="row">
|
||||||
<template v-if="literalRow.visible">
|
<template v-if="row.visible">
|
||||||
<tr :class="literalRow.bordered ? 'border-y border-gray-300' : null">
|
<tr :class="row.bordered ? 'border-y border-gray-300' : null">
|
||||||
<component :is="literalRow.heading ? 'th' : 'td'" class="py-0.5 pr-1 text-gray-900 text-start" :class="{ 'font-semibold': literalRow.heading }">
|
<component :is="row.heading ? 'th' : 'td'" class="py-0.5 pr-1 text-gray-900 text-start" :class="{ 'font-semibold': row.heading }">
|
||||||
<a :href="literalRow.link as string" class="hover:text-blue-700 hover:underline" v-if="literalRow.link !== null">{{ literalRow.text }}</a>
|
<a :href="row.link as string" class="hover:text-blue-700 hover:underline" v-if="row.link !== null">{{ row.text }}</a>
|
||||||
<template v-if="literalRow.link === null">{{ literalRow.text }}</template>
|
<template v-if="row.link === null">{{ row.text }}</template>
|
||||||
</component>
|
</component>
|
||||||
<component :is="literalRow.heading ? 'th' : 'td'" class="py-0.5 pl-1 text-gray-900 text-end" :class="{ 'font-semibold': literalRow.heading }" v-html="(cell !== 0 || literalRow.heading) ? ppBracketed(cell, literalRow.link ?? undefined) : ''" v-for="cell of literalRow.quantity">
|
<component :is="row.heading ? 'th' : 'td'" class="py-0.5 pl-1 text-gray-900 text-end" :class="{ 'font-semibold': row.heading }" v-html="(cell !== 0 || row.heading) ? ppBracketed(cell, row.link ?? undefined) : ''" v-for="cell of row.quantity">
|
||||||
</component>
|
</component>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@ -48,12 +48,12 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { ppBracketed } from '../display.ts';
|
import { ppBracketed } from '../display.ts';
|
||||||
import { DynamicReportEntry, LiteralRow, Section } from '../reports/base.ts';
|
import { DynamicReportEntry, Row, Section } from '../reports/base.ts';
|
||||||
|
|
||||||
const { entry } = defineProps<{ entry: DynamicReportEntry }>();
|
const { entry } = defineProps<{ entry: DynamicReportEntry }>();
|
||||||
|
|
||||||
const literalRow = computed(function() {
|
const row = computed(function() {
|
||||||
return (entry as { LiteralRow: LiteralRow }).LiteralRow;
|
return (entry as { Row: Row }).Row;
|
||||||
});
|
});
|
||||||
const section = computed(function() {
|
const section = computed(function() {
|
||||||
return (entry as { Section: Section }).Section;
|
return (entry as { Section: Section }).Section;
|
||||||
|
62
src/db.ts
62
src/db.ts
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
DrCr: Web-based double-entry bookkeeping framework
|
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
|
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
|
||||||
@ -67,54 +67,6 @@ export const db = reactive({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function totalBalances(session: ExtendedDatabase): Promise<Map<string, number>> {
|
|
||||||
const resultsRaw: {account: string, quantity: number}[] = await session.select(
|
|
||||||
`-- Get last transaction for each account
|
|
||||||
WITH max_dt_by_account AS (
|
|
||||||
SELECT account, max(dt) AS max_dt
|
|
||||||
FROM joined_transactions
|
|
||||||
GROUP BY account
|
|
||||||
),
|
|
||||||
max_tid_by_account AS (
|
|
||||||
SELECT max_dt_by_account.account, max(transaction_id) AS max_tid
|
|
||||||
FROM max_dt_by_account
|
|
||||||
JOIN joined_transactions ON max_dt_by_account.account = joined_transactions.account AND max_dt_by_account.max_dt = joined_transactions.dt
|
|
||||||
GROUP BY max_dt_by_account.account
|
|
||||||
)
|
|
||||||
-- Get running balance at last transaction for each account
|
|
||||||
SELECT max_tid_by_account.account, running_balance AS quantity
|
|
||||||
FROM max_tid_by_account
|
|
||||||
JOIN transactions_with_running_balances ON max_tid = transactions_with_running_balances.transaction_id AND max_tid_by_account.account = transactions_with_running_balances.account`
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Map(resultsRaw.map((x) => [x.account, x.quantity]));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function totalBalancesAtDate(session: ExtendedDatabase, dt: string): Promise<Map<string, number>> {
|
|
||||||
const resultsRaw: {account: string, quantity: number}[] = await session.select(
|
|
||||||
`-- Get last transaction for each account
|
|
||||||
WITH max_dt_by_account AS (
|
|
||||||
SELECT account, max(dt) AS max_dt
|
|
||||||
FROM joined_transactions
|
|
||||||
WHERE DATE(dt) <= DATE($1)
|
|
||||||
GROUP BY account
|
|
||||||
),
|
|
||||||
max_tid_by_account AS (
|
|
||||||
SELECT max_dt_by_account.account, max(transaction_id) AS max_tid
|
|
||||||
FROM max_dt_by_account
|
|
||||||
JOIN joined_transactions ON max_dt_by_account.account = joined_transactions.account AND max_dt_by_account.max_dt = joined_transactions.dt
|
|
||||||
GROUP BY max_dt_by_account.account
|
|
||||||
)
|
|
||||||
-- Get running balance at last transaction for each account
|
|
||||||
SELECT max_tid_by_account.account, running_balance AS quantity
|
|
||||||
FROM max_tid_by_account
|
|
||||||
JOIN transactions_with_running_balances ON max_tid = transactions_with_running_balances.transaction_id AND max_tid_by_account.account = transactions_with_running_balances.account`,
|
|
||||||
[dt]
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Map(resultsRaw.map((x) => [x.account, x.quantity]));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function joinedToTransactions(joinedTransactionPostings: JoinedTransactionPosting[]): Transaction[] {
|
export function joinedToTransactions(joinedTransactionPostings: JoinedTransactionPosting[]): Transaction[] {
|
||||||
// Group postings into transactions
|
// Group postings into transactions
|
||||||
const transactions: Transaction[] = [];
|
const transactions: Transaction[] = [];
|
||||||
@ -143,18 +95,6 @@ export function joinedToTransactions(joinedTransactionPostings: JoinedTransactio
|
|||||||
return transactions;
|
return transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAccountsForKind(session: ExtendedDatabase, kind: string): Promise<string[]> {
|
|
||||||
const rawAccountsForKind: {account: string}[] = await session.select(
|
|
||||||
`SELECT account
|
|
||||||
FROM account_configurations
|
|
||||||
WHERE kind = $1
|
|
||||||
ORDER BY account`,
|
|
||||||
[kind]
|
|
||||||
);
|
|
||||||
const accountsForKind = rawAccountsForKind.map((a) => a.account);
|
|
||||||
return accountsForKind;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serialiseAmount(quantity: number, commodity: string): string {
|
export function serialiseAmount(quantity: number, commodity: string): string {
|
||||||
// Pretty print the amount for an editable input
|
// Pretty print the amount for an editable input
|
||||||
if (quantity < 0) {
|
if (quantity < 0) {
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
import { ExclamationCircleIcon } from '@heroicons/vue/20/solid';
|
import { ExclamationCircleIcon } from '@heroicons/vue/20/solid';
|
||||||
|
|
||||||
import { DynamicReport, LiteralRow, reportEntryById } from './base.ts';
|
import { DynamicReport, Row, reportEntryById } from './base.ts';
|
||||||
import { db } from '../db.ts';
|
import { db } from '../db.ts';
|
||||||
import DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
import DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
||||||
|
|
||||||
@ -118,9 +118,9 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalAssets = (reportEntryById(report.value, 'total_assets') as { LiteralRow: LiteralRow }).LiteralRow.quantity;
|
const totalAssets = (reportEntryById(report.value, 'total_assets') as { Row: Row }).Row.quantity;
|
||||||
const totalLiabilities = (reportEntryById(report.value, 'total_liabilities') as { LiteralRow: LiteralRow }).LiteralRow.quantity;
|
const totalLiabilities = (reportEntryById(report.value, 'total_liabilities') as { Row: Row }).Row.quantity;
|
||||||
const totalEquity = (reportEntryById(report.value, 'total_equity') as { LiteralRow: LiteralRow }).LiteralRow.quantity;
|
const totalEquity = (reportEntryById(report.value, 'total_equity') as { Row: Row }).Row.quantity;
|
||||||
|
|
||||||
let doesBalance = true;
|
let doesBalance = true;
|
||||||
for (let column = 0; column < report.value.columns.length; column++) {
|
for (let column = 0; column < report.value.columns.length; column++) {
|
||||||
|
@ -24,7 +24,7 @@ export interface DynamicReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// serde_json serialises an enum like this
|
// serde_json serialises an enum like this
|
||||||
export type DynamicReportEntry = { Section: Section } | { LiteralRow: LiteralRow } | 'Spacer';
|
export type DynamicReportEntry = { Section: Section } | { Row: Row } | 'Spacer';
|
||||||
|
|
||||||
export interface Section {
|
export interface Section {
|
||||||
text: string;
|
text: string;
|
||||||
@ -34,7 +34,7 @@ export interface Section {
|
|||||||
entries: DynamicReportEntry[];
|
entries: DynamicReportEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LiteralRow {
|
export interface Row {
|
||||||
text: string;
|
text: string;
|
||||||
quantity: number[];
|
quantity: number[];
|
||||||
id: string;
|
id: string;
|
||||||
@ -55,8 +55,8 @@ export function reportEntryById(report: DynamicReport | Section, id: string): Dy
|
|||||||
if (result !== null) {
|
if (result !== null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} else if ((entry as { LiteralRow: LiteralRow }).LiteralRow) {
|
} else if ((entry as { Row: Row }).Row) {
|
||||||
if ((entry as { LiteralRow: LiteralRow }).LiteralRow.id === id) {
|
if ((entry as { Row: Row }).Row.id === id) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user