I need to fucking work.
This commit is contained in:
1 parent
e1e5d5dbf3
commit
f99513a8c8
7 files changed
+230
-53
No files matched your search
Generated
+57
@@ -210,6 +210,7 @@ dependencies = [
|
|||||||
"apache-avro",
|
"apache-avro",
|
||||||
"clap",
|
"clap",
|
||||||
"clap-verbosity-flag",
|
"clap-verbosity-flag",
|
||||||
|
"fraction",
|
||||||
"hex",
|
"hex",
|
||||||
"home",
|
"home",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -245,6 +246,16 @@ dependencies = [
|
|||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fraction"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c37ba08fe22fba12a3ada3e479d09a8f7fac6081aa6f5c27f22b78639025031"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"num",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.6"
|
version = "0.14.6"
|
||||||
@@ -396,6 +407,20 @@ version = "2.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -407,6 +432,15 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@@ -417,6 +451,29 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
+2
-1
@@ -20,7 +20,8 @@ hex = "0.4.3"
|
|||||||
regex = "1.7.0"
|
regex = "1.7.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
|
fraction = "0.13.0"
|
||||||
|
|
||||||
[dependencies.apache-avro]
|
[dependencies.apache-avro]
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
features = ["zstandard"]
|
features = ["zstandard"]
|
||||||
@@ -37,6 +37,7 @@ VALUES ('checking'),
|
|||||||
CREATE TABLE IF NOT EXISTS accounts
|
CREATE TABLE IF NOT EXISTS accounts
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
version INTEGER NOT NULL CHECK ( version >= 0 ),
|
||||||
security INTEGER NOT NULL,
|
security INTEGER NOT NULL,
|
||||||
account_type TEXT NOT NULL,
|
account_type TEXT NOT NULL,
|
||||||
|
|
||||||
@@ -48,6 +49,16 @@ CREATE TABLE IF NOT EXISTS accounts
|
|||||||
FOREIGN KEY (account_type) REFERENCES account_types (name)
|
FOREIGN KEY (account_type) REFERENCES account_types (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS accounts_history
|
||||||
|
(
|
||||||
|
account_id INTEGER NOT NULL,
|
||||||
|
account_version INTEGER NOT NULL,
|
||||||
|
amount INTEGER NOT NULL,
|
||||||
|
timestamp DATETIME NOT NULL,
|
||||||
|
FOREIGN KEY (account_id) REFERENCES accounts (id),
|
||||||
|
PRIMARY KEY (account_id, account_version)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS account_user_permissions
|
CREATE TABLE IF NOT EXISTS account_user_permissions
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -60,18 +71,19 @@ CREATE TABLE IF NOT EXISTS account_user_permissions
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS cashed_cheques
|
CREATE TABLE IF NOT EXISTS cashed_cheques
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
drawer_user_id INTEGER NOT NULL,
|
drawer_user_id INTEGER NOT NULL,
|
||||||
drawer_account_id INTEGER NOT NULL,
|
drawer_account_id INTEGER NOT NULL,
|
||||||
cheque_number INTEGER NOT NULL,
|
drawer_account_version INTEGER NOT NULL,
|
||||||
amount INTEGER NOT NULL,
|
cheque_number INTEGER NOT NULL,
|
||||||
underlying_security INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
payee_user_id INTEGER NOT NULL,
|
underlying_security INTEGER NOT NULL,
|
||||||
payee_account_id INTEGER NOT NULL,
|
payee_user_id INTEGER NOT NULL,
|
||||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
payee_account_id INTEGER NOT NULL,
|
||||||
|
payee_account_version INTEGER NOT NULL,
|
||||||
|
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
FOREIGN KEY (underlying_security) REFERENCES securities (id),
|
FOREIGN KEY (underlying_security) REFERENCES securities (id),
|
||||||
FOREIGN KEY (drawer_user_id) REFERENCES users (id),
|
FOREIGN KEY (drawer_user_id) REFERENCES users (id),
|
||||||
|
|||||||
+127
-30
@@ -1,4 +1,5 @@
|
|||||||
use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction};
|
use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction};
|
||||||
|
use fraction::{Fraction};
|
||||||
|
|
||||||
use crate::db::_external_id::{AccountId, Id, UserId};
|
use crate::db::_external_id::{AccountId, Id, UserId};
|
||||||
use crate::db::_securities::Security;
|
use crate::db::_securities::Security;
|
||||||
@@ -51,7 +52,56 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
|
#[derive(Debug)]
|
||||||
|
struct AccountSnapshot {
|
||||||
|
amount: i64,
|
||||||
|
version: i64,
|
||||||
|
timestamp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountSnapshot {
|
||||||
|
pub fn from_row(row: &Row) -> Result<AccountSnapshot> {
|
||||||
|
Ok(AccountSnapshot {
|
||||||
|
amount: row.get("amount")?,
|
||||||
|
version: row.get("account_version")?,
|
||||||
|
timestamp: row.get("timestamp")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccountHistory {
|
||||||
|
id: i64,
|
||||||
|
security: Security,
|
||||||
|
account_type: AccountType,
|
||||||
|
snapshots: Vec<AccountSnapshot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountHistory {
|
||||||
|
pub fn id(&self) -> AccountId {
|
||||||
|
AccountId::from_id(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.snapshots.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average_amount(&self) -> Fraction {
|
||||||
|
let num: i64 = self.snapshots.iter().map(|x| x.amount).sum();
|
||||||
|
Fraction::from(num) / Fraction::from(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(account: Account, snapshots: Vec<AccountSnapshot>) -> CustomResult<AccountHistory> {
|
||||||
|
Ok(AccountHistory {
|
||||||
|
id: account.id,
|
||||||
|
security: account.security,
|
||||||
|
account_type: account.account_type,
|
||||||
|
snapshots,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
|
||||||
let q = "SELECT acc.id AS acc_id,
|
let q = "SELECT acc.id AS acc_id,
|
||||||
acc.amount AS acc_amount,
|
acc.amount AS acc_amount,
|
||||||
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
||||||
@@ -63,16 +113,17 @@ pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
|
|||||||
FROM accounts acc
|
FROM accounts acc
|
||||||
JOIN securities sec ON acc.security = sec.id
|
JOIN securities sec ON acc.security = sec.id
|
||||||
ORDER BY acc.id ASC";
|
ORDER BY acc.id ASC";
|
||||||
let mut stmt = conn.prepare(q)?;
|
let mut stmt = conn.prepare(q).map_err(Error::SqlError)?;
|
||||||
let ret: Vec<Account> = stmt
|
let ret: Vec<Account> = stmt
|
||||||
.query_map([], &Account::from_row)?
|
.query_map([], &Account::from_row)
|
||||||
|
.map_err(Error::SqlError)?
|
||||||
.filter(Result::is_ok)
|
.filter(Result::is_ok)
|
||||||
.map(Result::unwrap)
|
.map(Result::unwrap)
|
||||||
.collect();
|
.collect();
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> Result<Account> {
|
pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Account> {
|
||||||
let q = "SELECT acc.id AS acc_id,
|
let q = "SELECT acc.id AS acc_id,
|
||||||
acc.amount AS acc_amount,
|
acc.amount AS acc_amount,
|
||||||
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
||||||
@@ -84,43 +135,64 @@ pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> Result<Account> {
|
|||||||
FROM accounts acc
|
FROM accounts acc
|
||||||
JOIN securities sec ON acc.security = sec.id
|
JOIN securities sec ON acc.security = sec.id
|
||||||
WHERE acc.id = ?1";
|
WHERE acc.id = ?1";
|
||||||
return tx.query_row(q, params![id], Account::from_row);
|
return tx.query_row(q, params![id], Account::from_row).map_err(Error::SqlError);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_account(tx: &Connection, sec: &SecurityId, user: &UserId, account_type: AccountType) -> Result<Account> {
|
fn create_account(tx: &Connection, sec: &SecurityId, user: &UserId, account_type: AccountType) -> CustomResult<Account> {
|
||||||
let q = "INSERT INTO accounts(security, amount, timestamp, account_type)
|
let q = "INSERT INTO accounts(security, version, amount, timestamp, account_type)
|
||||||
VALUES (?1, 0, CURRENT_TIMESTAMP, ?2)";
|
VALUES (?1, 0, 0, CURRENT_TIMESTAMP, ?2)";
|
||||||
let account_type = match account_type {
|
let account_type = match account_type {
|
||||||
AccountType::Checking => "checking",
|
AccountType::Checking => "checking",
|
||||||
AccountType::SecurityEmitter => "security_emitter",
|
AccountType::SecurityEmitter => "security_emitter",
|
||||||
};
|
};
|
||||||
tx.execute(q, (sec, account_type))?;
|
tx.execute(q, (sec, account_type)).map_err(Error::SqlError)?;
|
||||||
let id = AccountId::from_id(tx.last_insert_rowid() as i64);
|
let id = AccountId::from_id(tx.last_insert_rowid() as i64);
|
||||||
|
|
||||||
let q = "INSERT INTO account_user_permissions(user_id, account_id)
|
let q = "INSERT INTO account_user_permissions(user_id, account_id)
|
||||||
VALUES(?1, ?2)";
|
VALUES(?1, ?2)";
|
||||||
tx.execute(q, params![user, id])?;
|
tx.execute(q, params![user, id]).map_err(Error::SqlError)?;
|
||||||
|
|
||||||
|
update_account_history(tx, &id)?;
|
||||||
return get_account_by_id(tx, &id);
|
return get_account_by_id(tx, &id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_checking_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
fn get_account_history(tx: &Connection, id: &AccountId) -> CustomResult<AccountHistory> {
|
||||||
|
let q = "
|
||||||
|
SELECT account_version,
|
||||||
|
amount,
|
||||||
|
UNIXEPOCH(timestamp) AS timestamp
|
||||||
|
FROM accounts_history
|
||||||
|
WHERE account_id = ?1
|
||||||
|
ORDER BY account_version DESC";
|
||||||
|
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
|
||||||
|
let snapshots: Vec<AccountSnapshot> = stmt.query_map(params![id], AccountSnapshot::from_row)
|
||||||
|
.map_err(Error::SqlError)?
|
||||||
|
.filter(Result::is_ok)
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return AccountHistory::new(get_account_by_id(tx, id)?, snapshots);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_checking_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> CustomResult<Account> {
|
||||||
create_account(tx, sec, user, AccountType::Checking)
|
create_account(tx, sec, user, AccountType::Checking)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_security_emitter_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
pub fn create_security_emitter_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> CustomResult<Account> {
|
||||||
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_owner(tx: &Connection, account_id: &AccountId, user_id: &UserId) -> Result<bool> {
|
pub fn is_owner(tx: &Connection, account_id: &AccountId, user_id: &UserId) -> CustomResult<bool> {
|
||||||
let q = "SELECT id FROM account_user_permissions WHERE account_id = ?1 AND user_id = ?2";
|
let q = "SELECT id FROM account_user_permissions WHERE account_id = ?1 AND user_id = ?2";
|
||||||
let params = params![account_id, user_id];
|
let params = params![account_id, user_id];
|
||||||
let ite = |row: &Row| row.get::<usize, i64>(0);
|
let ite = |row: &Row| row.get::<usize, i64>(0);
|
||||||
let option = tx.query_row(q, params, ite).optional()?;
|
let option = tx.query_row(q, params, ite).optional().map_err(Error::SqlError)?;
|
||||||
Ok(option.is_some())
|
Ok(option.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Receipt {
|
pub struct Receipt {
|
||||||
pub account_id: AccountId,
|
pub account_id: AccountId,
|
||||||
|
pub version: i64,
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub amount: i64,
|
pub amount: i64,
|
||||||
}
|
}
|
||||||
@@ -129,12 +201,27 @@ impl Receipt {
|
|||||||
pub fn from_row(row: &Row) -> Result<Receipt> {
|
pub fn from_row(row: &Row) -> Result<Receipt> {
|
||||||
Ok(Receipt {
|
Ok(Receipt {
|
||||||
account_id: AccountId::from_id(row.get("id")?),
|
account_id: AccountId::from_id(row.get("id")?),
|
||||||
|
version: row.get("version")?,
|
||||||
amount: row.get("amount")?,
|
amount: row.get("amount")?,
|
||||||
timestamp: row.get("timestamp")?,
|
timestamp: row.get("timestamp")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_account_history(tx: &Connection, account_id: &AccountId) -> CustomResult<()> {
|
||||||
|
let q = "
|
||||||
|
INSERT INTO accounts_history
|
||||||
|
SELECT id AS account_id,
|
||||||
|
version AS account_version,
|
||||||
|
amount,
|
||||||
|
timestamp
|
||||||
|
FROM accounts
|
||||||
|
WHERE id = ?1
|
||||||
|
";
|
||||||
|
tx.execute(q, params![account_id]).map_err(Error::SqlError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_to_account(tx: &Connection,
|
pub fn add_to_account(tx: &Connection,
|
||||||
account_id: &AccountId,
|
account_id: &AccountId,
|
||||||
amount: i64) -> CustomResult<Receipt> {
|
amount: i64) -> CustomResult<Receipt> {
|
||||||
@@ -143,12 +230,15 @@ pub fn add_to_account(tx: &Connection,
|
|||||||
WHEN 'security_emitter'
|
WHEN 'security_emitter'
|
||||||
THEN amount
|
THEN amount
|
||||||
ELSE amount + ?1 END,
|
ELSE amount + ?1 END,
|
||||||
|
version = version + 1,
|
||||||
timestamp = CURRENT_TIMESTAMP
|
timestamp = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?2
|
WHERE id = ?2
|
||||||
RETURNING id, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp
|
RETURNING id, version, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp
|
||||||
";
|
";
|
||||||
let params = params![amount, account_id];
|
let params = params![amount, account_id];
|
||||||
tx.query_row(q, params, Receipt::from_row).map_err(Error::SqlError)
|
let ret = tx.query_row(q, params, Receipt::from_row).map_err(Error::SqlError);
|
||||||
|
update_account_history(tx, account_id)?;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -171,7 +261,7 @@ mod test_accounts {
|
|||||||
return (conn, users, securities);
|
return (conn, users, securities);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_all_accounts(tx: &Transaction, users: &Vec<User>, secs: &Vec<Security>) -> Result<Vec<Account>> {
|
fn create_all_accounts(tx: &Transaction, users: &Vec<User>, secs: &Vec<Security>) -> CustomResult<Vec<Account>> {
|
||||||
let mut ret: Vec<Account> = Vec::new();
|
let mut ret: Vec<Account> = Vec::new();
|
||||||
for sec in secs.iter() {
|
for sec in secs.iter() {
|
||||||
for user in users.iter() {
|
for user in users.iter() {
|
||||||
@@ -182,12 +272,12 @@ mod test_accounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplest_checking_account() -> Result<()> {
|
fn test_simplest_checking_account() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
let user = users.get(0).unwrap();
|
let user = users.get(0).unwrap();
|
||||||
let not_owner = users.get(1).unwrap();
|
let not_owner = users.get(1).unwrap();
|
||||||
let sec = secs.get(0).unwrap();
|
let sec = secs.get(0).unwrap();
|
||||||
let tx = conn.transaction()?;
|
let tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let acc = create_checking_account(&tx, &sec.id(), &user.id())?;
|
let acc = create_checking_account(&tx, &sec.id(), &user.id())?;
|
||||||
assert_eq!(acc.account_type, AccountType::Checking);
|
assert_eq!(acc.account_type, AccountType::Checking);
|
||||||
@@ -195,16 +285,18 @@ mod test_accounts {
|
|||||||
|
|
||||||
assert!(is_owner(&tx, &acc.id(), &user.id())?);
|
assert!(is_owner(&tx, &acc.id(), &user.id())?);
|
||||||
assert!(!is_owner(&tx, &acc.id(), ¬_owner.id())?);
|
assert!(!is_owner(&tx, &acc.id(), ¬_owner.id())?);
|
||||||
|
|
||||||
|
assert_eq!(get_account_history(&tx, &acc.id())?.len(), 1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplest_security_emitter_account() -> Result<()> {
|
fn test_simplest_security_emitter_account() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
let user = users.get(0).unwrap();
|
let user = users.get(0).unwrap();
|
||||||
let not_owner = users.get(1).unwrap();
|
let not_owner = users.get(1).unwrap();
|
||||||
let sec = secs.get(0).unwrap();
|
let sec = secs.get(0).unwrap();
|
||||||
let tx = conn.transaction()?;
|
let tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let acc = create_security_emitter_account(&tx, &sec.id(), &user.id())?;
|
let acc = create_security_emitter_account(&tx, &sec.id(), &user.id())?;
|
||||||
assert_eq!(acc.account_type, AccountType::SecurityEmitter);
|
assert_eq!(acc.account_type, AccountType::SecurityEmitter);
|
||||||
@@ -216,30 +308,30 @@ mod test_accounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_happy_path() -> Result<()> {
|
fn test_happy_path() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
let tx = conn.transaction()?;
|
let tx = conn.transaction().unwrap();
|
||||||
create_all_accounts(&tx, &users, &secs)?;
|
create_all_accounts(&tx, &users, &secs)?;
|
||||||
tx.commit()?;
|
tx.commit().unwrap();
|
||||||
let accs = list_accounts(&conn)?;
|
let accs = list_accounts(&conn).unwrap();
|
||||||
assert_eq!(4, accs.len());
|
assert_eq!(4, accs.len());
|
||||||
assert_eq!(1, accs.get(0).unwrap().id);
|
assert_eq!(1, accs.get(0).unwrap().id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_happy_path_no_commit() -> Result<()> {
|
fn test_happy_path_no_commit() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
create_all_accounts(&conn.transaction()?, &users, &secs)?;
|
create_all_accounts(&conn.transaction().unwrap(), &users, &secs)?;
|
||||||
let accs = list_accounts(&conn)?;
|
let accs = list_accounts(&conn)?;
|
||||||
assert_eq!(0, accs.len());
|
assert_eq!(0, accs.len());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_owner() -> Result<()> {
|
fn test_is_owner() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
let tx = &conn.transaction()?;
|
let tx = &conn.transaction().unwrap();
|
||||||
let _accounts = create_all_accounts(tx, &users, &secs)?;
|
let _accounts = create_all_accounts(tx, &users, &secs)?;
|
||||||
let user_1 = users.get(0).unwrap();
|
let user_1 = users.get(0).unwrap();
|
||||||
let user_2 = users.get(1).unwrap();
|
let user_2 = users.get(1).unwrap();
|
||||||
@@ -269,6 +361,8 @@ mod test_accounts {
|
|||||||
let acc = get_account_by_id(&tx2, &acc.id()).unwrap();
|
let acc = get_account_by_id(&tx2, &acc.id()).unwrap();
|
||||||
assert_eq!(acc.timestamp, receipt.timestamp);
|
assert_eq!(acc.timestamp, receipt.timestamp);
|
||||||
assert_eq!(acc.amount, 33);
|
assert_eq!(acc.amount, 33);
|
||||||
|
assert_eq!(get_account_history(&tx2, &acc.id())?.average_amount(),
|
||||||
|
Fraction::from(0 + 33) / Fraction::from(2));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -279,10 +373,12 @@ mod test_accounts {
|
|||||||
let tx = conn.transaction().unwrap();
|
let tx = conn.transaction().unwrap();
|
||||||
let sec = secs.get(0).unwrap().id();
|
let sec = secs.get(0).unwrap().id();
|
||||||
let user = users.get(0).unwrap().id();
|
let user = users.get(0).unwrap().id();
|
||||||
let acc = create_security_emitter_account(&tx, &sec, &user).unwrap();
|
let acc = create_security_emitter_account(&tx, &sec, &user)?;
|
||||||
|
|
||||||
let receipt = add_to_account(&tx, &acc.id(), 33)?;
|
let receipt = add_to_account(&tx, &acc.id(), 33)?;
|
||||||
assert_eq!(receipt.account_id, acc.id());
|
assert_eq!(receipt.account_id, acc.id());
|
||||||
assert_eq!(receipt.amount, 0);
|
assert_eq!(receipt.amount, 0);
|
||||||
|
assert_eq!(receipt.version, 1);
|
||||||
tx.commit().unwrap();
|
tx.commit().unwrap();
|
||||||
|
|
||||||
let tx2 = conn.transaction().unwrap();
|
let tx2 = conn.transaction().unwrap();
|
||||||
@@ -290,6 +386,7 @@ mod test_accounts {
|
|||||||
assert_eq!(acc.timestamp, receipt.timestamp);
|
assert_eq!(acc.timestamp, receipt.timestamp);
|
||||||
assert_eq!(acc.amount, 0);
|
assert_eq!(acc.amount, 0);
|
||||||
|
|
||||||
|
assert_eq!(get_account_history(&tx2, &acc.id())?.len(), 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::db::_external_id::UserId;
|
|||||||
use crate::db::_securities::{NewSecurity, Security, SecurityType};
|
use crate::db::_securities::{NewSecurity, Security, SecurityType};
|
||||||
use crate::db::_users::User;
|
use crate::db::_users::User;
|
||||||
use crate::db::accounts::Account;
|
use crate::db::accounts::Account;
|
||||||
|
use crate::util::CustomResult;
|
||||||
|
|
||||||
pub fn mock_currency_1() -> NewSecurity {
|
pub fn mock_currency_1() -> NewSecurity {
|
||||||
return NewSecurity {
|
return NewSecurity {
|
||||||
@@ -41,8 +42,8 @@ pub fn setup_users_and_securities(tx: &Transaction) -> Result<(Vec<User>, Vec<Se
|
|||||||
Ok((users, securities))
|
Ok((users, securities))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_basic_environment(tx: &Transaction) -> Result<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
pub fn setup_basic_environment(tx: &Transaction) -> CustomResult<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
||||||
let (users, securities) = setup_users_and_securities(tx)?;
|
let (users, securities) = setup_users_and_securities(tx).unwrap();
|
||||||
|
|
||||||
let acc1 = create_checking_account(tx,
|
let acc1 = create_checking_account(tx,
|
||||||
&securities.get(0).unwrap().id(),
|
&securities.get(0).unwrap().id(),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub mod users {
|
|||||||
|
|
||||||
pub mod accounts {
|
pub mod accounts {
|
||||||
pub use super::_accounts::{Account,
|
pub use super::_accounts::{Account,
|
||||||
|
Receipt,
|
||||||
create_checking_account,
|
create_checking_account,
|
||||||
create_security_emitter_account,
|
create_security_emitter_account,
|
||||||
is_owner,
|
is_owner,
|
||||||
|
|||||||
@@ -8,35 +8,40 @@ use crate::util::*;
|
|||||||
|
|
||||||
fn _move_securities(tx: &Savepoint,
|
fn _move_securities(tx: &Savepoint,
|
||||||
cheque: &Cheque,
|
cheque: &Cheque,
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
account_id: &AccountId) -> CustomResult<(accounts::Receipt, accounts::Receipt)> {
|
||||||
let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?;
|
let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?;
|
||||||
let _payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?;
|
let payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?;
|
||||||
if drawer_receipt.amount < 0 {
|
if drawer_receipt.amount < 0 {
|
||||||
let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque);
|
let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque);
|
||||||
return Err(Error::InvalidAmount(m));
|
return Err(Error::InvalidAmount(m));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok((drawer_receipt, payee_receipt))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
||||||
cheque: &Cheque,
|
cheque: &Cheque,
|
||||||
payee_account_id: &AccountId) -> Result<(), Error> {
|
drawer_receipt: &accounts::Receipt,
|
||||||
|
payee_receipt: &accounts::Receipt) -> Result<(), Error> {
|
||||||
let q = "
|
let q = "
|
||||||
INSERT INTO cashed_cheques(
|
INSERT INTO cashed_cheques(
|
||||||
drawer_user_id,
|
drawer_user_id,
|
||||||
drawer_account_id,
|
drawer_account_id,
|
||||||
|
drawer_account_version,
|
||||||
payee_user_id,
|
payee_user_id,
|
||||||
payee_account_id,
|
payee_account_id,
|
||||||
|
payee_account_version,
|
||||||
cheque_number,
|
cheque_number,
|
||||||
amount,
|
amount,
|
||||||
underlying_security
|
underlying_security
|
||||||
)
|
)
|
||||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||||
";
|
";
|
||||||
let params = params![cheque.drawer_user_id,
|
let params = params![cheque.drawer_user_id,
|
||||||
cheque.drawer_account_id,
|
cheque.drawer_account_id,
|
||||||
|
drawer_receipt.version,
|
||||||
cheque.payee_user_id,
|
cheque.payee_user_id,
|
||||||
payee_account_id,
|
payee_receipt.account_id,
|
||||||
|
payee_receipt.version,
|
||||||
cheque.cheque_number,
|
cheque.cheque_number,
|
||||||
cheque.amount,
|
cheque.amount,
|
||||||
cheque.security_id];
|
cheque.security_id];
|
||||||
@@ -55,16 +60,19 @@ fn cash_cheque_unsafe(savepoint: &Savepoint,
|
|||||||
if cheque.amount < 0 {
|
if cheque.amount < 0 {
|
||||||
return Err(Error::InvalidAmount(format!("The cheque {:?} has a negative amount.", cheque)));
|
return Err(Error::InvalidAmount(format!("The cheque {:?} has a negative amount.", cheque)));
|
||||||
}
|
}
|
||||||
if !accounts::is_owner(&savepoint, &cheque.drawer_account_id, &cheque.drawer_user_id).map_err(Error::SqlError)? {
|
if !accounts::is_owner(&savepoint, &cheque.drawer_account_id, &cheque.drawer_user_id)? {
|
||||||
return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque)));
|
return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id).map_err(Error::SqlError)? {
|
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id)? {
|
||||||
return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \
|
return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \
|
||||||
an account {:?} that is not owned by the payee.", cheque, account_id)));
|
an account {:?} that is not owned by the payee.", cheque, account_id)));
|
||||||
}
|
}
|
||||||
_move_securities(&savepoint, cheque, account_id)?;
|
let (drawer_receipt, payee_receipt) = _move_securities(&savepoint, cheque, account_id)?;
|
||||||
_add_cheque_to_cashed_cheques_log(&savepoint, cheque, account_id)?;
|
_add_cheque_to_cashed_cheques_log(&savepoint,
|
||||||
|
cheque,
|
||||||
|
&drawer_receipt,
|
||||||
|
&payee_receipt)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user