Compare commits
3 Commits
e8e88e8629
...
4f845eaaea
Author | SHA1 | Date | |
---|---|---|---|
4f845eaaea | |||
315ff158c3 | |||
49dc6bc078 |
823
libdrcr/src/austax/mod.rs
Normal file
823
libdrcr/src/austax/mod.rs
Normal file
@ -0,0 +1,823 @@
|
|||||||
|
/*
|
||||||
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Implements Australian individual income tax calculations
|
||||||
|
|
||||||
|
// TODO: Ideally this would be separated into its own plugin
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use crate::reporting::executor::ReportingExecutionError;
|
||||||
|
use crate::reporting::steps::AllTransactionsExceptEarningsToEquityBalances;
|
||||||
|
use crate::reporting::types::{
|
||||||
|
BalancesBetween, DateStartDateEndArgs, ReportingContext, ReportingProductId,
|
||||||
|
ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId,
|
||||||
|
Transactions, VoidArgs,
|
||||||
|
};
|
||||||
|
use crate::util::sofy_from_eofy;
|
||||||
|
use crate::{QuantityInt, INCOME_TAX, INCOME_TAX_CONTROL};
|
||||||
|
|
||||||
|
// Constants and tax calculations
|
||||||
|
fn get_grossedup_rfb(taxable_value: QuantityInt) -> QuantityInt {
|
||||||
|
// FIXME: May vary from year to year
|
||||||
|
((taxable_value as f64) * 2.0802) as QuantityInt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_base_income_tax(net_taxable: QuantityInt) -> QuantityInt {
|
||||||
|
// FIXME: May vary from year to year
|
||||||
|
if net_taxable <= 18200_00 {
|
||||||
|
0
|
||||||
|
} else if net_taxable <= 45000_00 {
|
||||||
|
(0.16 * (net_taxable - 18200_00) as f64) as QuantityInt
|
||||||
|
} else if net_taxable <= 135000_00 {
|
||||||
|
4288_00 + (0.30 * (net_taxable - 45000_00) as f64) as QuantityInt
|
||||||
|
} else if net_taxable <= 190000_00 {
|
||||||
|
31288_00 + (0.37 * (net_taxable - 135000_00) as f64) as QuantityInt
|
||||||
|
} else {
|
||||||
|
51638_00 + (0.45 * (net_taxable - 190000_00) as f64) as QuantityInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn get_medicare_levy(net_taxable: QuantityInt) -> QuantityInt {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn get_medicare_levy_surcharge(
|
||||||
|
// net_taxable: QuantityInt,
|
||||||
|
// rfb_grossedup: QuantityInt,
|
||||||
|
// ) -> QuantityInt {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Call [ReportingContext::register_lookup_fn] for all steps provided by this module
|
||||||
|
pub fn register_lookup_fns(context: &mut ReportingContext) {
|
||||||
|
CalculateIncomeTax::register_lookup_fn(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates income tax
|
||||||
|
///
|
||||||
|
/// [Transactions] product represents income tax charge for the year.
|
||||||
|
/// [DynamicReport] product represents the tax summary report.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CalculateIncomeTax {}
|
||||||
|
|
||||||
|
impl CalculateIncomeTax {
|
||||||
|
fn register_lookup_fn(context: &mut ReportingContext) {
|
||||||
|
context.register_lookup_fn(
|
||||||
|
"CalculateIncomeTax",
|
||||||
|
&[ReportingProductKind::Transactions],
|
||||||
|
Self::takes_args,
|
||||||
|
Self::from_args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
|
||||||
|
args.is::<VoidArgs>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_args(_args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
|
||||||
|
Box::new(CalculateIncomeTax {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CalculateIncomeTax {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}", self.id()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ReportingStep for CalculateIncomeTax {
|
||||||
|
fn id(&self) -> ReportingStepId {
|
||||||
|
ReportingStepId {
|
||||||
|
name: "CalculateIncomeTax",
|
||||||
|
product_kinds: &[
|
||||||
|
ReportingProductKind::DynamicReport,
|
||||||
|
ReportingProductKind::Transactions,
|
||||||
|
],
|
||||||
|
args: Box::new(VoidArgs {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
||||||
|
// CalculateIncomeTax depends on CombineOrdinaryTransactions
|
||||||
|
vec![ReportingProductId {
|
||||||
|
name: "CombineOrdinaryTransactions",
|
||||||
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
|
args: Box::new(DateStartDateEndArgs {
|
||||||
|
date_start: sofy_from_eofy(context.eofy_date),
|
||||||
|
date_end: context.eofy_date.clone(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_init_graph(
|
||||||
|
&self,
|
||||||
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
dependencies: &mut ReportingGraphDependencies,
|
||||||
|
_context: &ReportingContext,
|
||||||
|
) {
|
||||||
|
for other in steps {
|
||||||
|
if let Some(other) =
|
||||||
|
other.downcast_ref::<AllTransactionsExceptEarningsToEquityBalances>()
|
||||||
|
{
|
||||||
|
// AllTransactionsExceptEarningsToEquity depends on CalculateIncomeTax
|
||||||
|
dependencies.add_dependency(
|
||||||
|
other.id(),
|
||||||
|
ReportingProductId {
|
||||||
|
name: self.id().name,
|
||||||
|
kind: other.product_kinds[0],
|
||||||
|
args: if other.product_kinds[0] == ReportingProductKind::Transactions {
|
||||||
|
Box::new(VoidArgs {})
|
||||||
|
} else {
|
||||||
|
other.id().args
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
context: &ReportingContext,
|
||||||
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
_dependencies: &ReportingGraphDependencies,
|
||||||
|
products: &RwLock<ReportingProducts>,
|
||||||
|
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||||
|
let products = products.read().await;
|
||||||
|
|
||||||
|
// Get balances for current year
|
||||||
|
let balances = &products
|
||||||
|
.get_or_err(&ReportingProductId {
|
||||||
|
name: "CombineOrdinaryTransactions",
|
||||||
|
kind: ReportingProductKind::BalancesBetween,
|
||||||
|
args: Box::new(DateStartDateEndArgs {
|
||||||
|
date_start: sofy_from_eofy(context.eofy_date),
|
||||||
|
date_end: context.eofy_date.clone(),
|
||||||
|
}),
|
||||||
|
})?
|
||||||
|
.downcast_ref::<BalancesBetween>()
|
||||||
|
.unwrap()
|
||||||
|
.balances;
|
||||||
|
|
||||||
|
// Get taxable income and deduction accounts
|
||||||
|
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)
|
||||||
|
let rfb_taxable = balances
|
||||||
|
.iter()
|
||||||
|
.filter(|(acc, _)| {
|
||||||
|
kinds_for_account
|
||||||
|
.get(*acc)
|
||||||
|
.map(|kinds| kinds.iter().any(|k| k == "austax.rfb"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.map(|(_, bal)| *bal)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut report: DynamicReport = report.calculate();
|
||||||
|
report.auto_hide();
|
||||||
|
|
||||||
|
let total_tax = report.quantity_for_id("total_tax").unwrap()[0];
|
||||||
|
|
||||||
|
// Generate income tax transaction
|
||||||
|
let transactions = Transactions {
|
||||||
|
transactions: vec![TransactionWithPostings {
|
||||||
|
transaction: Transaction {
|
||||||
|
id: None,
|
||||||
|
dt: context
|
||||||
|
.db_connection
|
||||||
|
.metadata()
|
||||||
|
.eofy_date
|
||||||
|
.and_hms_opt(0, 0, 0)
|
||||||
|
.unwrap(),
|
||||||
|
description: "Estimated income tax".to_string(),
|
||||||
|
},
|
||||||
|
postings: vec![
|
||||||
|
Posting {
|
||||||
|
id: None,
|
||||||
|
transaction_id: None,
|
||||||
|
description: None,
|
||||||
|
account: INCOME_TAX.to_string(),
|
||||||
|
quantity: total_tax,
|
||||||
|
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||||
|
quantity_ascost: Some(total_tax),
|
||||||
|
},
|
||||||
|
Posting {
|
||||||
|
id: None,
|
||||||
|
transaction_id: None,
|
||||||
|
description: None,
|
||||||
|
account: INCOME_TAX_CONTROL.to_string(),
|
||||||
|
quantity: -total_tax,
|
||||||
|
commodity: context.db_connection.metadata().reporting_commodity.clone(),
|
||||||
|
quantity_ascost: Some(total_tax),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store products
|
||||||
|
let mut result = ReportingProducts::new();
|
||||||
|
result.insert(
|
||||||
|
ReportingProductId {
|
||||||
|
name: self.id().name,
|
||||||
|
kind: ReportingProductKind::Transactions,
|
||||||
|
args: Box::new(VoidArgs {}),
|
||||||
|
},
|
||||||
|
Box::new(transactions),
|
||||||
|
);
|
||||||
|
result.insert(
|
||||||
|
ReportingProductId {
|
||||||
|
name: self.id().name,
|
||||||
|
kind: ReportingProductKind::DynamicReport,
|
||||||
|
args: Box::new(VoidArgs {}),
|
||||||
|
},
|
||||||
|
Box::new(report),
|
||||||
|
);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call [entries_for_kind] then round results down to next multiple of `floor`
|
||||||
|
fn entries_for_kind_floor(
|
||||||
|
kind: &str,
|
||||||
|
invert: bool,
|
||||||
|
balances: &Vec<&HashMap<String, QuantityInt>>,
|
||||||
|
kinds_for_account: &HashMap<String, Vec<String>>,
|
||||||
|
floor: QuantityInt,
|
||||||
|
) -> Vec<CalculatableDynamicReportEntry> {
|
||||||
|
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
|
||||||
|
.quantity
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|v| *v = (*v / floor) * floor),
|
||||||
|
_ => unreachable!(),
|
||||||
|
});
|
||||||
|
entries_for_kind
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floor_quantity(mut quantity: Vec<QuantityInt>, floor: QuantityInt) -> Vec<QuantityInt> {
|
||||||
|
quantity.iter_mut().for_each(|v| *v = (*v / floor) * floor);
|
||||||
|
quantity
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod account_config;
|
pub mod account_config;
|
||||||
|
pub mod austax;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod reporting;
|
pub mod reporting;
|
||||||
@ -11,4 +12,6 @@ pub type QuantityInt = i64;
|
|||||||
// Magic strings
|
// Magic strings
|
||||||
// TODO: Make this configurable
|
// TODO: Make this configurable
|
||||||
pub const CURRENT_YEAR_EARNINGS: &'static str = "Current Year Earnings";
|
pub const CURRENT_YEAR_EARNINGS: &'static str = "Current Year Earnings";
|
||||||
|
pub const INCOME_TAX: &'static str = "Income Tax";
|
||||||
|
pub const INCOME_TAX_CONTROL: &'static str = "Income Tax Control";
|
||||||
pub const RETAINED_EARNINGS: &'static str = "Retained Earnings";
|
pub const RETAINED_EARNINGS: &'static str = "Retained Earnings";
|
||||||
|
@ -20,11 +20,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use libdrcr::db::DbConnection;
|
use libdrcr::db::DbConnection;
|
||||||
use libdrcr::reporting::builders::register_dynamic_builders;
|
|
||||||
use libdrcr::reporting::calculator::{steps_as_graphviz, steps_for_targets};
|
use libdrcr::reporting::calculator::{steps_as_graphviz, steps_for_targets};
|
||||||
use libdrcr::reporting::dynamic_report::DynamicReport;
|
use libdrcr::reporting::dynamic_report::DynamicReport;
|
||||||
use libdrcr::reporting::generate_report;
|
use libdrcr::reporting::generate_report;
|
||||||
use libdrcr::reporting::steps::register_lookup_fns;
|
|
||||||
use libdrcr::reporting::types::{
|
use libdrcr::reporting::types::{
|
||||||
DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
||||||
ReportingContext, ReportingProductId, ReportingProductKind, VoidArgs,
|
ReportingContext, ReportingProductId, ReportingProductKind, VoidArgs,
|
||||||
@ -43,8 +41,9 @@ async fn main() {
|
|||||||
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||||
"$".to_string(),
|
"$".to_string(),
|
||||||
);
|
);
|
||||||
register_lookup_fns(&mut context);
|
libdrcr::reporting::steps::register_lookup_fns(&mut context);
|
||||||
register_dynamic_builders(&mut context);
|
libdrcr::reporting::builders::register_dynamic_builders(&mut context);
|
||||||
|
libdrcr::austax::register_lookup_fns(&mut context);
|
||||||
|
|
||||||
let context = Arc::new(context);
|
let context = Arc::new(context);
|
||||||
|
|
||||||
@ -65,7 +64,7 @@ async fn main() {
|
|||||||
// },
|
// },
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: Box::new(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
@ -74,7 +73,7 @@ async fn main() {
|
|||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateStartDateEndArgs {
|
args: Box::new(MultipleDateStartDateEndArgs {
|
||||||
dates: vec![DateStartDateEndArgs {
|
dates: vec![DateStartDateEndArgs {
|
||||||
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(),
|
||||||
@ -109,6 +108,18 @@ async fn main() {
|
|||||||
let products = generate_report(targets, Arc::clone(&context))
|
let products = generate_report(targets, Arc::clone(&context))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let result = products
|
||||||
|
.get_or_err(&ReportingProductId {
|
||||||
|
name: "CalculateIncomeTax",
|
||||||
|
kind: ReportingProductKind::DynamicReport,
|
||||||
|
args: Box::new(VoidArgs {}),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("Tax summary:");
|
||||||
|
println!("{:?}", result);
|
||||||
|
|
||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "AllTransactionsExceptEarningsToEquity",
|
name: "AllTransactionsExceptEarningsToEquity",
|
||||||
@ -133,7 +144,7 @@ async fn main() {
|
|||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: Box::new(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
@ -148,7 +159,7 @@ async fn main() {
|
|||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: Box::new(MultipleDateArgs {
|
||||||
dates: vec![DateArgs {
|
dates: vec![DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
@ -173,7 +184,7 @@ async fn main() {
|
|||||||
},
|
},
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs {
|
args: Box::new(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
@ -186,7 +197,7 @@ async fn main() {
|
|||||||
let result = products
|
let result = products
|
||||||
.get_or_err(&ReportingProductId {
|
.get_or_err(&ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs {
|
args: Box::new(DateArgs {
|
||||||
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
|
||||||
}),
|
}),
|
||||||
|
@ -155,22 +155,28 @@ impl CalculatableDynamicReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the subtotals for the [Section] with the given id
|
/// Calculate the subtotals for the [Section] with the given id
|
||||||
pub fn subtotal_for_id(&self, id: &str) -> Vec<QuantityInt> {
|
pub fn subtotal_for_id(&self, id: &str) -> Option<Vec<QuantityInt>> {
|
||||||
let entry = self.by_id(id).expect("Invalid id");
|
if let Some(entry) = self.by_id(id) {
|
||||||
if let CalculatableDynamicReportEntry::CalculatableSection(section) = entry {
|
if let CalculatableDynamicReportEntry::CalculatableSection(section) = entry {
|
||||||
section.subtotal(&self)
|
Some(section.subtotal(&self))
|
||||||
|
} else {
|
||||||
|
panic!("Called subtotal_for_id on non-Section");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Called subtotal_for_id on non-Section");
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the quantities for the [LiteralRow] with the given id
|
// Return the quantities for the [LiteralRow] with the given id
|
||||||
pub fn quantity_for_id(&self, id: &str) -> Vec<QuantityInt> {
|
pub fn quantity_for_id(&self, id: &str) -> Option<Vec<QuantityInt>> {
|
||||||
let entry = self.by_id(id).expect("Invalid id");
|
if let Some(entry) = self.by_id(id) {
|
||||||
if let CalculatableDynamicReportEntry::LiteralRow(row) = entry {
|
if let CalculatableDynamicReportEntry::LiteralRow(row) = entry {
|
||||||
row.quantity
|
Some(row.quantity)
|
||||||
|
} else {
|
||||||
|
panic!("Called quantity_for_id on non-LiteralRow");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Called quantity_for_id on non-LiteralRow");
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,6 +224,59 @@ impl DynamicReport {
|
|||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
serde_json::to_string(self).unwrap()
|
serde_json::to_string(self).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up [DynamicReportEntry] by id
|
||||||
|
///
|
||||||
|
/// Returns a cloned copy of the [DynamicReportEntry]. This is necessary because the entry may be within a [Section], and [RefCell] semantics cannot express this type of nested borrow.
|
||||||
|
pub fn by_id(&self, id: &str) -> Option<DynamicReportEntry> {
|
||||||
|
// Manually iterate over self.entries rather than self.entries()
|
||||||
|
// 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(match e {
|
||||||
|
DynamicReportEntry::Section(section) => {
|
||||||
|
DynamicReportEntry::Section(section.clone())
|
||||||
|
}
|
||||||
|
DynamicReportEntry::LiteralRow(row) => {
|
||||||
|
DynamicReportEntry::LiteralRow(row.clone())
|
||||||
|
}
|
||||||
|
DynamicReportEntry::Spacer => DynamicReportEntry::Spacer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DynamicReportEntry::LiteralRow(row) => {
|
||||||
|
if let Some(i) = &row.id {
|
||||||
|
if i == id {
|
||||||
|
return Some(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DynamicReportEntry::Spacer => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 DynamicReportEntry::LiteralRow(row) = entry {
|
||||||
|
Some(row.quantity)
|
||||||
|
} else {
|
||||||
|
panic!("Called quantity_for_id on non-LiteralRow");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingProduct for DynamicReport {}
|
impl ReportingProduct for DynamicReport {}
|
||||||
@ -296,7 +355,9 @@ impl CalculatableSection {
|
|||||||
|
|
||||||
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
calculated_entries.push(DynamicReportEntry::LiteralRow(updated_row));
|
||||||
}
|
}
|
||||||
CalculatableDynamicReportEntry::Spacer => (),
|
CalculatableDynamicReportEntry::Spacer => {
|
||||||
|
calculated_entries.push(DynamicReportEntry::Spacer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ pub fn register_lookup_fns(context: &mut ReportingContext) {
|
|||||||
AllTransactionsExceptEarningsToEquityBalances::register_lookup_fn(context);
|
AllTransactionsExceptEarningsToEquityBalances::register_lookup_fn(context);
|
||||||
AllTransactionsIncludingEarningsToEquity::register_lookup_fn(context);
|
AllTransactionsIncludingEarningsToEquity::register_lookup_fn(context);
|
||||||
BalanceSheet::register_lookup_fn(context);
|
BalanceSheet::register_lookup_fn(context);
|
||||||
CalculateIncomeTax::register_lookup_fn(context);
|
|
||||||
CombineOrdinaryTransactions::register_lookup_fn(context);
|
CombineOrdinaryTransactions::register_lookup_fn(context);
|
||||||
CombineOrdinaryTransactionsBalances::register_lookup_fn(context);
|
CombineOrdinaryTransactionsBalances::register_lookup_fn(context);
|
||||||
CurrentYearEarningsToEquity::register_lookup_fn(context);
|
CurrentYearEarningsToEquity::register_lookup_fn(context);
|
||||||
@ -384,7 +383,7 @@ impl BalanceSheet {
|
|||||||
fn register_lookup_fn(context: &mut ReportingContext) {
|
fn register_lookup_fn(context: &mut ReportingContext) {
|
||||||
context.register_lookup_fn(
|
context.register_lookup_fn(
|
||||||
"BalanceSheet",
|
"BalanceSheet",
|
||||||
&[ReportingProductKind::Generic],
|
&[ReportingProductKind::DynamicReport],
|
||||||
Self::takes_args,
|
Self::takes_args,
|
||||||
Self::from_args,
|
Self::from_args,
|
||||||
);
|
);
|
||||||
@ -412,7 +411,7 @@ impl ReportingStep for BalanceSheet {
|
|||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
product_kinds: &[ReportingProductKind::Generic],
|
product_kinds: &[ReportingProductKind::DynamicReport],
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,7 +473,7 @@ impl ReportingStep for BalanceSheet {
|
|||||||
CalculatedRow {
|
CalculatedRow {
|
||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Total assets".to_string(),
|
text: "Total assets".to_string(),
|
||||||
quantity: report.subtotal_for_id("assets"),
|
quantity: report.subtotal_for_id("assets").unwrap(),
|
||||||
id: Some("total_assets".to_string()),
|
id: Some("total_assets".to_string()),
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: false,
|
auto_hide: false,
|
||||||
@ -500,7 +499,7 @@ impl ReportingStep for BalanceSheet {
|
|||||||
CalculatedRow {
|
CalculatedRow {
|
||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Total liabilities".to_string(),
|
text: "Total liabilities".to_string(),
|
||||||
quantity: report.subtotal_for_id("liabilities"),
|
quantity: report.subtotal_for_id("liabilities").unwrap(),
|
||||||
id: Some("total_liabilities".to_string()),
|
id: Some("total_liabilities".to_string()),
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: false,
|
auto_hide: false,
|
||||||
@ -526,7 +525,7 @@ impl ReportingStep for BalanceSheet {
|
|||||||
CalculatedRow {
|
CalculatedRow {
|
||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Total equity".to_string(),
|
text: "Total equity".to_string(),
|
||||||
quantity: report.subtotal_for_id("equity"),
|
quantity: report.subtotal_for_id("equity").unwrap(),
|
||||||
id: Some("total_equity".to_string()),
|
id: Some("total_equity".to_string()),
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: false,
|
auto_hide: false,
|
||||||
@ -550,7 +549,7 @@ impl ReportingStep for BalanceSheet {
|
|||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(report),
|
Box::new(report),
|
||||||
@ -559,110 +558,6 @@ impl ReportingStep for BalanceSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates income tax
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CalculateIncomeTax {}
|
|
||||||
|
|
||||||
impl CalculateIncomeTax {
|
|
||||||
fn register_lookup_fn(context: &mut ReportingContext) {
|
|
||||||
context.register_lookup_fn(
|
|
||||||
"CalculateIncomeTax",
|
|
||||||
&[ReportingProductKind::Transactions],
|
|
||||||
Self::takes_args,
|
|
||||||
Self::from_args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
|
|
||||||
args.is::<VoidArgs>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_args(_args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
|
|
||||||
Box::new(CalculateIncomeTax {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for CalculateIncomeTax {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}", self.id()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl ReportingStep for CalculateIncomeTax {
|
|
||||||
fn id(&self) -> ReportingStepId {
|
|
||||||
ReportingStepId {
|
|
||||||
name: "CalculateIncomeTax",
|
|
||||||
product_kinds: &[ReportingProductKind::Transactions],
|
|
||||||
args: Box::new(VoidArgs {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
|
||||||
// CalculateIncomeTax depends on CombineOrdinaryTransactions
|
|
||||||
vec![ReportingProductId {
|
|
||||||
name: "CombineOrdinaryTransactions",
|
|
||||||
kind: ReportingProductKind::BalancesBetween,
|
|
||||||
args: Box::new(DateStartDateEndArgs {
|
|
||||||
date_start: sofy_from_eofy(context.eofy_date),
|
|
||||||
date_end: context.eofy_date.clone(),
|
|
||||||
}),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn after_init_graph(
|
|
||||||
&self,
|
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
dependencies: &mut ReportingGraphDependencies,
|
|
||||||
_context: &ReportingContext,
|
|
||||||
) {
|
|
||||||
for other in steps {
|
|
||||||
if let Some(other) =
|
|
||||||
other.downcast_ref::<AllTransactionsExceptEarningsToEquityBalances>()
|
|
||||||
{
|
|
||||||
// AllTransactionsExceptEarningsToEquity depends on CalculateIncomeTax
|
|
||||||
dependencies.add_dependency(
|
|
||||||
other.id(),
|
|
||||||
ReportingProductId {
|
|
||||||
name: self.id().name,
|
|
||||||
kind: other.product_kinds[0],
|
|
||||||
args: if other.product_kinds[0] == ReportingProductKind::Transactions {
|
|
||||||
Box::new(VoidArgs {})
|
|
||||||
} else {
|
|
||||||
other.id().args
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute(
|
|
||||||
&self,
|
|
||||||
_context: &ReportingContext,
|
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
_dependencies: &ReportingGraphDependencies,
|
|
||||||
_products: &RwLock<ReportingProducts>,
|
|
||||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
|
||||||
eprintln!("Stub: CalculateIncomeTax.execute");
|
|
||||||
|
|
||||||
let transactions = Transactions {
|
|
||||||
transactions: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = ReportingProducts::new();
|
|
||||||
result.insert(
|
|
||||||
ReportingProductId {
|
|
||||||
name: self.id().name,
|
|
||||||
kind: ReportingProductKind::Transactions,
|
|
||||||
args: Box::new(VoidArgs {}),
|
|
||||||
},
|
|
||||||
Box::new(transactions),
|
|
||||||
);
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combines all steps producing ordinary transactions (returns transaction list)
|
/// Combines all steps producing ordinary transactions (returns transaction list)
|
||||||
///
|
///
|
||||||
/// By default, these are [DBTransactions] and [PostUnreconciledStatementLines].
|
/// By default, these are [DBTransactions] and [PostUnreconciledStatementLines].
|
||||||
@ -1120,7 +1015,7 @@ impl IncomeStatement {
|
|||||||
fn register_lookup_fn(context: &mut ReportingContext) {
|
fn register_lookup_fn(context: &mut ReportingContext) {
|
||||||
context.register_lookup_fn(
|
context.register_lookup_fn(
|
||||||
"IncomeStatement",
|
"IncomeStatement",
|
||||||
&[ReportingProductKind::Generic],
|
&[ReportingProductKind::DynamicReport],
|
||||||
Self::takes_args,
|
Self::takes_args,
|
||||||
Self::from_args,
|
Self::from_args,
|
||||||
);
|
);
|
||||||
@ -1148,7 +1043,7 @@ impl ReportingStep for IncomeStatement {
|
|||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement",
|
||||||
product_kinds: &[ReportingProductKind::Generic],
|
product_kinds: &[ReportingProductKind::DynamicReport],
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1214,7 +1109,7 @@ impl ReportingStep for IncomeStatement {
|
|||||||
CalculatedRow {
|
CalculatedRow {
|
||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Total income".to_string(),
|
text: "Total income".to_string(),
|
||||||
quantity: report.subtotal_for_id("income"),
|
quantity: report.subtotal_for_id("income").unwrap(),
|
||||||
id: Some("total_income".to_string()),
|
id: Some("total_income".to_string()),
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: false,
|
auto_hide: false,
|
||||||
@ -1240,7 +1135,7 @@ impl ReportingStep for IncomeStatement {
|
|||||||
CalculatedRow {
|
CalculatedRow {
|
||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Total expenses".to_string(),
|
text: "Total expenses".to_string(),
|
||||||
quantity: report.subtotal_for_id("expenses"),
|
quantity: report.subtotal_for_id("expenses").unwrap(),
|
||||||
id: Some("total_expenses".to_string()),
|
id: Some("total_expenses".to_string()),
|
||||||
visible: true,
|
visible: true,
|
||||||
auto_hide: false,
|
auto_hide: false,
|
||||||
@ -1258,9 +1153,10 @@ impl ReportingStep for IncomeStatement {
|
|||||||
calculate_fn: |report| LiteralRow {
|
calculate_fn: |report| LiteralRow {
|
||||||
text: "Net surplus (deficit)".to_string(),
|
text: "Net surplus (deficit)".to_string(),
|
||||||
quantity: report
|
quantity: report
|
||||||
.quantity_for_id("total_income") // Get total income row
|
.quantity_for_id("total_income")
|
||||||
|
.unwrap() // Get total income row
|
||||||
.iter()
|
.iter()
|
||||||
.zip(report.quantity_for_id("total_expenses").iter()) // Zip with total expenses row
|
.zip(report.quantity_for_id("total_expenses").unwrap().iter()) // Zip with total expenses row
|
||||||
.map(|(i, e)| i - e) // Compute net surplus
|
.map(|(i, e)| i - e) // Compute net surplus
|
||||||
.collect(),
|
.collect(),
|
||||||
id: Some("net_surplus".to_string()),
|
id: Some("net_surplus".to_string()),
|
||||||
@ -1282,7 +1178,7 @@ impl ReportingStep for IncomeStatement {
|
|||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(report),
|
Box::new(report),
|
||||||
@ -1546,7 +1442,7 @@ impl TrialBalance {
|
|||||||
fn register_lookup_fn(context: &mut ReportingContext) {
|
fn register_lookup_fn(context: &mut ReportingContext) {
|
||||||
context.register_lookup_fn(
|
context.register_lookup_fn(
|
||||||
"TrialBalance",
|
"TrialBalance",
|
||||||
&[ReportingProductKind::Generic],
|
&[ReportingProductKind::DynamicReport],
|
||||||
Self::takes_args,
|
Self::takes_args,
|
||||||
Self::from_args,
|
Self::from_args,
|
||||||
);
|
);
|
||||||
@ -1574,7 +1470,7 @@ impl ReportingStep for TrialBalance {
|
|||||||
fn id(&self) -> ReportingStepId {
|
fn id(&self) -> ReportingStepId {
|
||||||
ReportingStepId {
|
ReportingStepId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance",
|
||||||
product_kinds: &[ReportingProductKind::Generic],
|
product_kinds: &[ReportingProductKind::DynamicReport],
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1677,7 +1573,7 @@ impl ReportingStep for TrialBalance {
|
|||||||
result.insert(
|
result.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(self.args.clone()),
|
args: Box::new(self.args.clone()),
|
||||||
},
|
},
|
||||||
Box::new(report),
|
Box::new(report),
|
||||||
|
@ -152,12 +152,20 @@ impl Display for ReportingProductId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies a type of [ReportingProduct]
|
/// Identifies a type of [Box]ed [ReportingProduct]
|
||||||
|
///
|
||||||
|
/// See [Box::downcast].
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum ReportingProductKind {
|
pub enum ReportingProductKind {
|
||||||
|
/// The [Box]ed [ReportingProduct] is a [Transactions]
|
||||||
Transactions,
|
Transactions,
|
||||||
|
/// The [Box]ed [ReportingProduct] is a [BalancesAt]
|
||||||
BalancesAt,
|
BalancesAt,
|
||||||
|
/// The [Box]ed [ReportingProduct] is a [BalancesBetween]
|
||||||
BalancesBetween,
|
BalancesBetween,
|
||||||
|
/// The [Box]ed [ReportingProduct] is a [DynamicReport]
|
||||||
|
DynamicReport,
|
||||||
|
/// Unused in libdrcr - available for plugin use
|
||||||
Generic,
|
Generic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mod libdrcr_austax;
|
||||||
mod libdrcr_bridge;
|
mod libdrcr_bridge;
|
||||||
mod sql;
|
mod sql;
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
get_open_filename,
|
get_open_filename,
|
||||||
set_open_filename,
|
set_open_filename,
|
||||||
|
libdrcr_austax::get_tax_summary,
|
||||||
libdrcr_bridge::get_all_transactions_except_earnings_to_equity,
|
libdrcr_bridge::get_all_transactions_except_earnings_to_equity,
|
||||||
libdrcr_bridge::get_all_transactions_except_earnings_to_equity_for_account,
|
libdrcr_bridge::get_all_transactions_except_earnings_to_equity_for_account,
|
||||||
libdrcr_bridge::get_balance_sheet,
|
libdrcr_bridge::get_balance_sheet,
|
||||||
|
41
src-tauri/src/libdrcr_austax.rs
Normal file
41
src-tauri/src/libdrcr_austax.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use libdrcr::reporting::dynamic_report::DynamicReport;
|
||||||
|
use libdrcr::reporting::types::{ReportingProductId, ReportingProductKind, VoidArgs};
|
||||||
|
use tauri::State;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::libdrcr_bridge::get_report;
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn get_tax_summary(state: State<'_, Mutex<AppState>>) -> Result<String, ()> {
|
||||||
|
Ok(get_report(
|
||||||
|
state,
|
||||||
|
&ReportingProductId {
|
||||||
|
name: "CalculateIncomeTax",
|
||||||
|
kind: ReportingProductKind::DynamicReport,
|
||||||
|
args: Box::new(VoidArgs {}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.downcast_ref::<DynamicReport>()
|
||||||
|
.unwrap()
|
||||||
|
.to_json())
|
||||||
|
}
|
@ -22,10 +22,8 @@ use std::sync::Arc;
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use libdrcr::db::DbConnection;
|
use libdrcr::db::DbConnection;
|
||||||
use libdrcr::model::assertions::BalanceAssertion;
|
use libdrcr::model::assertions::BalanceAssertion;
|
||||||
use libdrcr::reporting::builders::register_dynamic_builders;
|
|
||||||
use libdrcr::reporting::dynamic_report::DynamicReport;
|
use libdrcr::reporting::dynamic_report::DynamicReport;
|
||||||
use libdrcr::reporting::generate_report;
|
use libdrcr::reporting::generate_report;
|
||||||
use libdrcr::reporting::steps::register_lookup_fns;
|
|
||||||
use libdrcr::reporting::types::{
|
use libdrcr::reporting::types::{
|
||||||
BalancesAt, DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
BalancesAt, DateArgs, DateStartDateEndArgs, MultipleDateArgs, MultipleDateStartDateEndArgs,
|
||||||
ReportingContext, ReportingProduct, ReportingProductId, ReportingProductKind, Transactions,
|
ReportingContext, ReportingProduct, ReportingProductId, ReportingProductKind, Transactions,
|
||||||
@ -37,7 +35,13 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
async fn get_report(
|
fn prepare_reporting_context(context: &mut ReportingContext) {
|
||||||
|
libdrcr::austax::register_lookup_fns(context);
|
||||||
|
libdrcr::reporting::steps::register_lookup_fns(context);
|
||||||
|
libdrcr::reporting::builders::register_dynamic_builders(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_report(
|
||||||
state: State<'_, Mutex<AppState>>,
|
state: State<'_, Mutex<AppState>>,
|
||||||
target: &ReportingProductId,
|
target: &ReportingProductId,
|
||||||
) -> Box<dyn ReportingProduct> {
|
) -> Box<dyn ReportingProduct> {
|
||||||
@ -51,11 +55,11 @@ async fn get_report(
|
|||||||
// Initialise ReportingContext
|
// Initialise ReportingContext
|
||||||
let eofy_date = db_connection.metadata().eofy_date;
|
let eofy_date = db_connection.metadata().eofy_date;
|
||||||
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
||||||
register_lookup_fns(&mut context);
|
prepare_reporting_context(&mut context);
|
||||||
register_dynamic_builders(&mut context);
|
|
||||||
|
|
||||||
// Get dynamic report
|
// Get dynamic report
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
|
// FIXME: Make this configurable
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: "CalculateIncomeTax",
|
name: "CalculateIncomeTax",
|
||||||
kind: ReportingProductKind::Transactions,
|
kind: ReportingProductKind::Transactions,
|
||||||
@ -136,7 +140,7 @@ pub(crate) async fn get_balance_sheet(
|
|||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "BalanceSheet",
|
name: "BalanceSheet",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateArgs {
|
args: Box::new(MultipleDateArgs {
|
||||||
dates: date_args.clone(),
|
dates: date_args.clone(),
|
||||||
}),
|
}),
|
||||||
@ -165,7 +169,7 @@ pub(crate) async fn get_income_statement(
|
|||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "IncomeStatement",
|
name: "IncomeStatement",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(MultipleDateStartDateEndArgs {
|
args: Box::new(MultipleDateStartDateEndArgs {
|
||||||
dates: date_args.clone(),
|
dates: date_args.clone(),
|
||||||
}),
|
}),
|
||||||
@ -188,7 +192,7 @@ pub(crate) async fn get_trial_balance(
|
|||||||
state,
|
state,
|
||||||
&ReportingProductId {
|
&ReportingProductId {
|
||||||
name: "TrialBalance",
|
name: "TrialBalance",
|
||||||
kind: ReportingProductKind::Generic,
|
kind: ReportingProductKind::DynamicReport,
|
||||||
args: Box::new(DateArgs { date }),
|
args: Box::new(DateArgs { date }),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -230,8 +234,7 @@ pub(crate) async fn get_validated_balance_assertions(
|
|||||||
// Initialise ReportingContext
|
// Initialise ReportingContext
|
||||||
let eofy_date = db_connection.metadata().eofy_date;
|
let eofy_date = db_connection.metadata().eofy_date;
|
||||||
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
|
||||||
register_lookup_fns(&mut context);
|
prepare_reporting_context(&mut context);
|
||||||
register_dynamic_builders(&mut context);
|
|
||||||
|
|
||||||
// Get report targets
|
// Get report targets
|
||||||
let mut targets = vec![ReportingProductId {
|
let mut targets = vec![ReportingProductId {
|
||||||
|
36
src/austax/TaxSummaryReport.vue
Normal file
36
src/austax/TaxSummaryReport.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DynamicReportComponent :report="report" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
||||||
|
import { DynamicReport } from '../reports/base.ts';
|
||||||
|
|
||||||
|
const report = ref(null as DynamicReport | null);
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
report.value = JSON.parse(await invoke('get_tax_summary'));
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
</script>
|
@ -19,21 +19,25 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="literalRow">
|
<template v-if="literalRow">
|
||||||
<tr :class="literalRow.bordered ? 'border-y border-gray-300' : null">
|
<template v-if="literalRow.visible">
|
||||||
<component :is="literalRow.heading ? 'th' : 'td'" class="py-0.5 pr-1 text-gray-900 text-start" :class="{ 'font-semibold': literalRow.heading }">
|
<tr :class="literalRow.bordered ? 'border-y border-gray-300' : null">
|
||||||
<a :href="literalRow.link as string" class="hover:text-blue-700 hover:underline" v-if="literalRow.link !== null">{{ literalRow.text }}</a>
|
<component :is="literalRow.heading ? 'th' : 'td'" class="py-0.5 pr-1 text-gray-900 text-start" :class="{ 'font-semibold': literalRow.heading }">
|
||||||
<template v-if="literalRow.link === null">{{ literalRow.text }}</template>
|
<a :href="literalRow.link as string" class="hover:text-blue-700 hover:underline" v-if="literalRow.link !== null">{{ literalRow.text }}</a>
|
||||||
</component>
|
<template v-if="literalRow.link === null">{{ literalRow.text }}</template>
|
||||||
<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>
|
||||||
</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">
|
||||||
</tr>
|
</component>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="section">
|
<template v-if="section">
|
||||||
<tr v-if="section.text !== null">
|
<template v-if="section.visible">
|
||||||
<th class="py-0.5 pr-1 text-gray-900 font-semibold text-start">{{ section.text }}</th>
|
<tr v-if="section.text !== null">
|
||||||
<th></th><!-- FIXME: Have correct colspan -->
|
<th class="py-0.5 pr-1 text-gray-900 font-semibold text-start">{{ section.text }}</th>
|
||||||
</tr>
|
<th></th><!-- FIXME: Have correct colspan -->
|
||||||
<DynamicReportEntryComponent :entry="child" v-for="child of section.entries" />
|
</tr>
|
||||||
|
<DynamicReportEntryComponent :entry="child" v-for="child of section.entries" />
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="entry == 'Spacer'">
|
<template v-if="entry == 'Spacer'">
|
||||||
<tr><td colspan="2" class="py-0.5"> </td></tr><!-- FIXME: Have correct colspan -->
|
<tr><td colspan="2" class="py-0.5"> </td></tr><!-- FIXME: Have correct colspan -->
|
||||||
|
@ -44,6 +44,7 @@ async function initApp() {
|
|||||||
{ path: '/statement-lines/import', name: 'import-statement', component: () => import('./pages/ImportStatementView.vue') },
|
{ path: '/statement-lines/import', name: 'import-statement', component: () => import('./pages/ImportStatementView.vue') },
|
||||||
{ path: '/transactions/:account', name: 'transactions', component: () => import('./pages/TransactionsView.vue') },
|
{ path: '/transactions/:account', name: 'transactions', component: () => import('./pages/TransactionsView.vue') },
|
||||||
{ path: '/trial-balance', name: 'trial-balance', component: () => import('./reports/TrialBalanceReport.vue') },
|
{ path: '/trial-balance', name: 'trial-balance', component: () => import('./reports/TrialBalanceReport.vue') },
|
||||||
|
{ path: '/austax/tax-summary', name: 'tax-summary', component: () => import('./austax/TaxSummaryReport.vue') },
|
||||||
];
|
];
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<!--
|
<!--
|
||||||
DrCr: Web-based double-entry bookkeeping framework
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
Copyright (C) 2022–2024 Lee Yingtong Li (RunasSudo)
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -40,7 +40,8 @@
|
|||||||
<div class="pl-4">
|
<div class="pl-4">
|
||||||
<h2 class="font-medium text-gray-700 mb-2">Advanced reports</h2>
|
<h2 class="font-medium text-gray-700 mb-2">Advanced reports</h2>
|
||||||
<ul class="list-disc ml-6">
|
<ul class="list-disc ml-6">
|
||||||
<!-- TODO: Plugin reports -->
|
<!-- TODO: Generate this list dynamically -->
|
||||||
|
<li><RouterLink :to="{ name: 'tax-summary' }" class="text-gray-900 hover:text-blue-700 hover:underline">Tax summary</RouterLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user