Make reporting API async
This commit is contained in:
parent
b111e7023c
commit
148390f030
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -38,6 +38,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
@ -676,6 +687,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
name = "libdrcr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"downcast-rs",
|
||||
"dyn-clone",
|
||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.88"
|
||||
chrono = "0.4.41"
|
||||
downcast-rs = "2.0.1"
|
||||
dyn-clone = "1.0.19"
|
||||
|
43
src/db.rs
43
src/db.rs
@ -17,39 +17,26 @@
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::{cell::RefCell, future::Future};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use sqlx::sqlite::SqliteRow;
|
||||
use sqlx::{Connection, Row, SqliteConnection};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::account_config::AccountConfiguration;
|
||||
use crate::{util::format_date, QuantityInt};
|
||||
|
||||
pub struct DbConnection {
|
||||
sqlx_connection: RefCell<SqliteConnection>,
|
||||
url: String,
|
||||
metadata: DbMetadata,
|
||||
}
|
||||
|
||||
fn run_blocking<F: Future>(future: F) -> F::Output {
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(future)
|
||||
}
|
||||
|
||||
impl DbConnection {
|
||||
/// Connect to the given Sqlite database
|
||||
pub fn connect(url: &str) -> Self {
|
||||
run_blocking(DbConnection::connect_async(url))
|
||||
}
|
||||
|
||||
async fn connect_async(url: &str) -> Self {
|
||||
pub async fn new(url: &str) -> Self {
|
||||
let mut connection = SqliteConnection::connect(url).await.expect("SQL error");
|
||||
let metadata = DbMetadata::from_database(&mut connection).await;
|
||||
|
||||
Self {
|
||||
sqlx_connection: RefCell::new(connection),
|
||||
url: url.to_string(),
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
@ -58,13 +45,15 @@ impl DbConnection {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
/// Get account balances from the database
|
||||
pub fn get_balances(&self, date: NaiveDate) -> HashMap<String, QuantityInt> {
|
||||
run_blocking(self.get_balances_async(date))
|
||||
pub async fn connect(&self) -> SqliteConnection {
|
||||
SqliteConnection::connect(&self.url)
|
||||
.await
|
||||
.expect("SQL error")
|
||||
}
|
||||
|
||||
async fn get_balances_async(&self, date: NaiveDate) -> HashMap<String, QuantityInt> {
|
||||
let mut connection = self.sqlx_connection.borrow_mut();
|
||||
/// Get account balances from the database
|
||||
pub async fn get_balances(&self, date: NaiveDate) -> HashMap<String, QuantityInt> {
|
||||
let mut connection = self.connect().await;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"-- Get last transaction for each account
|
||||
@ -84,7 +73,7 @@ impl DbConnection {
|
||||
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"
|
||||
).bind(format_date(date)).fetch_all(connection.deref_mut()).await.expect("SQL error");
|
||||
).bind(format_date(date)).fetch_all(&mut connection).await.expect("SQL error");
|
||||
|
||||
let mut balances = HashMap::new();
|
||||
for row in rows {
|
||||
@ -95,12 +84,8 @@ impl DbConnection {
|
||||
}
|
||||
|
||||
/// Get account configurations from the database
|
||||
pub fn get_account_configurations(&self) -> Vec<AccountConfiguration> {
|
||||
run_blocking(self.get_account_configurations_async())
|
||||
}
|
||||
|
||||
async fn get_account_configurations_async(&self) -> Vec<AccountConfiguration> {
|
||||
let mut connection = self.sqlx_connection.borrow_mut();
|
||||
pub async fn get_account_configurations(&self) -> Vec<AccountConfiguration> {
|
||||
let mut connection = self.connect().await;
|
||||
|
||||
let mut account_configurations =
|
||||
sqlx::query("SELECT id, account, kind, data FROM account_configurations")
|
||||
@ -110,7 +95,7 @@ impl DbConnection {
|
||||
kind: r.get("kind"),
|
||||
data: r.get("data"),
|
||||
})
|
||||
.fetch_all(connection.deref_mut())
|
||||
.fetch_all(&mut connection)
|
||||
.await
|
||||
.expect("SQL error");
|
||||
|
||||
|
@ -28,11 +28,12 @@ use libdrcr::reporting::types::{
|
||||
ReportingProductKind, VoidArgs,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
const YEAR: i32 = 2023;
|
||||
|
||||
// Connect to database
|
||||
let db_connection = DbConnection::connect("sqlite:drcr_testing.db");
|
||||
let db_connection = DbConnection::new("sqlite:drcr_testing.db").await;
|
||||
|
||||
// Initialise ReportingContext
|
||||
let mut context = ReportingContext::new(
|
||||
@ -85,7 +86,7 @@ fn main() {
|
||||
},
|
||||
];
|
||||
|
||||
let products = generate_report(targets, &context).unwrap();
|
||||
let products = generate_report(targets, &context).await.unwrap();
|
||||
let result = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "AllTransactionsExceptEarningsToEquity",
|
||||
@ -119,7 +120,7 @@ fn main() {
|
||||
},
|
||||
];
|
||||
|
||||
let products = generate_report(targets, &context).unwrap();
|
||||
let products = generate_report(targets, &context).await.unwrap();
|
||||
let result = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "BalanceSheet",
|
||||
|
@ -23,6 +23,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::transaction::update_balances_from_transactions;
|
||||
|
||||
use super::calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies};
|
||||
@ -124,6 +127,7 @@ impl Display for BalancesAtToBalancesBetween {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for BalancesAtToBalancesBetween {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -153,13 +157,15 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get balances at dates
|
||||
let balances_start = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
@ -196,7 +202,8 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
@ -204,8 +211,7 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,6 +291,7 @@ impl Display for GenerateBalances {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for GenerateBalances {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -303,13 +310,15 @@ impl ReportingStep for GenerateBalances {
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get the transactions
|
||||
let transactions = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
@ -328,7 +337,8 @@ impl ReportingStep for GenerateBalances {
|
||||
update_balances_from_transactions(&mut balances.balances, transactions.iter());
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
@ -336,8 +346,7 @@ impl ReportingStep for GenerateBalances {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,6 +450,7 @@ impl Display for UpdateBalancesAt {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for UpdateBalancesAt {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -499,13 +509,15 @@ impl ReportingStep for UpdateBalancesAt {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Look up the parent step, so we can extract the appropriate args
|
||||
let parent_step = steps
|
||||
.iter()
|
||||
@ -566,7 +578,8 @@ impl ReportingStep for UpdateBalancesAt {
|
||||
);
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
@ -574,8 +587,7 @@ impl ReportingStep for UpdateBalancesAt {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,6 +658,7 @@ impl Display for UpdateBalancesBetween {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for UpdateBalancesBetween {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -683,13 +696,15 @@ impl ReportingStep for UpdateBalancesBetween {
|
||||
);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Look up the parent step, so we can extract the appropriate args
|
||||
let parent_step = steps
|
||||
.iter()
|
||||
@ -736,7 +751,8 @@ impl ReportingStep for UpdateBalancesBetween {
|
||||
);
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
@ -744,7 +760,6 @@ impl ReportingStep for UpdateBalancesBetween {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
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;
|
||||
|
||||
@ -25,17 +27,21 @@ use crate::QuantityInt;
|
||||
|
||||
use super::types::{GenericReportingProduct, ReportingProduct};
|
||||
|
||||
/// Represents a dynamically generated report composed of [DynamicReportEntry]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DynamicReport {
|
||||
/// Represents a dynamically generated report composed of [CalculatableDynamicReportEntry]
|
||||
#[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<DynamicReportEntry>>,
|
||||
pub entries: Vec<RefCell<CalculatableDynamicReportEntry>>,
|
||||
}
|
||||
|
||||
impl DynamicReport {
|
||||
pub fn new(title: String, columns: Vec<String>, entries: Vec<DynamicReportEntry>) -> Self {
|
||||
impl CalculatableDynamicReport {
|
||||
pub fn new(
|
||||
title: String,
|
||||
columns: Vec<String>,
|
||||
entries: Vec<CalculatableDynamicReportEntry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
title,
|
||||
columns,
|
||||
@ -43,67 +49,60 @@ 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(|e| match &mut *e.borrow_mut() {
|
||||
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::CalculatedRow(_) => true,
|
||||
DynamicReportEntry::Spacer => true,
|
||||
});
|
||||
}
|
||||
|
||||
/// Recursively calculate all [CalculatedRow] entries
|
||||
pub fn calculate(&mut self) {
|
||||
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 {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
||||
// Clone first, in case calculation needs to take reference to the section
|
||||
let mut updated_section = section.clone();
|
||||
updated_section.calculate(&self);
|
||||
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 = DynamicReportEntry::Section(updated_section);
|
||||
*entry_mut = CalculatableDynamicReportEntry::Section(updated_section.clone());
|
||||
|
||||
calculated_entries.push(DynamicReportEntry::Section(updated_section));
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(_) => (),
|
||||
DynamicReportEntry::CalculatedRow(row) => {
|
||||
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 = DynamicReportEntry::LiteralRow(updated_row);
|
||||
*entry_mut = CalculatableDynamicReportEntry::LiteralRow(updated_row.clone());
|
||||
|
||||
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
||||
}
|
||||
DynamicReportEntry::Spacer => (),
|
||||
CalculatableDynamicReportEntry::Spacer => (),
|
||||
}
|
||||
}
|
||||
|
||||
DynamicReport {
|
||||
title: self.title,
|
||||
columns: self.columns,
|
||||
entries: calculated_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up [DynamicReportEntry] by id
|
||||
/// Look up [CalculatableDynamicReportEntry] by id
|
||||
///
|
||||
/// 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> {
|
||||
/// 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 {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
||||
if let Some(i) = §ion.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
@ -113,15 +112,35 @@ impl DynamicReport {
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::CalculatedRow(_) => (),
|
||||
DynamicReportEntry::Spacer => (),
|
||||
CalculatableDynamicReportEntry::CalculatedRow(_) => (),
|
||||
CalculatableDynamicReportEntry::Spacer => (),
|
||||
},
|
||||
Err(err) => panic!(
|
||||
"Attempt to call by_id on DynamicReportEntry which is mutably borrowed: {}",
|
||||
@ -136,7 +155,7 @@ impl DynamicReport {
|
||||
/// Calculate the subtotals for the [Section] with the given id
|
||||
pub fn subtotal_for_id(&self, id: &str) -> Vec<QuantityInt> {
|
||||
let entry = self.by_id(id).expect("Invalid id");
|
||||
if let DynamicReportEntry::Section(section) = entry {
|
||||
if let CalculatableDynamicReportEntry::CalculatableSection(section) = entry {
|
||||
section.subtotal(&self)
|
||||
} else {
|
||||
panic!("Called subtotal_for_id on non-Section");
|
||||
@ -146,59 +165,26 @@ impl DynamicReport {
|
||||
// Return the quantities for the [LiteralRow] with the given id
|
||||
pub fn quantity_for_id(&self, id: &str) -> Vec<QuantityInt> {
|
||||
let entry = self.by_id(id).expect("Invalid id");
|
||||
if let DynamicReportEntry::LiteralRow(row) = entry {
|
||||
if let CalculatableDynamicReportEntry::LiteralRow(row) = entry {
|
||||
row.quantity
|
||||
} else {
|
||||
panic!("Called quantity_for_id on non-LiteralRow");
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialise the report (as JSON) using serde
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericReportingProduct for DynamicReport {}
|
||||
impl ReportingProduct for DynamicReport {}
|
||||
|
||||
/// Represents a dynamically generated report composed of [DynamicReportEntry], with no [CalculatedRow]s
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum DynamicReportEntry {
|
||||
Section(Section),
|
||||
LiteralRow(LiteralRow),
|
||||
#[serde(skip)]
|
||||
CalculatedRow(CalculatedRow),
|
||||
Spacer,
|
||||
pub struct DynamicReport {
|
||||
pub title: String,
|
||||
pub columns: Vec<String>,
|
||||
pub entries: Vec<DynamicReportEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Section {
|
||||
pub text: String,
|
||||
pub id: Option<String>,
|
||||
pub visible: bool,
|
||||
pub auto_hide: bool,
|
||||
pub entries: Vec<RefCell<DynamicReportEntry>>,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
pub fn new(
|
||||
text: String,
|
||||
id: Option<String>,
|
||||
visible: bool,
|
||||
auto_hide: bool,
|
||||
entries: Vec<DynamicReportEntry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
text,
|
||||
id,
|
||||
visible,
|
||||
auto_hide,
|
||||
entries: entries.into_iter().map(|e| RefCell::new(e)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_hide_children(&mut self) {
|
||||
self.entries.retain(|e| match &mut *e.borrow_mut() {
|
||||
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() {
|
||||
@ -214,59 +200,116 @@ impl Section {
|
||||
true
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::CalculatedRow(_) => true,
|
||||
DynamicReportEntry::Spacer => true,
|
||||
});
|
||||
}
|
||||
|
||||
fn can_auto_hide_self(&self) -> bool {
|
||||
self.auto_hide
|
||||
&& self.entries.iter().all(|e| match &*e.borrow() {
|
||||
DynamicReportEntry::Section(section) => section.can_auto_hide_self(),
|
||||
DynamicReportEntry::LiteralRow(row) => row.can_auto_hide(),
|
||||
DynamicReportEntry::CalculatedRow(_) => false,
|
||||
DynamicReportEntry::Spacer => true,
|
||||
})
|
||||
/// Serialise the report (as JSON) using serde
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericReportingProduct 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)]
|
||||
pub enum DynamicReportEntry {
|
||||
Section(Section),
|
||||
LiteralRow(LiteralRow),
|
||||
Spacer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CalculatableSection {
|
||||
pub text: String,
|
||||
pub id: Option<String>,
|
||||
pub visible: bool,
|
||||
pub auto_hide: bool,
|
||||
pub entries: Vec<RefCell<CalculatableDynamicReportEntry>>,
|
||||
}
|
||||
|
||||
impl CalculatableSection {
|
||||
pub fn new(
|
||||
text: String,
|
||||
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: &DynamicReport) {
|
||||
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 {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
// Clone first, in case calculation needs to take reference to the section
|
||||
let mut updated_section = section.clone();
|
||||
updated_section.calculate(&report);
|
||||
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 = DynamicReportEntry::Section(updated_section);
|
||||
*entry_mut = CalculatableDynamicReportEntry::Section(updated_section.clone());
|
||||
|
||||
calculated_entries.push(DynamicReportEntry::Section(updated_section));
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(_) => (),
|
||||
DynamicReportEntry::CalculatedRow(row) => {
|
||||
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 = DynamicReportEntry::LiteralRow(updated_row);
|
||||
*entry_mut = CalculatableDynamicReportEntry::LiteralRow(updated_row.clone());
|
||||
|
||||
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
||||
}
|
||||
DynamicReportEntry::Spacer => (),
|
||||
CalculatableDynamicReportEntry::Spacer => (),
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
text: self.text.clone(),
|
||||
id: self.id.clone(),
|
||||
visible: self.visible,
|
||||
auto_hide: self.auto_hide,
|
||||
entries: calculated_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up [DynamicReportEntry] by id
|
||||
/// Look up [CalculatableDynamicReportEntry] by id
|
||||
///
|
||||
/// Returns a cloned copy of the [DynamicReportEntry].
|
||||
pub fn by_id(&self, id: &str) -> Option<DynamicReportEntry> {
|
||||
/// 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 {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
CalculatableDynamicReportEntry::CalculatableSection(section) => {
|
||||
if let Some(i) = §ion.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
@ -276,15 +319,16 @@ impl Section {
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
CalculatableDynamicReportEntry::Section(_) => todo!(),
|
||||
CalculatableDynamicReportEntry::LiteralRow(row) => {
|
||||
if let Some(i) = &row.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::CalculatedRow(_) => (),
|
||||
DynamicReportEntry::Spacer => (),
|
||||
CalculatableDynamicReportEntry::CalculatedRow(_) => (),
|
||||
CalculatableDynamicReportEntry::Spacer => (),
|
||||
},
|
||||
Err(err) => panic!(
|
||||
"Attempt to call by_id on DynamicReportEntry which is mutably borrowed: {}",
|
||||
@ -296,11 +340,111 @@ impl Section {
|
||||
None
|
||||
}
|
||||
|
||||
/// Calculate the subtotals for this [Section]
|
||||
pub fn subtotal(&self, report: &DynamicReport) -> Vec<QuantityInt> {
|
||||
/// 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)]
|
||||
pub struct Section {
|
||||
pub text: String,
|
||||
pub id: Option<String>,
|
||||
pub visible: bool,
|
||||
pub auto_hide: bool,
|
||||
pub entries: Vec<DynamicReportEntry>,
|
||||
}
|
||||
|
||||
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
|
||||
///
|
||||
/// 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()
|
||||
// To catch the situation where entry is already mutably borrowed
|
||||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
DynamicReportEntry::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(e);
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
if let Some(i) = &row.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::Spacer => (),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Calculate the subtotals for this [Section]
|
||||
pub fn subtotal(&self, report: &CalculatableDynamicReport) -> Vec<QuantityInt> {
|
||||
let mut subtotals = vec![0; report.columns.len()];
|
||||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
for (col_idx, subtotal) in section.subtotal(report).into_iter().enumerate() {
|
||||
subtotals[col_idx] += subtotal;
|
||||
@ -311,7 +455,6 @@ impl Section {
|
||||
subtotals[col_idx] += subtotal;
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::CalculatedRow(_) => (),
|
||||
DynamicReportEntry::Spacer => (),
|
||||
}
|
||||
}
|
||||
@ -341,7 +484,7 @@ impl LiteralRow {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CalculatedRow {
|
||||
//pub text: String,
|
||||
pub calculate_fn: fn(report: &DynamicReport) -> LiteralRow,
|
||||
pub calculate_fn: fn(report: &CalculatableDynamicReport) -> LiteralRow,
|
||||
//pub id: Option<String>,
|
||||
//pub visible: bool,
|
||||
//pub auto_hide: bool,
|
||||
@ -351,7 +494,7 @@ pub struct CalculatedRow {
|
||||
}
|
||||
|
||||
impl CalculatedRow {
|
||||
fn calculate(&self, report: &DynamicReport) -> LiteralRow {
|
||||
fn calculate(&self, report: &CalculatableDynamicReport) -> LiteralRow {
|
||||
(self.calculate_fn)(report)
|
||||
}
|
||||
}
|
||||
@ -361,7 +504,7 @@ pub fn entries_for_kind(
|
||||
invert: bool,
|
||||
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||
) -> Vec<DynamicReportEntry> {
|
||||
) -> Vec<CalculatableDynamicReportEntry> {
|
||||
// Get accounts of specified kind
|
||||
let mut accounts = kinds_for_account
|
||||
.iter()
|
||||
@ -393,7 +536,7 @@ pub fn entries_for_kind(
|
||||
heading: false,
|
||||
bordered: false,
|
||||
};
|
||||
entries.push(DynamicReportEntry::LiteralRow(entry));
|
||||
entries.push(CalculatableDynamicReportEntry::LiteralRow(entry));
|
||||
}
|
||||
|
||||
entries
|
||||
|
@ -16,23 +16,35 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::{calculator::ReportingGraphDependencies, types::{ReportingContext, ReportingProducts, ReportingStep}};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::{
|
||||
calculator::ReportingGraphDependencies,
|
||||
types::{ReportingContext, ReportingProducts, ReportingStep},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReportingExecutionError {
|
||||
DependencyNotAvailable { message: String }
|
||||
DependencyNotAvailable { message: String },
|
||||
}
|
||||
|
||||
pub fn execute_steps(
|
||||
pub async fn execute_steps(
|
||||
steps: Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: ReportingGraphDependencies,
|
||||
context: &ReportingContext,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let mut products = ReportingProducts::new();
|
||||
let products = RwLock::new(ReportingProducts::new());
|
||||
|
||||
for step in steps.iter() {
|
||||
step.execute(context, &steps, &dependencies, &mut products)?;
|
||||
// Execute the step
|
||||
// TODO: Do this in parallel
|
||||
let mut new_products = step
|
||||
.execute(context, &steps, &dependencies, &products)
|
||||
.await?;
|
||||
|
||||
// Insert the new products
|
||||
products.write().await.append(&mut new_products);
|
||||
}
|
||||
|
||||
Ok(products)
|
||||
Ok(products.into_inner())
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ impl From<ReportingExecutionError> for ReportingError {
|
||||
/// Calculate the steps required to generate the requested [ReportingProductId]s and then execute them
|
||||
///
|
||||
/// Helper function to call [steps_for_targets] followed by [execute_steps].
|
||||
pub fn generate_report(
|
||||
pub async fn generate_report(
|
||||
targets: Vec<ReportingProductId>,
|
||||
context: &ReportingContext,
|
||||
) -> Result<ReportingProducts, ReportingError> {
|
||||
@ -56,7 +56,7 @@ pub fn generate_report(
|
||||
let (sorted_steps, dependencies) = steps_for_targets(targets, context)?;
|
||||
|
||||
// Execute steps
|
||||
let products = execute_steps(sorted_steps, dependencies, context)?;
|
||||
let products = execute_steps(sorted_steps, dependencies, context).await?;
|
||||
|
||||
Ok(products)
|
||||
}
|
||||
|
@ -21,7 +21,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Datelike;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::account_config::kinds_for_account;
|
||||
use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProductId, Transactions};
|
||||
@ -33,7 +35,8 @@ use crate::QuantityInt;
|
||||
|
||||
use super::calculator::ReportingGraphDependencies;
|
||||
use super::dynamic_report::{
|
||||
entries_for_kind, CalculatedRow, DynamicReport, DynamicReportEntry, LiteralRow, Section,
|
||||
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
||||
CalculatableSection, CalculatedRow, LiteralRow,
|
||||
};
|
||||
use super::executor::ReportingExecutionError;
|
||||
use super::types::{
|
||||
@ -105,6 +108,7 @@ impl Display for AllTransactionsExceptEarningsToEquity {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for AllTransactionsExceptEarningsToEquity {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -123,13 +127,15 @@ impl ReportingStep for AllTransactionsExceptEarningsToEquity {
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get all dependencies
|
||||
let step_dependencies = dependencies.dependencies_for_step(&self.id());
|
||||
|
||||
@ -139,7 +145,8 @@ impl ReportingStep for AllTransactionsExceptEarningsToEquity {
|
||||
for (product_id, product) in products.map().iter().rev() {
|
||||
if step_dependencies.iter().any(|d| d.product == *product_id) {
|
||||
// Store the result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: product_kind,
|
||||
@ -147,8 +154,7 @@ impl ReportingStep for AllTransactionsExceptEarningsToEquity {
|
||||
},
|
||||
product.clone(),
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +203,7 @@ impl Display for AllTransactionsIncludingEarningsToEquity {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for AllTransactionsIncludingEarningsToEquity {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -229,13 +236,15 @@ impl ReportingStep for AllTransactionsIncludingEarningsToEquity {
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get opening balances from AllTransactionsExceptEarningsToEquity
|
||||
let opening_balances = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
@ -280,7 +289,8 @@ impl ReportingStep for AllTransactionsIncludingEarningsToEquity {
|
||||
);
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
@ -288,8 +298,7 @@ impl ReportingStep for AllTransactionsIncludingEarningsToEquity {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,6 +335,7 @@ impl Display for BalanceSheet {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for BalanceSheet {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -350,13 +360,15 @@ impl ReportingStep for BalanceSheet {
|
||||
result
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get balances for each period
|
||||
let mut balances: Vec<&HashMap<String, QuantityInt>> = Vec::new();
|
||||
for date_args in self.args.dates.iter() {
|
||||
@ -371,14 +383,14 @@ impl ReportingStep for BalanceSheet {
|
||||
|
||||
// Get names of all balance sheet accounts
|
||||
let kinds_for_account =
|
||||
kinds_for_account(context.db_connection.get_account_configurations());
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Init report
|
||||
let mut report = DynamicReport::new(
|
||||
let report = CalculatableDynamicReport::new(
|
||||
"Balance sheet".to_string(),
|
||||
self.args.dates.iter().map(|d| d.date.to_string()).collect(),
|
||||
vec![
|
||||
DynamicReportEntry::Section(Section::new(
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Assets".to_string(),
|
||||
Some("assets".to_string()),
|
||||
true,
|
||||
@ -386,23 +398,25 @@ impl ReportingStep for BalanceSheet {
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.asset", false, &balances, &kinds_for_account);
|
||||
entries.push(DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total assets".to_string(),
|
||||
quantity: report.subtotal_for_id("assets"),
|
||||
id: Some("total_assets".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total assets".to_string(),
|
||||
quantity: report.subtotal_for_id("assets"),
|
||||
id: Some("total_assets".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
DynamicReportEntry::Spacer,
|
||||
DynamicReportEntry::Section(Section::new(
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Liabilities".to_string(),
|
||||
Some("liabilities".to_string()),
|
||||
true,
|
||||
@ -410,23 +424,25 @@ impl ReportingStep for BalanceSheet {
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.liability", true, &balances, &kinds_for_account);
|
||||
entries.push(DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total liabilities".to_string(),
|
||||
quantity: report.subtotal_for_id("liabilities"),
|
||||
id: Some("total_liabilities".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total liabilities".to_string(),
|
||||
quantity: report.subtotal_for_id("liabilities"),
|
||||
id: Some("total_liabilities".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
DynamicReportEntry::Spacer,
|
||||
DynamicReportEntry::Section(Section::new(
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Equity".to_string(),
|
||||
Some("equity".to_string()),
|
||||
true,
|
||||
@ -434,29 +450,32 @@ impl ReportingStep for BalanceSheet {
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.equity", true, &balances, &kinds_for_account);
|
||||
entries.push(DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total equity".to_string(),
|
||||
quantity: report.subtotal_for_id("equity"),
|
||||
id: Some("total_equity".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total equity".to_string(),
|
||||
quantity: report.subtotal_for_id("equity"),
|
||||
id: Some("total_equity".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
],
|
||||
);
|
||||
|
||||
report.calculate();
|
||||
let mut report = report.calculate();
|
||||
report.auto_hide();
|
||||
|
||||
// Store the result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: "BalanceSheet",
|
||||
kind: ReportingProductKind::Generic,
|
||||
@ -464,8 +483,7 @@ impl ReportingStep for BalanceSheet {
|
||||
},
|
||||
Box::new(report),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,6 +516,7 @@ impl Display for CalculateIncomeTax {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for CalculateIncomeTax {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -540,20 +559,21 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
_products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
eprintln!("Stub: CalculateIncomeTax.execute");
|
||||
|
||||
let transactions = Transactions {
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
@ -561,8 +581,7 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,6 +620,7 @@ impl Display for CombineOrdinaryTransactions {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for CombineOrdinaryTransactions {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -627,13 +647,15 @@ impl ReportingStep for CombineOrdinaryTransactions {
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Sum balances of all dependencies
|
||||
|
||||
let mut balances = BalancesAt {
|
||||
@ -653,7 +675,8 @@ impl ReportingStep for CombineOrdinaryTransactions {
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
@ -661,8 +684,7 @@ impl ReportingStep for CombineOrdinaryTransactions {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,6 +721,7 @@ impl Display for CurrentYearEarningsToEquity {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for CurrentYearEarningsToEquity {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -722,13 +745,14 @@ impl ReportingStep for CurrentYearEarningsToEquity {
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
let eofy_date = get_eofy(&self.args.date, &context.eofy_date);
|
||||
|
||||
// Get balances for this financial year
|
||||
@ -746,7 +770,7 @@ impl ReportingStep for CurrentYearEarningsToEquity {
|
||||
|
||||
// Get income and expense accounts
|
||||
let kinds_for_account =
|
||||
kinds_for_account(context.db_connection.get_account_configurations());
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Transfer income and expense balances to current year earnings
|
||||
let mut transactions = Transactions {
|
||||
@ -789,7 +813,8 @@ impl ReportingStep for CurrentYearEarningsToEquity {
|
||||
}
|
||||
|
||||
// Store product
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
@ -797,8 +822,7 @@ impl ReportingStep for CurrentYearEarningsToEquity {
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -835,6 +859,7 @@ impl Display for DBBalances {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for DBBalances {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -844,19 +869,21 @@ impl ReportingStep for DBBalances {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
_products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
// Get balances from DB
|
||||
let balances = BalancesAt {
|
||||
balances: context.db_connection.get_balances(self.args.date),
|
||||
balances: context.db_connection.get_balances(self.args.date).await,
|
||||
};
|
||||
|
||||
products.insert(
|
||||
// Store result
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
@ -864,8 +891,7 @@ impl ReportingStep for DBBalances {
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -902,6 +928,7 @@ impl Display for IncomeStatement {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for IncomeStatement {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -926,13 +953,15 @@ impl ReportingStep for IncomeStatement {
|
||||
result
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
|
||||
// Get balances for each period
|
||||
let mut balances: Vec<&HashMap<String, QuantityInt>> = Vec::new();
|
||||
for date_args in self.args.dates.iter() {
|
||||
@ -947,10 +976,10 @@ impl ReportingStep for IncomeStatement {
|
||||
|
||||
// Get names of all income statement accounts
|
||||
let kinds_for_account =
|
||||
kinds_for_account(context.db_connection.get_account_configurations());
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Init report
|
||||
let mut report = DynamicReport::new(
|
||||
let report = CalculatableDynamicReport::new(
|
||||
"Income statement".to_string(),
|
||||
self.args
|
||||
.dates
|
||||
@ -958,7 +987,7 @@ impl ReportingStep for IncomeStatement {
|
||||
.map(|d| d.date_end.to_string())
|
||||
.collect(),
|
||||
vec![
|
||||
DynamicReportEntry::Section(Section::new(
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Income".to_string(),
|
||||
Some("income".to_string()),
|
||||
true,
|
||||
@ -966,23 +995,25 @@ impl ReportingStep for IncomeStatement {
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.income", true, &balances, &kinds_for_account);
|
||||
entries.push(DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total income".to_string(),
|
||||
quantity: report.subtotal_for_id("income"),
|
||||
id: Some("total_income".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total income".to_string(),
|
||||
quantity: report.subtotal_for_id("income"),
|
||||
id: Some("total_income".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
DynamicReportEntry::Spacer,
|
||||
DynamicReportEntry::Section(Section::new(
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Expenses".to_string(),
|
||||
Some("expenses".to_string()),
|
||||
true,
|
||||
@ -990,23 +1021,25 @@ impl ReportingStep for IncomeStatement {
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.expense", false, &balances, &kinds_for_account);
|
||||
entries.push(DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total expenses".to_string(),
|
||||
quantity: report.subtotal_for_id("expenses"),
|
||||
id: Some("total_expenses".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total expenses".to_string(),
|
||||
quantity: report.subtotal_for_id("expenses"),
|
||||
id: Some("total_expenses".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
DynamicReportEntry::Spacer,
|
||||
DynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Net surplus (deficit)".to_string(),
|
||||
quantity: report
|
||||
@ -1026,11 +1059,12 @@ impl ReportingStep for IncomeStatement {
|
||||
],
|
||||
);
|
||||
|
||||
report.calculate();
|
||||
let mut report = report.calculate();
|
||||
report.auto_hide();
|
||||
|
||||
// Store the result
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: "IncomeStatement",
|
||||
kind: ReportingProductKind::Generic,
|
||||
@ -1038,8 +1072,7 @@ impl ReportingStep for IncomeStatement {
|
||||
},
|
||||
Box::new(report),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1076,6 +1109,7 @@ impl Display for PostUnreconciledStatementLines {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for PostUnreconciledStatementLines {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -1085,20 +1119,22 @@ impl ReportingStep for PostUnreconciledStatementLines {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
_products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
eprintln!("Stub: PostUnreconciledStatementLines.execute");
|
||||
|
||||
let transactions = Transactions {
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
// Store result
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
@ -1106,8 +1142,7 @@ impl ReportingStep for PostUnreconciledStatementLines {
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1144,6 +1179,7 @@ impl Display for RetainedEarningsToEquity {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReportingStep for RetainedEarningsToEquity {
|
||||
fn id(&self) -> ReportingStepId {
|
||||
ReportingStepId {
|
||||
@ -1167,13 +1203,14 @@ impl ReportingStep for RetainedEarningsToEquity {
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let products = products.read().await;
|
||||
let eofy_date = get_eofy(&self.args.date, &context.eofy_date);
|
||||
let last_eofy_date = eofy_date.with_year(eofy_date.year() - 1).unwrap();
|
||||
|
||||
@ -1191,7 +1228,7 @@ impl ReportingStep for RetainedEarningsToEquity {
|
||||
|
||||
// Get income and expense accounts
|
||||
let kinds_for_account =
|
||||
kinds_for_account(context.db_connection.get_account_configurations());
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Transfer income and expense balances to retained earnings
|
||||
let mut transactions = Transactions {
|
||||
@ -1234,7 +1271,8 @@ impl ReportingStep for RetainedEarningsToEquity {
|
||||
}
|
||||
|
||||
// Store product
|
||||
products.insert(
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
@ -1242,7 +1280,6 @@ impl ReportingStep for RetainedEarningsToEquity {
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDate;
|
||||
use downcast_rs::Downcast;
|
||||
use dyn_clone::DynClone;
|
||||
use dyn_eq::DynEq;
|
||||
use dyn_hash::DynHash;
|
||||
use indexmap::IndexMap;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::db::DbConnection;
|
||||
use crate::transaction::TransactionWithPostings;
|
||||
@ -159,7 +161,7 @@ pub enum ReportingProductKind {
|
||||
}
|
||||
|
||||
/// Represents the result of a [ReportingStep]
|
||||
pub trait ReportingProduct: Debug + Downcast + DynClone {}
|
||||
pub trait ReportingProduct: Debug + Downcast + DynClone + Send + Sync {}
|
||||
|
||||
downcast_rs::impl_downcast!(ReportingProduct);
|
||||
dyn_clone::clone_trait_object!(ReportingProduct);
|
||||
@ -205,14 +207,25 @@ impl ReportingProducts {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [IndexMap]
|
||||
pub fn map(&self) -> &IndexMap<ReportingProductId, Box<dyn ReportingProduct>> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the map
|
||||
///
|
||||
/// See [IndexMap::insert].
|
||||
pub fn insert(&mut self, key: ReportingProductId, value: Box<dyn ReportingProduct>) {
|
||||
self.map.insert(key, value);
|
||||
}
|
||||
|
||||
/// Moves all key-value pairs from `other` into `self`, leaving `other` empty
|
||||
///
|
||||
/// See [IndexMap::append].
|
||||
pub fn append(&mut self, other: &mut ReportingProducts) {
|
||||
self.map.append(&mut other.map);
|
||||
}
|
||||
|
||||
pub fn get_or_err(
|
||||
&self,
|
||||
key: &ReportingProductId,
|
||||
@ -260,7 +273,8 @@ impl Display for ReportingStepId {
|
||||
}
|
||||
|
||||
/// Represents a step in a reporting job
|
||||
pub trait ReportingStep: Debug + Display + Downcast {
|
||||
#[async_trait]
|
||||
pub trait ReportingStep: Debug + Display + Downcast + Send + Sync {
|
||||
/// Get the [ReportingStepId] for this [ReportingStep]
|
||||
fn id(&self) -> ReportingStepId;
|
||||
|
||||
@ -293,14 +307,16 @@ pub trait ReportingStep: Debug + Display + Downcast {
|
||||
}
|
||||
|
||||
/// Called to generate the [ReportingProduct] for this [ReportingStep]
|
||||
///
|
||||
/// Returns a [ReportingProducts] containing (only) the new [ReportingProduct]s.
|
||||
#[allow(unused_variables)]
|
||||
fn execute(
|
||||
async fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
products: &RwLock<ReportingProducts>,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
todo!("{}", self);
|
||||
}
|
||||
}
|
||||
@ -311,7 +327,10 @@ downcast_rs::impl_downcast!(ReportingStep);
|
||||
// REPORTING STEP ARGUMENTS
|
||||
|
||||
/// Represents arguments to a [ReportingStep]
|
||||
pub trait ReportingStepArgs: Debug + Display + Downcast + DynClone + DynEq + DynHash {}
|
||||
pub trait ReportingStepArgs:
|
||||
Debug + Display + Downcast + DynClone + DynEq + DynHash + Send + Sync
|
||||
{
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(ReportingStepArgs);
|
||||
dyn_clone::clone_trait_object!(ReportingStepArgs);
|
||||
|
Loading…
x
Reference in New Issue
Block a user