Refactor dynamic reporting API
Change from a declarative style to an imperative style Previous declarative style was not good fit for Rust, as the borrow checker does not understand the flat data structure
This commit is contained in:
parent
4f845eaaea
commit
aefe5a351c
@ -30,8 +30,7 @@ use crate::account_config::kinds_for_account;
|
||||
use crate::model::transaction::{Posting, Transaction, TransactionWithPostings};
|
||||
use crate::reporting::calculator::ReportingGraphDependencies;
|
||||
use crate::reporting::dynamic_report::{
|
||||
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
||||
CalculatableSection, CalculatedRow, DynamicReport, LiteralRow,
|
||||
entries_for_kind, DynamicReport, DynamicReportEntry, Row, Section,
|
||||
};
|
||||
use crate::reporting::executor::ReportingExecutionError;
|
||||
use crate::reporting::steps::AllTransactionsExceptEarningsToEquityBalances;
|
||||
@ -44,6 +43,23 @@ use crate::util::sofy_from_eofy;
|
||||
use crate::{QuantityInt, INCOME_TAX, INCOME_TAX_CONTROL};
|
||||
|
||||
// Constants and tax calculations
|
||||
#[rustfmt::skip]
|
||||
const INCOME_TYPES: &[(&str, &str, &str)] = &[
|
||||
("income1", "Salary or wages", "1"),
|
||||
("income5", "Australian Government allowances and payments", "5"),
|
||||
("income10", "Gross interest", "10"),
|
||||
("income13", "Partnerships and trusts", "13"),
|
||||
("income24", "Other income", "24"),
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const DEDUCTION_TYPES: &[(&str, &str, &str)] = &[
|
||||
("d2", "Work-related travel expenses", "D2"),
|
||||
("d4", "Work-related self-education expenses", "D4"),
|
||||
("d5", "Other work-related expenses", "D5"),
|
||||
("d9", "Gifts or donations", "D9"),
|
||||
];
|
||||
|
||||
fn get_grossedup_rfb(taxable_value: QuantityInt) -> QuantityInt {
|
||||
// FIXME: May vary from year to year
|
||||
((taxable_value as f64) * 2.0802) as QuantityInt
|
||||
@ -191,7 +207,156 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
let kinds_for_account =
|
||||
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
|
||||
.iter()
|
||||
.filter(|(acc, _)| {
|
||||
@ -202,543 +367,37 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
})
|
||||
.map(|(_, bal)| *bal)
|
||||
.sum();
|
||||
let _rfb_grossedup = get_grossedup_rfb(rfb_taxable);
|
||||
|
||||
// Generate tax summary report
|
||||
let report = CalculatableDynamicReport::new(
|
||||
"Tax summary".to_string(),
|
||||
vec!["$".to_string()],
|
||||
vec![
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Salary or wages (1)".to_string(),
|
||||
Some("income1".to_string()),
|
||||
true,
|
||||
true,
|
||||
{
|
||||
let mut entries = entries_for_kind_floor(
|
||||
"austax.income1",
|
||||
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,
|
||||
},
|
||||
}),
|
||||
],
|
||||
// Base income tax row
|
||||
let tax_base = get_base_income_tax(net_taxable);
|
||||
report.entries.push(
|
||||
Row {
|
||||
text: "Base income tax".to_string(),
|
||||
quantity: vec![tax_base],
|
||||
id: Some("tax_base".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: false,
|
||||
bordered: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let mut report: DynamicReport = report.calculate();
|
||||
report.auto_hide();
|
||||
|
||||
let total_tax = report.quantity_for_id("total_tax").unwrap()[0];
|
||||
// Total income tax row
|
||||
let tax_total = tax_base;
|
||||
report.entries.push(
|
||||
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
|
||||
let transactions = Transactions {
|
||||
@ -759,18 +418,18 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
transaction_id: None,
|
||||
description: None,
|
||||
account: INCOME_TAX.to_string(),
|
||||
quantity: total_tax,
|
||||
quantity: tax_total,
|
||||
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||
quantity_ascost: Some(total_tax),
|
||||
quantity_ascost: Some(tax_total),
|
||||
},
|
||||
Posting {
|
||||
id: None,
|
||||
transaction_id: None,
|
||||
description: None,
|
||||
account: INCOME_TAX_CONTROL.to_string(),
|
||||
quantity: -total_tax,
|
||||
quantity: -tax_total,
|
||||
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||
quantity_ascost: Some(total_tax),
|
||||
quantity_ascost: Some(tax_total),
|
||||
},
|
||||
],
|
||||
}],
|
||||
@ -805,10 +464,10 @@ fn entries_for_kind_floor(
|
||||
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||
floor: QuantityInt,
|
||||
) -> Vec<CalculatableDynamicReportEntry> {
|
||||
) -> Vec<DynamicReportEntry> {
|
||||
let mut entries_for_kind = entries_for_kind(kind, invert, balances, kinds_for_account);
|
||||
entries_for_kind.iter_mut().for_each(|e| match e {
|
||||
CalculatableDynamicReportEntry::LiteralRow(row) => row
|
||||
DynamicReportEntry::Row(row) => row
|
||||
.quantity
|
||||
.iter_mut()
|
||||
.for_each(|v| *v = (*v / floor) * floor),
|
||||
|
@ -16,9 +16,6 @@
|
||||
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 serde::{Deserialize, Serialize};
|
||||
@ -27,161 +24,7 @@ use crate::QuantityInt;
|
||||
|
||||
use super::types::ReportingProduct;
|
||||
|
||||
/// 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<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
|
||||
/// Represents a dynamically generated report composed of [DynamicReportEntry]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DynamicReport {
|
||||
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
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
|
||||
/// Look up [DynamicReportEntry] 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> {
|
||||
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() {
|
||||
@ -236,25 +55,17 @@ impl DynamicReport {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
if let Some(i) = §ion.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
return Some(entry);
|
||||
}
|
||||
}
|
||||
if let Some(e) = section.by_id(id) {
|
||||
return Some(match e {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
DynamicReportEntry::Section(section.clone())
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
DynamicReportEntry::LiteralRow(row.clone())
|
||||
}
|
||||
DynamicReportEntry::Spacer => DynamicReportEntry::Spacer,
|
||||
});
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
DynamicReportEntry::Row(row) => {
|
||||
if let Some(i) = &row.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
|
||||
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 DynamicReportEntry::LiteralRow(row) = entry {
|
||||
Some(row.quantity)
|
||||
if let DynamicReportEntry::Row(row) = entry {
|
||||
Some(&row.quantity)
|
||||
} else {
|
||||
panic!("Called quantity_for_id on non-LiteralRow");
|
||||
}
|
||||
@ -281,207 +92,36 @@ impl 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),
|
||||
Row(Row),
|
||||
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 From<Section> for DynamicReportEntry {
|
||||
fn from(value: Section) -> Self {
|
||||
DynamicReportEntry::Section(value)
|
||||
}
|
||||
}
|
||||
|
||||
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: &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
|
||||
impl From<Row> for DynamicReportEntry {
|
||||
fn from(value: Row) -> Self {
|
||||
DynamicReportEntry::Row(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Section {
|
||||
pub text: String,
|
||||
pub text: Option<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> {
|
||||
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() {
|
||||
@ -489,17 +129,17 @@ impl Section {
|
||||
DynamicReportEntry::Section(section) => {
|
||||
if let Some(i) = §ion.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
return Some(entry);
|
||||
}
|
||||
}
|
||||
if let Some(e) = section.by_id(id) {
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
DynamicReportEntry::Row(row) => {
|
||||
if let Some(i) = &row.id {
|
||||
if i == id {
|
||||
return Some(entry.clone());
|
||||
return Some(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -511,7 +151,7 @@ impl 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()];
|
||||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
@ -520,7 +160,7 @@ impl Section {
|
||||
subtotals[col_idx] += subtotal;
|
||||
}
|
||||
}
|
||||
DynamicReportEntry::LiteralRow(row) => {
|
||||
DynamicReportEntry::Row(row) => {
|
||||
for (col_idx, subtotal) in row.quantity.iter().enumerate() {
|
||||
subtotals[col_idx] += subtotal;
|
||||
}
|
||||
@ -533,48 +173,22 @@ impl Section {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct LiteralRow {
|
||||
pub struct Row {
|
||||
pub text: String,
|
||||
pub quantity: Vec<QuantityInt>,
|
||||
pub id: Option<String>,
|
||||
pub visible: bool,
|
||||
pub auto_hide: bool,
|
||||
pub link: Option<String>,
|
||||
pub heading: 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(
|
||||
kind: &str,
|
||||
invert: bool,
|
||||
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||
) -> Vec<CalculatableDynamicReportEntry> {
|
||||
) -> Vec<DynamicReportEntry> {
|
||||
// Get accounts of specified kind
|
||||
let mut accounts = kinds_for_account
|
||||
.iter()
|
||||
@ -596,6 +210,11 @@ pub fn entries_for_kind(
|
||||
.map(|b| b.get(account).unwrap_or(&0) * if invert { -1 } else { 1 })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Do not show if all quantities are zero
|
||||
if quantities.iter().all(|q| *q == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some exceptions for the link
|
||||
let link;
|
||||
if account == crate::CURRENT_YEAR_EARNINGS {
|
||||
@ -606,17 +225,16 @@ pub fn entries_for_kind(
|
||||
link = Some(format!("/transactions/{}", account));
|
||||
}
|
||||
|
||||
let entry = LiteralRow {
|
||||
let entry = Row {
|
||||
text: account.to_string(),
|
||||
quantity: quantities,
|
||||
id: None,
|
||||
visible: true,
|
||||
auto_hide: true,
|
||||
link,
|
||||
heading: false,
|
||||
bordered: false,
|
||||
};
|
||||
entries.push(CalculatableDynamicReportEntry::LiteralRow(entry));
|
||||
entries.push(entry.into());
|
||||
}
|
||||
|
||||
entries
|
||||
|
@ -35,8 +35,7 @@ use crate::QuantityInt;
|
||||
|
||||
use super::calculator::ReportingGraphDependencies;
|
||||
use super::dynamic_report::{
|
||||
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
||||
CalculatableSection, CalculatedRow, DynamicReport, DynamicReportEntry, LiteralRow,
|
||||
entries_for_kind, DynamicReport, DynamicReportEntry, Row, Section,
|
||||
};
|
||||
use super::executor::ReportingExecutionError;
|
||||
use super::types::{
|
||||
@ -457,92 +456,79 @@ impl ReportingStep for BalanceSheet {
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Init report
|
||||
let report = CalculatableDynamicReport::new(
|
||||
let mut report = DynamicReport::new(
|
||||
"Balance sheet".to_string(),
|
||||
self.args.dates.iter().map(|d| d.date.to_string()).collect(),
|
||||
vec![
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Assets".to_string(),
|
||||
Some("assets".to_string()),
|
||||
true,
|
||||
false,
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.asset", false, &balances, &kinds_for_account);
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total assets".to_string(),
|
||||
quantity: report.subtotal_for_id("assets").unwrap(),
|
||||
id: Some("total_assets".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Liabilities".to_string(),
|
||||
Some("liabilities".to_string()),
|
||||
true,
|
||||
false,
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.liability", true, &balances, &kinds_for_account);
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total liabilities".to_string(),
|
||||
quantity: report.subtotal_for_id("liabilities").unwrap(),
|
||||
id: Some("total_liabilities".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Equity".to_string(),
|
||||
Some("equity".to_string()),
|
||||
true,
|
||||
false,
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.equity", true, &balances, &kinds_for_account);
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total equity".to_string(),
|
||||
quantity: report.subtotal_for_id("equity").unwrap(),
|
||||
id: Some("total_equity".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
],
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut report = report.calculate();
|
||||
report.auto_hide();
|
||||
// Add assets section
|
||||
let mut assets = Section {
|
||||
text: Some("Assets".to_string()),
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: entries_for_kind("drcr.asset", false, &balances, &kinds_for_account),
|
||||
};
|
||||
let total_assets = assets.subtotal(&report);
|
||||
assets.entries.push(
|
||||
Row {
|
||||
text: "Total assets".to_string(),
|
||||
quantity: total_assets,
|
||||
id: Some("total_assets".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
report.entries.push(assets.into());
|
||||
report.entries.push(DynamicReportEntry::Spacer);
|
||||
|
||||
// Add liabilities section
|
||||
let mut liabilities = Section {
|
||||
text: Some("Liabilities".to_string()),
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: entries_for_kind("drcr.liability", true, &balances, &kinds_for_account),
|
||||
};
|
||||
let total_liabilities = liabilities.subtotal(&report);
|
||||
liabilities.entries.push(
|
||||
Row {
|
||||
text: "Total liabilities".to_string(),
|
||||
quantity: total_liabilities,
|
||||
id: Some("total_liabilities".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
report.entries.push(liabilities.into());
|
||||
report.entries.push(DynamicReportEntry::Spacer);
|
||||
|
||||
// Add equity section
|
||||
let mut equity = Section {
|
||||
text: Some("Equity".to_string()),
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: entries_for_kind("drcr.equity", true, &balances, &kinds_for_account),
|
||||
};
|
||||
let total_equity = equity.subtotal(&report);
|
||||
equity.entries.push(
|
||||
Row {
|
||||
text: "Total equity".to_string(),
|
||||
quantity: total_equity,
|
||||
id: Some("total_equity".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
report.entries.push(equity.into());
|
||||
|
||||
// Store the result
|
||||
let mut result = ReportingProducts::new();
|
||||
@ -1089,89 +1075,80 @@ impl ReportingStep for IncomeStatement {
|
||||
kinds_for_account(context.db_connection.get_account_configurations().await);
|
||||
|
||||
// Init report
|
||||
let report = CalculatableDynamicReport::new(
|
||||
let mut report = DynamicReport::new(
|
||||
"Income statement".to_string(),
|
||||
self.args
|
||||
.dates
|
||||
.iter()
|
||||
.map(|d| d.date_end.to_string())
|
||||
.collect(),
|
||||
vec![
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Income".to_string(),
|
||||
Some("income".to_string()),
|
||||
true,
|
||||
false,
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.income", true, &balances, &kinds_for_account);
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total income".to_string(),
|
||||
quantity: report.subtotal_for_id("income").unwrap(),
|
||||
id: Some("total_income".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatableSection(CalculatableSection::new(
|
||||
"Expenses".to_string(),
|
||||
Some("expenses".to_string()),
|
||||
true,
|
||||
false,
|
||||
{
|
||||
let mut entries =
|
||||
entries_for_kind("drcr.expense", false, &balances, &kinds_for_account);
|
||||
entries.push(CalculatableDynamicReportEntry::CalculatedRow(
|
||||
CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Total expenses".to_string(),
|
||||
quantity: report.subtotal_for_id("expenses").unwrap(),
|
||||
id: Some("total_expenses".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
},
|
||||
));
|
||||
entries
|
||||
},
|
||||
)),
|
||||
CalculatableDynamicReportEntry::Spacer,
|
||||
CalculatableDynamicReportEntry::CalculatedRow(CalculatedRow {
|
||||
calculate_fn: |report| LiteralRow {
|
||||
text: "Net surplus (deficit)".to_string(),
|
||||
quantity: report
|
||||
.quantity_for_id("total_income")
|
||||
.unwrap() // Get total income row
|
||||
.iter()
|
||||
.zip(report.quantity_for_id("total_expenses").unwrap().iter()) // Zip with total expenses row
|
||||
.map(|(i, e)| i - e) // Compute net surplus
|
||||
.collect(),
|
||||
id: Some("net_surplus".to_string()),
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut report = report.calculate();
|
||||
report.auto_hide();
|
||||
// Add income section
|
||||
let mut income = Section {
|
||||
text: Some("Income".to_string()),
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: entries_for_kind("drcr.income", true, &balances, &kinds_for_account),
|
||||
};
|
||||
let total_income = income.subtotal(&report);
|
||||
income.entries.push(
|
||||
Row {
|
||||
text: "Total income".to_string(),
|
||||
quantity: total_income.clone(),
|
||||
id: Some("total_income".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
report.entries.push(income.into());
|
||||
report.entries.push(DynamicReportEntry::Spacer);
|
||||
|
||||
// Add expenses section
|
||||
let mut expenses = Section {
|
||||
text: Some("Expenses".to_string()),
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: entries_for_kind("drcr.expense", false, &balances, &kinds_for_account),
|
||||
};
|
||||
let total_expenses = expenses.subtotal(&report);
|
||||
expenses.entries.push(
|
||||
Row {
|
||||
text: "Total expenses".to_string(),
|
||||
quantity: total_expenses.clone(),
|
||||
id: Some("total_expenses".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
report.entries.push(expenses.into());
|
||||
report.entries.push(DynamicReportEntry::Spacer);
|
||||
|
||||
// Add net surplus (deficit) row
|
||||
let net_surplus = total_income
|
||||
.into_iter()
|
||||
.zip(total_expenses.into_iter())
|
||||
.map(|(i, e)| i - e)
|
||||
.collect();
|
||||
report.entries.push(
|
||||
Row {
|
||||
text: "Net surplus (deficit)".to_string(),
|
||||
quantity: net_surplus,
|
||||
id: Some("net_surplus".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
// Store the result
|
||||
let mut result = ReportingProducts::new();
|
||||
@ -1512,62 +1489,64 @@ impl ReportingStep for TrialBalance {
|
||||
let mut accounts = balances.keys().collect::<Vec<_>>();
|
||||
accounts.sort();
|
||||
|
||||
// Get total debits and credits
|
||||
let total_dr = balances.values().filter(|b| **b >= 0).sum::<i64>();
|
||||
let total_cr = -balances.values().filter(|b| **b < 0).sum::<i64>();
|
||||
|
||||
// Init report
|
||||
let mut report = DynamicReport::new(
|
||||
"Trial balance".to_string(),
|
||||
vec!["Dr".to_string(), "Cr".to_string()],
|
||||
{
|
||||
let mut entries = Vec::new();
|
||||
let mut report = DynamicReport {
|
||||
title: "Trial balance".to_string(),
|
||||
columns: vec!["Dr".to_string(), "Cr".to_string()],
|
||||
entries: Vec::new(),
|
||||
};
|
||||
|
||||
// Entry for each account
|
||||
for account in accounts {
|
||||
entries.push(DynamicReportEntry::LiteralRow(LiteralRow {
|
||||
text: account.clone(),
|
||||
quantity: vec![
|
||||
// Dr cell
|
||||
if balances[account] >= 0 {
|
||||
balances[account]
|
||||
} else {
|
||||
0
|
||||
},
|
||||
// Cr cell
|
||||
if balances[account] < 0 {
|
||||
-balances[account]
|
||||
} else {
|
||||
0
|
||||
},
|
||||
],
|
||||
id: None,
|
||||
visible: true,
|
||||
auto_hide: true,
|
||||
link: Some(format!("/transactions/{}", account)),
|
||||
heading: false,
|
||||
bordered: false,
|
||||
}));
|
||||
}
|
||||
|
||||
// Total row
|
||||
entries.push(DynamicReportEntry::LiteralRow(LiteralRow {
|
||||
text: "Totals".to_string(),
|
||||
quantity: vec![total_dr, total_cr],
|
||||
id: Some("totals".to_string()),
|
||||
// Add entry for each account
|
||||
let mut section = Section {
|
||||
text: None,
|
||||
id: None,
|
||||
visible: true,
|
||||
entries: Vec::new(),
|
||||
};
|
||||
for account in accounts {
|
||||
section.entries.push(
|
||||
Row {
|
||||
text: account.clone(),
|
||||
quantity: vec![
|
||||
// Dr cell
|
||||
if balances[account] >= 0 {
|
||||
balances[account]
|
||||
} else {
|
||||
0
|
||||
},
|
||||
// Cr cell
|
||||
if balances[account] < 0 {
|
||||
-balances[account]
|
||||
} else {
|
||||
0
|
||||
},
|
||||
],
|
||||
id: None,
|
||||
visible: true,
|
||||
auto_hide: false,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}));
|
||||
link: Some(format!("/transactions/{}", account)),
|
||||
heading: false,
|
||||
bordered: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let totals_row = section.subtotal(&report);
|
||||
report.entries.push(section.into());
|
||||
|
||||
entries
|
||||
},
|
||||
// Add total row
|
||||
report.entries.push(
|
||||
Row {
|
||||
text: "Totals".to_string(),
|
||||
quantity: totals_row,
|
||||
id: Some("totals".to_string()),
|
||||
visible: true,
|
||||
link: None,
|
||||
heading: true,
|
||||
bordered: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
report.auto_hide();
|
||||
|
||||
// Store result
|
||||
let mut result = ReportingProducts::new();
|
||||
result.insert(
|
||||
|
@ -18,14 +18,14 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<template v-if="literalRow">
|
||||
<template v-if="literalRow.visible">
|
||||
<tr :class="literalRow.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 }">
|
||||
<a :href="literalRow.link as string" class="hover:text-blue-700 hover:underline" v-if="literalRow.link !== null">{{ literalRow.text }}</a>
|
||||
<template v-if="literalRow.link === null">{{ literalRow.text }}</template>
|
||||
<template v-if="row">
|
||||
<template v-if="row.visible">
|
||||
<tr :class="row.bordered ? 'border-y border-gray-300' : null">
|
||||
<component :is="row.heading ? 'th' : 'td'" class="py-0.5 pr-1 text-gray-900 text-start" :class="{ 'font-semibold': row.heading }">
|
||||
<a :href="row.link as string" class="hover:text-blue-700 hover:underline" v-if="row.link !== null">{{ row.text }}</a>
|
||||
<template v-if="row.link === null">{{ row.text }}</template>
|
||||
</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>
|
||||
</tr>
|
||||
</template>
|
||||
@ -48,12 +48,12 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 literalRow = computed(function() {
|
||||
return (entry as { LiteralRow: LiteralRow }).LiteralRow;
|
||||
const row = computed(function() {
|
||||
return (entry as { Row: Row }).Row;
|
||||
});
|
||||
const section = computed(function() {
|
||||
return (entry as { Section: Section }).Section;
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
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 DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
||||
|
||||
@ -118,9 +118,9 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
const totalAssets = (reportEntryById(report.value, 'total_assets') as { LiteralRow: LiteralRow }).LiteralRow.quantity;
|
||||
const totalLiabilities = (reportEntryById(report.value, 'total_liabilities') as { LiteralRow: LiteralRow }).LiteralRow.quantity;
|
||||
const totalEquity = (reportEntryById(report.value, 'total_equity') 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 { Row: Row }).Row.quantity;
|
||||
const totalEquity = (reportEntryById(report.value, 'total_equity') as { Row: Row }).Row.quantity;
|
||||
|
||||
let doesBalance = true;
|
||||
for (let column = 0; column < report.value.columns.length; column++) {
|
||||
|
@ -24,7 +24,7 @@ export interface DynamicReport {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
text: string;
|
||||
@ -34,7 +34,7 @@ export interface Section {
|
||||
entries: DynamicReportEntry[];
|
||||
}
|
||||
|
||||
export interface LiteralRow {
|
||||
export interface Row {
|
||||
text: string;
|
||||
quantity: number[];
|
||||
id: string;
|
||||
@ -55,8 +55,8 @@ export function reportEntryById(report: DynamicReport | Section, id: string): Dy
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
} else if ((entry as { LiteralRow: LiteralRow }).LiteralRow) {
|
||||
if ((entry as { LiteralRow: LiteralRow }).LiteralRow.id === id) {
|
||||
} else if ((entry as { Row: Row }).Row) {
|
||||
if ((entry as { Row: Row }).Row.id === id) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user