One test fails :(
This commit is contained in:
1 parent
0a31f89501
commit
1f05ae1957
5 files changed
+170
-12
No files matched your search
@@ -2,7 +2,6 @@ PRAGMA journal_mode= WAL;
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS ledger_internal
|
CREATE TABLE IF NOT EXISTS ledger_internal
|
||||||
(
|
(
|
||||||
-- th
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
cookie_secret TEXT NOT NULL
|
cookie_secret TEXT NOT NULL
|
||||||
@@ -38,6 +37,20 @@ CREATE TABLE IF NOT EXISTS securities
|
|||||||
FOREIGN KEY (security_type) REFERENCES security_types (name)
|
FOREIGN KEY (security_type) REFERENCES security_types (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS interest_rates
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
security_id INTEGER NOT NULL,
|
||||||
|
security_rate_id INTEGER NOT NULL,
|
||||||
|
start_date DATETIME NOT NULL,
|
||||||
|
end_date DATETIME,
|
||||||
|
rate REAL NOT NULL,
|
||||||
|
FOREIGN KEY (security_id) REFERENCES securities(id),
|
||||||
|
CONSTRAINT unique_security_rate_id UNIQUE (security_id, security_rate_id),
|
||||||
|
CHECK (end_date IS NULL OR start_date <= end_date),
|
||||||
|
CHECK ( rate >= 0 )
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS account_types
|
CREATE TABLE IF NOT EXISTS account_types
|
||||||
(
|
(
|
||||||
name TEXT PRIMARY KEY
|
name TEXT PRIMARY KEY
|
||||||
@@ -60,7 +73,9 @@ CREATE TABLE IF NOT EXISTS accounts
|
|||||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
FOREIGN KEY (security) REFERENCES securities (id),
|
FOREIGN KEY (security) REFERENCES securities (id),
|
||||||
FOREIGN KEY (account_type) REFERENCES account_types (name)
|
FOREIGN KEY (account_type) REFERENCES account_types (name),
|
||||||
|
CHECK (amount >= 0 OR (account_type = 'security_emitter'))
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS accounts_history
|
CREATE TABLE IF NOT EXISTS accounts_history
|
||||||
|
|||||||
@@ -373,6 +373,13 @@ fn update_account_history(tx: &Connection, account_id: &AccountId) -> CustomResu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Super low level functions that needs to be wrapped in something else.
|
||||||
|
///
|
||||||
|
/// This method does NOT guarantee that all amount added into an account
|
||||||
|
/// comes from another account.
|
||||||
|
///
|
||||||
|
/// This method does NOT guarantee that checking account cannot go in the
|
||||||
|
/// negative.
|
||||||
pub fn add_to_account(
|
pub fn add_to_account(
|
||||||
tx: &Connection,
|
tx: &Connection,
|
||||||
account_id: &AccountId,
|
account_id: &AccountId,
|
||||||
@@ -411,6 +418,10 @@ pub fn get_number_of_accounts(tx: &Connection,
|
|||||||
tx.query_row(q, params, |row| row.get("count")).map_err(Error::SqlError)
|
tx.query_row(q, params, |row| row.get("count")).map_err(Error::SqlError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Market cap is computer by summing the security emitter accounts.
|
||||||
|
/// We use this technique because the sum of the security
|
||||||
|
/// emitter accounts MUST equals (more or less very tiny interest
|
||||||
|
/// amounts) the sum of the checking accounts.
|
||||||
pub fn get_security_market_cap(tx: &Connection,
|
pub fn get_security_market_cap(tx: &Connection,
|
||||||
sec: &SecurityId) -> CustomResult<i64> {
|
sec: &SecurityId) -> CustomResult<i64> {
|
||||||
let q = "
|
let q = "
|
||||||
|
|||||||
@@ -1 +1,30 @@
|
|||||||
|
|
||||||
|
const _one_massive_fucking_query : &str = "
|
||||||
|
WITH RECURSIVE
|
||||||
|
effective_interest_rates
|
||||||
|
AS (SELECT ROW_NUMBER() OVER () AS id,
|
||||||
|
interest_rates.id AS shit_id,
|
||||||
|
max(13, start_date) AS start_date,
|
||||||
|
min(coalesce(end_date, 20), 20) AS end_date,
|
||||||
|
min(coalesce(end_date, 20), 20) - max(13, start_date) AS duration,
|
||||||
|
interest_rate
|
||||||
|
FROM interest_rates
|
||||||
|
WHERE ((start_date >= 13) AND NOT (start_date > 20))
|
||||||
|
OR ((start_date < 13) AND (end_date > 13 OR end_date IS NULL))
|
||||||
|
ORDER BY start_date ASC),
|
||||||
|
compound_value(compound_value_id, value, interest_rate, duration)
|
||||||
|
AS (SELECT 0, 100, 0, 0 -- the 100 is the initial value
|
||||||
|
UNION
|
||||||
|
SELECT compound_value.compound_value_id + 1,
|
||||||
|
compound_value.value *
|
||||||
|
pow(1 + effective_interest_rates.interest_rate, effective_interest_rates.duration),
|
||||||
|
effective_interest_rates.interest_rate,
|
||||||
|
effective_interest_rates.duration
|
||||||
|
FROM effective_interest_rates
|
||||||
|
JOIN compound_value ON compound_value.compound_value_id + 1 = effective_interest_rates.id)
|
||||||
|
SELECT value
|
||||||
|
FROM compound_value
|
||||||
|
ORDER BY compound_value_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
|
||||||
+112
-10
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{params, Connection, Result, Row, ToSql, Transaction, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension, Result, Row, ToSql, Transaction};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::db::_external_id::{Id, SecurityId};
|
use crate::db::_external_id::{Id, SecurityId};
|
||||||
@@ -89,7 +89,7 @@ impl Security {
|
|||||||
|
|
||||||
impl Clone for Security {
|
impl Clone for Security {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Security{
|
Security {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
security_type: self.security_type.clone(),
|
security_type: self.security_type.clone(),
|
||||||
symbol: self.symbol.clone(),
|
symbol: self.symbol.clone(),
|
||||||
@@ -104,19 +104,97 @@ pub struct NewSecurity {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InterestRate {
|
||||||
|
rate: f64,
|
||||||
|
start_date: i64,
|
||||||
|
end_date: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterestRate {
|
||||||
|
pub fn from_row(row: &Row) -> Result<InterestRate> {
|
||||||
|
Ok(InterestRate {
|
||||||
|
rate: row.get("rate")?,
|
||||||
|
start_date: row.get("start_date")?,
|
||||||
|
end_date: row.get("end_date")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_initial_interest_rate(tx: &Connection, sec_id: &SecurityId) -> CustomResult<()> {
|
||||||
|
let q = "
|
||||||
|
INSERT INTO interest_rates(
|
||||||
|
security_id,
|
||||||
|
security_rate_id,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
rate
|
||||||
|
)
|
||||||
|
SELECT id, 0, CURRENT_TIMESTAMP, NULL, 1.0
|
||||||
|
FROM securities
|
||||||
|
WHERE id = ?1 AND securities.security_type = ?2
|
||||||
|
";
|
||||||
|
tx.execute(q, params![sec_id, SecurityType::Currency]).map_err(Error::SqlError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interest_rate(tx: &Connection, sec_id: &SecurityId, rate: f64) -> CustomResult<()> {
|
||||||
|
let q = "
|
||||||
|
INSERT INTO interest_rates(
|
||||||
|
security_id,
|
||||||
|
security_rate_id,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
rate
|
||||||
|
)
|
||||||
|
SELECT ?1 AS security_id,
|
||||||
|
MAX(interest_rates.security_rate_id) + 1 AS security_rate_id,
|
||||||
|
CURRENT_TIMESTAMP AS start_date,
|
||||||
|
NULL AS end_date,
|
||||||
|
?2 AS rate
|
||||||
|
FROM interest_rates JOIN securities ON interest_rates.security_id = securities.id
|
||||||
|
WHERE interest_rates.security_id = ?1
|
||||||
|
AND securities.security_type = ?3
|
||||||
|
";
|
||||||
|
tx.execute(q, params![sec_id, rate, SecurityType::Currency])
|
||||||
|
.map_err(Error::SqlError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rates(tx: &Connection, sec_id: &SecurityId) -> CustomResult<Vec<InterestRate>> {
|
||||||
|
let q = "
|
||||||
|
SELECT rate,
|
||||||
|
UNIXEPOCH(start_date) AS start_date,
|
||||||
|
UNIXEPOCH(end_date) AS end_date
|
||||||
|
FROM interest_rates
|
||||||
|
WHERE security_id = ?1
|
||||||
|
ORDER BY security_rate_id ASC";
|
||||||
|
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
|
||||||
|
let ret: Vec<InterestRate> = stmt
|
||||||
|
.query_map(params![sec_id], InterestRate::from_row)
|
||||||
|
.map_err(Error::SqlError)?
|
||||||
|
.filter(Result::is_ok)
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect();
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_security(tx: &Connection, sec: &NewSecurity) -> CustomResult<Security> {
|
pub fn create_security(tx: &Connection, sec: &NewSecurity) -> CustomResult<Security> {
|
||||||
let q = "INSERT INTO
|
let q = "INSERT INTO
|
||||||
securities(name, symbol, security_type)
|
securities(name, symbol, security_type)
|
||||||
VALUES(?1, ?2, ?3)";
|
VALUES(?1, ?2, ?3)";
|
||||||
let params = (&sec.name, &sec.symbol, &sec.security_type);
|
let params = (&sec.name, &sec.symbol, &sec.security_type);
|
||||||
tx.execute(q, params).map_err(Error::SqlError)?;
|
tx.execute(q, params).map_err(Error::SqlError)?;
|
||||||
let new_sec_id:i64 = tx.last_insert_rowid();
|
let new_sec_id = &SecurityId::from_id(tx.last_insert_rowid());
|
||||||
|
|
||||||
let ret = get_security_by_id(tx, &SecurityId::from_id(new_sec_id))?;
|
if sec.security_type == SecurityType::Currency {
|
||||||
|
set_initial_interest_rate(tx, new_sec_id)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = get_security_by_id(tx, new_sec_id)?;
|
||||||
Ok(ret.unwrap())
|
Ok(ret.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_security_by_id(tx: &Connection, id: & SecurityId) -> CustomResult<Option<Security>> {
|
pub fn get_security_by_id(tx: &Connection, id: &SecurityId) -> CustomResult<Option<Security>> {
|
||||||
let q = "SELECT id AS sec_id,
|
let q = "SELECT id AS sec_id,
|
||||||
security_type AS sec_security_type,
|
security_type AS sec_security_type,
|
||||||
symbol AS sec_symbol,
|
symbol AS sec_symbol,
|
||||||
@@ -126,7 +204,6 @@ pub fn get_security_by_id(tx: &Connection, id: & SecurityId) -> CustomResult<Opt
|
|||||||
tx.query_row(q, params![id], Security::from_row)
|
tx.query_row(q, params![id], Security::from_row)
|
||||||
.optional()
|
.optional()
|
||||||
.map_err(Error::SqlError)
|
.map_err(Error::SqlError)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
|
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
|
||||||
@@ -184,14 +261,39 @@ mod test_securities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_security() -> CustomResult<()> {
|
fn test_create_security_currency() -> CustomResult<()> {
|
||||||
let (mut conn, _users) = setup();
|
let (mut conn, _users) = setup();
|
||||||
let new_sec = mock_currency_1();
|
let new_sec = mock_currency_1();
|
||||||
let tx = conn.transaction().unwrap();
|
let sec = create_security(&conn, &new_sec)?;
|
||||||
let sec = create_security(&tx, &new_sec)?;
|
|
||||||
assert_eq!(sec.symbol, new_sec.symbol);
|
assert_eq!(sec.symbol, new_sec.symbol);
|
||||||
|
assert_eq!(sec.security_type, SecurityType::Currency);
|
||||||
|
|
||||||
tx.commit().unwrap();
|
let interest_rate = get_rates(&conn, &sec.id())?;
|
||||||
|
assert_eq!(interest_rate.len(), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_security_bond() -> CustomResult<()> {
|
||||||
|
let (mut conn, _users) = setup();
|
||||||
|
let new_sec = mock_currency_2();
|
||||||
|
let sec = create_security(&conn, &new_sec)?;
|
||||||
|
assert_eq!(sec.symbol, new_sec.symbol);
|
||||||
|
assert_eq!(sec.security_type, SecurityType::Bond);
|
||||||
|
let interest_rate = get_rates(&conn, &sec.id())?;
|
||||||
|
assert_eq!(interest_rate.len(), 0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_interest_rate() -> CustomResult<()> {
|
||||||
|
let (mut conn, _users) = setup();
|
||||||
|
let new_sec = mock_currency_1();
|
||||||
|
let sec = &create_security(&conn, &new_sec)?.id();
|
||||||
|
set_interest_rate(&conn, sec, 90.00)?;
|
||||||
|
let interest_rate = get_rates(&conn, sec)?;
|
||||||
|
assert_eq!(interest_rate.len(), 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -459,6 +459,7 @@ mod test_caching_cheques {
|
|||||||
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||||
match ret {
|
match ret {
|
||||||
Err(Error::InvalidAmount(_)) => Ok(()),
|
Err(Error::InvalidAmount(_)) => Ok(()),
|
||||||
|
Err(x) => Err(Error::Testing(format!("{:?}", x))),
|
||||||
_ => Err(Error::Testing(
|
_ => Err(Error::Testing(
|
||||||
"Expected invalid amount error here.".to_string(),
|
"Expected invalid amount error here.".to_string(),
|
||||||
)),
|
)),
|
||||||
|
|||||||
Reference in new issue
Block a user