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
|
||||
(
|
||||
-- th
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
cookie_secret TEXT NOT NULL
|
||||
@@ -38,6 +37,20 @@ CREATE TABLE IF NOT EXISTS securities
|
||||
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
|
||||
(
|
||||
name TEXT PRIMARY KEY
|
||||
@@ -60,7 +73,9 @@ CREATE TABLE IF NOT EXISTS accounts
|
||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
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
|
||||
|
||||
@@ -373,6 +373,13 @@ fn update_account_history(tx: &Connection, account_id: &AccountId) -> CustomResu
|
||||
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(
|
||||
tx: &Connection,
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
sec: &SecurityId) -> CustomResult<i64> {
|
||||
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 crate::db::_external_id::{Id, SecurityId};
|
||||
@@ -89,7 +89,7 @@ impl Security {
|
||||
|
||||
impl Clone for Security {
|
||||
fn clone(&self) -> Self {
|
||||
Security{
|
||||
Security {
|
||||
id: self.id,
|
||||
security_type: self.security_type.clone(),
|
||||
symbol: self.symbol.clone(),
|
||||
@@ -104,19 +104,97 @@ pub struct NewSecurity {
|
||||
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> {
|
||||
let q = "INSERT INTO
|
||||
securities(name, symbol, security_type)
|
||||
VALUES(?1, ?2, ?3)";
|
||||
let params = (&sec.name, &sec.symbol, &sec.security_type);
|
||||
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())
|
||||
}
|
||||
|
||||
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,
|
||||
security_type AS sec_security_type,
|
||||
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)
|
||||
.optional()
|
||||
.map_err(Error::SqlError)
|
||||
|
||||
}
|
||||
|
||||
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
|
||||
@@ -184,14 +261,39 @@ mod test_securities {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_security() -> CustomResult<()> {
|
||||
fn test_create_security_currency() -> CustomResult<()> {
|
||||
let (mut conn, _users) = setup();
|
||||
let new_sec = mock_currency_1();
|
||||
let tx = conn.transaction().unwrap();
|
||||
let sec = create_security(&tx, &new_sec)?;
|
||||
let sec = create_security(&conn, &new_sec)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -459,6 +459,7 @@ mod test_caching_cheques {
|
||||
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||
match ret {
|
||||
Err(Error::InvalidAmount(_)) => Ok(()),
|
||||
Err(x) => Err(Error::Testing(format!("{:?}", x))),
|
||||
_ => Err(Error::Testing(
|
||||
"Expected invalid amount error here.".to_string(),
|
||||
)),
|
||||
|
||||
Reference in new issue
Block a user