Implement RetainedEarningsToEquity
This commit is contained in:
parent
ec470f8ced
commit
3add701d3c
43
src/account_config.rs
Normal file
43
src/account_config.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct AccountConfiguration {
|
||||||
|
pub id: Option<u64>,
|
||||||
|
pub account: String,
|
||||||
|
pub kind: String,
|
||||||
|
pub data: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert [`Vec<AccountConfiguration>`] into a [HashMap] mapping account names to account kinds
|
||||||
|
pub fn kinds_for_account(
|
||||||
|
account_configurations: Vec<AccountConfiguration>,
|
||||||
|
) -> HashMap<String, Vec<String>> {
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
|
||||||
|
for account_configuration in account_configurations {
|
||||||
|
// Record the account kind
|
||||||
|
result
|
||||||
|
.entry(account_configuration.account)
|
||||||
|
.or_insert_with(|| Vec::new())
|
||||||
|
.push(account_configuration.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
27
src/db.rs
27
src/db.rs
@ -21,9 +21,11 @@ use std::ops::DerefMut;
|
|||||||
use std::{cell::RefCell, future::Future};
|
use std::{cell::RefCell, future::Future};
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
use sqlx::sqlite::SqliteRow;
|
||||||
use sqlx::{Connection, Row, SqliteConnection};
|
use sqlx::{Connection, Row, SqliteConnection};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use crate::account_config::AccountConfiguration;
|
||||||
use crate::{util::format_date, QuantityInt};
|
use crate::{util::format_date, QuantityInt};
|
||||||
|
|
||||||
pub struct DbConnection {
|
pub struct DbConnection {
|
||||||
@ -73,7 +75,7 @@ impl DbConnection {
|
|||||||
SELECT max_tid_by_account.account, running_balance AS quantity
|
SELECT max_tid_by_account.account, running_balance AS quantity
|
||||||
FROM max_tid_by_account
|
FROM max_tid_by_account
|
||||||
JOIN transactions_with_running_balances ON max_tid = transactions_with_running_balances.transaction_id AND max_tid_by_account.account = transactions_with_running_balances.account"
|
JOIN transactions_with_running_balances ON max_tid = transactions_with_running_balances.transaction_id AND max_tid_by_account.account = transactions_with_running_balances.account"
|
||||||
).bind(format_date(date)).fetch_all(connection.deref_mut()).await.unwrap();
|
).bind(format_date(date)).fetch_all(connection.deref_mut()).await.expect("SQL error");
|
||||||
|
|
||||||
let mut balances = HashMap::new();
|
let mut balances = HashMap::new();
|
||||||
for row in rows {
|
for row in rows {
|
||||||
@ -82,4 +84,27 @@ impl DbConnection {
|
|||||||
|
|
||||||
balances
|
balances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get account configurations from the database
|
||||||
|
pub fn get_account_configurations(&self) -> Vec<AccountConfiguration> {
|
||||||
|
run_blocking(self.get_account_configurations_async())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_account_configurations_async(&self) -> Vec<AccountConfiguration> {
|
||||||
|
let mut connection = self.sqlx_connection.borrow_mut();
|
||||||
|
|
||||||
|
let account_configurations =
|
||||||
|
sqlx::query("SELECT id, account, kind, data FROM account_configurations")
|
||||||
|
.map(|r: SqliteRow| AccountConfiguration {
|
||||||
|
id: r.get("id"),
|
||||||
|
account: r.get("account"),
|
||||||
|
kind: r.get("kind"),
|
||||||
|
data: r.get("data"),
|
||||||
|
})
|
||||||
|
.fetch_all(connection.deref_mut())
|
||||||
|
.await
|
||||||
|
.expect("SQL error");
|
||||||
|
|
||||||
|
account_configurations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod account_config;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod reporting;
|
pub mod reporting;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -34,8 +34,11 @@ fn main() {
|
|||||||
let db_connection = DbConnection::connect("sqlite:drcr_testing.db");
|
let db_connection = DbConnection::connect("sqlite:drcr_testing.db");
|
||||||
|
|
||||||
// Initialise ReportingContext
|
// Initialise ReportingContext
|
||||||
let mut context =
|
let mut context = ReportingContext::new(
|
||||||
ReportingContext::new(db_connection, NaiveDate::from_ymd_opt(2025, 6, 30).unwrap());
|
db_connection,
|
||||||
|
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||||
|
"$".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
register_lookup_fns(&mut context);
|
register_lookup_fns(&mut context);
|
||||||
register_dynamic_builders(&mut context);
|
register_dynamic_builders(&mut context);
|
||||||
@ -65,6 +68,7 @@ fn main() {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
println!("Income statement:");
|
||||||
println!("{:?}", result);
|
println!("{:?}", result);
|
||||||
|
|
||||||
// Get balance sheet
|
// Get balance sheet
|
||||||
@ -89,6 +93,6 @@ fn main() {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
//println!("{}", products);
|
println!("Balance sheet:");
|
||||||
println!("{:?}", result);
|
println!("{:?}", result);
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,11 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
|
|
||||||
|
use crate::account_config::kinds_for_account;
|
||||||
use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProductId, Transactions};
|
use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProductId, Transactions};
|
||||||
use crate::transaction::update_balances_from_transactions;
|
use crate::transaction::{
|
||||||
|
update_balances_from_transactions, Posting, Transaction, TransactionWithPostings,
|
||||||
|
};
|
||||||
use crate::util::sofy_from_eofy;
|
use crate::util::sofy_from_eofy;
|
||||||
|
|
||||||
use super::calculator::ReportingGraphDependencies;
|
use super::calculator::ReportingGraphDependencies;
|
||||||
@ -659,17 +662,72 @@ impl ReportingStep for RetainedEarningsToEquity {
|
|||||||
|
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
_context: &ReportingContext,
|
context: &ReportingContext,
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
_dependencies: &ReportingGraphDependencies,
|
_dependencies: &ReportingGraphDependencies,
|
||||||
products: &mut ReportingProducts,
|
products: &mut ReportingProducts,
|
||||||
) -> Result<(), ReportingExecutionError> {
|
) -> Result<(), ReportingExecutionError> {
|
||||||
eprintln!("Stub: RetainedEarningsToEquity.execute");
|
// Get balances at end of last financial year
|
||||||
|
let last_eofy_date = context
|
||||||
|
.eofy_date
|
||||||
|
.with_year(context.eofy_date.year() - 1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let transactions = Transactions {
|
let balances_last_eofy = products
|
||||||
|
.get_or_err(&ReportingProductId {
|
||||||
|
name: "CombineOrdinaryTransactions",
|
||||||
|
kind: ReportingProductKind::BalancesAt,
|
||||||
|
args: Box::new(DateArgs {
|
||||||
|
date: last_eofy_date.clone(),
|
||||||
|
}),
|
||||||
|
})?
|
||||||
|
.downcast_ref::<BalancesAt>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Get income and expense accounts
|
||||||
|
let kinds_for_account =
|
||||||
|
kinds_for_account(context.db_connection.get_account_configurations());
|
||||||
|
|
||||||
|
// Transfer income and expense balances to retained earnings
|
||||||
|
let mut transactions = Transactions {
|
||||||
transactions: Vec::new(),
|
transactions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (account, balance) in balances_last_eofy.balances.iter() {
|
||||||
|
if let Some(kinds) = kinds_for_account.get(account) {
|
||||||
|
if kinds
|
||||||
|
.iter()
|
||||||
|
.any(|k| k == "drcr.income" || k == "drcr.expense")
|
||||||
|
{
|
||||||
|
transactions.transactions.push(TransactionWithPostings {
|
||||||
|
transaction: Transaction {
|
||||||
|
id: None,
|
||||||
|
dt: last_eofy_date.and_hms_opt(0, 0, 0).unwrap(),
|
||||||
|
description: "Retained earnings".to_string(),
|
||||||
|
},
|
||||||
|
postings: vec![
|
||||||
|
Posting {
|
||||||
|
id: None,
|
||||||
|
transaction_id: None,
|
||||||
|
description: None,
|
||||||
|
account: account.clone(),
|
||||||
|
quantity: -balance,
|
||||||
|
commodity: context.reporting_commodity.clone(),
|
||||||
|
},
|
||||||
|
Posting {
|
||||||
|
id: None,
|
||||||
|
transaction_id: None,
|
||||||
|
description: None,
|
||||||
|
account: "Retained Earnings".to_string(),
|
||||||
|
quantity: *balance,
|
||||||
|
commodity: context.reporting_commodity.clone(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
products.insert(
|
products.insert(
|
||||||
ReportingProductId {
|
ReportingProductId {
|
||||||
name: self.id().name,
|
name: self.id().name,
|
||||||
|
@ -41,6 +41,7 @@ pub struct ReportingContext {
|
|||||||
// Configuration
|
// Configuration
|
||||||
pub db_connection: DbConnection,
|
pub db_connection: DbConnection,
|
||||||
pub eofy_date: NaiveDate,
|
pub eofy_date: NaiveDate,
|
||||||
|
pub reporting_commodity: String,
|
||||||
|
|
||||||
// State
|
// State
|
||||||
pub(crate) step_lookup_fn: HashMap<
|
pub(crate) step_lookup_fn: HashMap<
|
||||||
@ -52,10 +53,15 @@ pub struct ReportingContext {
|
|||||||
|
|
||||||
impl ReportingContext {
|
impl ReportingContext {
|
||||||
/// Initialise a new [ReportingContext]
|
/// Initialise a new [ReportingContext]
|
||||||
pub fn new(db_connection: DbConnection, eofy_date: NaiveDate) -> Self {
|
pub fn new(
|
||||||
|
db_connection: DbConnection,
|
||||||
|
eofy_date: NaiveDate,
|
||||||
|
reporting_commodity: String,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db_connection: db_connection,
|
db_connection,
|
||||||
eofy_date: eofy_date,
|
eofy_date,
|
||||||
|
reporting_commodity,
|
||||||
step_lookup_fn: HashMap::new(),
|
step_lookup_fn: HashMap::new(),
|
||||||
step_dynamic_builders: Vec::new(),
|
step_dynamic_builders: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ pub struct TransactionWithPostings {
|
|||||||
pub struct Posting {
|
pub struct Posting {
|
||||||
pub id: Option<u64>,
|
pub id: Option<u64>,
|
||||||
pub transaction_id: Option<u64>,
|
pub transaction_id: Option<u64>,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
pub account: String,
|
pub account: String,
|
||||||
pub quantity: QuantityInt,
|
pub quantity: QuantityInt,
|
||||||
pub commodity: String,
|
pub commodity: String,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user