Started to implement account history.

This commit is contained in:
didier amyot committed 2023-04-10 12:00:31 -04:00
1 parent 20b5f32c71
commit f4593df95b
8 files changed
+158 -10

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
use rusqlite::{Connection, OptionalExtension, params};
use crate::db::accounts;
use crate::db::accounts::Account;
use crate::db::external_id::AccountId;
use crate::util::{CustomResult, Error};
pub struct AccountHistory {
account: Account,
timestamps: Vec<i64>,
amounts: Vec<i64>,
}
impl AccountHistory {
pub fn account(&self) -> &Account {
&self.account
}
pub fn timestamps(&self) -> &Vec<i64> {
&self.timestamps
}
pub fn amount(&self) -> &Vec<i64> {
&self.amounts
}
}
pub fn get_account_history(tx: &Connection, account_id: &AccountId) -> CustomResult<AccountHistory> {
let q = "
SELECT amount, CAST(timestamp AS INT) AS timestamp
FROM accounts_history
WHERE account_id = ?1
ORDER BY account_version ASC
";
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
let rows = stmt
.query_map(params![account_id], |row| Ok((row.get("amount")?, row.get("timestamp")?)))
.optional()
.map_err(Error::SqlError)?;
let ret: Vec<(i64, i64)> = rows.map(|x|
x.map(Result::unwrap)
.collect()).unwrap_or(vec![]);
let account = accounts::get_account_by_id(tx, account_id)?;
let mut amounts: Vec<i64> = ret.iter().map(|r| r.0).collect();
let mut timestamps: Vec<i64> = ret.iter().map(|r| r.1).collect();
amounts.push(account.amount());
timestamps.push(account.timestamp());
Ok(AccountHistory{account, timestamps, amounts })
}
#[cfg(test)]
mod test_account_history {
use crate::db::test_utils::setup_basic_environment;
use crate::migration;
use crate::util::CustomResult;
use super::*;
fn setup() -> CustomResult<(Connection, Account)> {
let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).unwrap();
let ret = {
let mut tx = conn.transaction().unwrap();
let (_, _, accounts) = setup_basic_environment(&mut tx)?;
tx.commit().unwrap();
accounts.get(0).unwrap().clone()
};
Ok((conn, ret))
}
#[test]
fn test_get_account_history_trivial() -> CustomResult<()>{
let (mut conn, acc) = setup()?;
let acc_hist = get_account_history(&conn, &acc.id())?;
assert_eq!(acc_hist.amounts, vec![0, 0]);
assert_eq!(acc_hist.timestamps.len(), 2);
assert_eq!(acc_hist.account.label(), "acc1");
Ok(())
}
#[test]
fn test_get_account_history_with_transactions() -> CustomResult<()>{
let (mut conn, acc) = setup()?;
{
let tx = conn.transaction().unwrap();
accounts::add_to_account(&tx, &acc.id(), 1)?;
tx.commit().unwrap();
}
let tx = conn.transaction().unwrap();
let acc_hist = get_account_history(&tx, &acc.id())?;
assert_eq!(acc_hist.amounts, vec![0, 1, 1]);
assert_eq!(acc_hist.timestamps.len(), 3);
assert_eq!(acc_hist.account.label(), "acc1");
Ok(())
}
}
+22
View File
@@ -50,6 +50,15 @@ impl ToString for AccountType{
}
}
impl Clone for AccountType {
fn clone(&self) -> Self {
match self {
AccountType::Checking => AccountType::Checking,
AccountType::SecurityEmitter => AccountType::SecurityEmitter,
}
}
}
pub struct Account {
id: i64,
amount: i64,
@@ -97,6 +106,19 @@ impl Account {
}
}
impl Clone for Account{
fn clone(&self) -> Self {
Account{
id: self.id,
amount: self.amount,
timestamp: self.timestamp,
security: self.security.clone(),
account_type: self.account_type.clone(),
label: self.label.clone(),
}
}
}
#[derive(Debug)]
struct AccountSnapshot {
amount: i64,
+1 -1
View File
@@ -233,7 +233,7 @@ pub mod test_external_ids {
#[test]
fn test_micro_id() {
let sid = SecurityId::from_id(33);
assert_eq!(sid.micro_id(), "s-ea676268")
assert_eq!(sid.micro_id(), "s-ea6762")
}
#[test]
+11
View File
@@ -87,6 +87,17 @@ impl Security {
}
}
impl Clone for Security {
fn clone(&self) -> Self {
Security{
id: self.id,
security_type: self.security_type.clone(),
symbol: self.symbol.clone(),
name: self.name.clone(),
}
}
}
pub struct NewSecurity {
pub security_type: SecurityType,
pub symbol: String,
+5
View File
@@ -6,6 +6,7 @@ mod _ledger_internal;
mod _securities;
mod _test_utils;
mod _users;
mod _account_history;
pub mod ledger_internal {
pub use super::_ledger_internal::*;
@@ -33,6 +34,10 @@ pub mod accounts {
};
}
pub mod account_history{
pub use super::_account_history::get_account_history;
}
pub mod cheques {
pub use super::_cheques::{Cheque, Drawer, Payee, WireTransferRequest};
}
+12 -1
View File
@@ -4,7 +4,7 @@ use crate::db::external_id::{AccountId, Id, SecurityId, UserId};
use crate::util::{CustomResult, Error};
use serde::Deserialize;
use serde::Serialize;
use crate::db::accounts;
use crate::db::{account_history, accounts};
use crate::db::securities::Security;
use crate::services::_securities::SecurityDTO;
@@ -22,6 +22,11 @@ pub struct AccountDTO {
pub label: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct AccountHistoryDTO {
}
impl AccountDTO {
fn from_entity(account: & Account) -> AccountDTO {
AccountDTO{
@@ -66,4 +71,10 @@ impl AccountService<'_> {
let ret = &accounts::get_account_by_id(self.conn, account_id)?;
Ok(AccountDTO::from_entity(ret))
}
pub fn get_account_history_by_id(&self, account_id: &AccountId, user_id: &UserId) -> CustomResult<()> {
let ret = account_history::get_account_history(self.conn, account_id)?;
Ok(())
}
}
+2 -2
View File
@@ -70,8 +70,8 @@ fn create_views_scope() -> Scope {
.service(index_views::login)
.service(index_views::logout)
.service(views::actions)
.service(views::accounts)
.service(views::account)
.service(views::get_accounts)
.service(views::get_account)
.service(views::securities)
.service(views::security)
.service(views::users)
+6 -6
View File
@@ -21,9 +21,9 @@ pub async fn actions(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Resp
}
#[actix_web::get("/accounts")]
pub async fn accounts(tmpl_env: MiniJinjaRenderer,
req: HttpRequest,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
pub async fn get_accounts(tmpl_env: MiniJinjaRenderer,
req: HttpRequest,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
let conn = service_factory.get_connection()?;
let auth_service = service_factory.get_authentication_service(&conn)?;
let user = auth_service.get_login(&req);
@@ -34,9 +34,9 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer,
}
#[actix_web::get("/accounts/{account_id}")]
pub async fn account(tmpl_env: MiniJinjaRenderer,
req: HttpRequest,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
pub async fn get_account(tmpl_env: MiniJinjaRenderer,
req: HttpRequest,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
let path_event_type = req.match_info().get("account_id").unwrap();
let account_id = AccountId::from_string(&path_event_type.to_string()).unwrap();
let conn = service_factory.get_connection()?;