I should be working by now.
This commit is contained in:
1 parent
2715dc20fa
commit
77d0442a3a
3 files changed
+89
-33
No files matched your search
+37
-15
@@ -5,7 +5,7 @@ use crate::db::_external_id::{AccountId, Id, UserId};
|
|||||||
use crate::db::_securities::Security;
|
use crate::db::_securities::Security;
|
||||||
use crate::db::_users::User;
|
use crate::db::_users::User;
|
||||||
use crate::db::external_id::SecurityId;
|
use crate::db::external_id::SecurityId;
|
||||||
use crate::util::{CustomResult, TransactionHelper};
|
use crate::util::{CustomResult, Error, TransactionHelper};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum AccountType {
|
pub enum AccountType {
|
||||||
@@ -73,7 +73,7 @@ pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result<Account> {
|
pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> Result<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,
|
||||||
@@ -88,7 +88,7 @@ pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result<Account> {
|
|||||||
return tx.query_row(q, params![id], Account::from_row);
|
return tx.query_row(q, params![id], Account::from_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId, account_type: AccountType) -> Result<Account> {
|
fn create_account(tx: &Connection, sec: &SecurityId, user: &UserId, account_type: AccountType) -> Result<Account> {
|
||||||
let q = "INSERT INTO accounts(security, amount, timestamp, account_type)
|
let q = "INSERT INTO accounts(security, amount, timestamp, account_type)
|
||||||
VALUES (?1, 0, CURRENT_TIMESTAMP, ?2)";
|
VALUES (?1, 0, CURRENT_TIMESTAMP, ?2)";
|
||||||
let account_type = match account_type {
|
let account_type = match account_type {
|
||||||
@@ -104,15 +104,15 @@ fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId, account_typ
|
|||||||
return get_account_by_id(tx, &id);
|
return get_account_by_id(tx, &id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_checking_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
pub fn create_checking_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
||||||
create_account(tx, sec, user, AccountType::Checking)
|
create_account(tx, sec, user, AccountType::Checking)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_security_emitter_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
pub fn create_security_emitter_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
||||||
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> Result<bool> {
|
pub fn is_owner(tx: &Connection, account_id: &AccountId, user_id: &UserId) -> Result<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);
|
||||||
@@ -121,9 +121,9 @@ pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> R
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Receipt {
|
pub struct Receipt {
|
||||||
account_id: AccountId,
|
pub account_id: AccountId,
|
||||||
timestamp: i64,
|
pub timestamp: i64,
|
||||||
amount: i64,
|
pub amount: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Receipt {
|
impl Receipt {
|
||||||
@@ -136,17 +136,20 @@ impl Receipt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_to_account(tx: &TransactionHelper,
|
pub fn add_to_account(tx: &Connection,
|
||||||
account_id: &AccountId,
|
account_id: &AccountId,
|
||||||
amount: i64) -> CustomResult<Receipt> {
|
amount: i64) -> CustomResult<Receipt> {
|
||||||
let q = "
|
let q = "
|
||||||
UPDATE accounts SET amount = amount + ?1,
|
UPDATE accounts SET amount = CASE account_type
|
||||||
|
WHEN 'security_emitter'
|
||||||
|
THEN amount
|
||||||
|
ELSE amount + ?1 END,
|
||||||
timestamp = CURRENT_TIMESTAMP
|
timestamp = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?2
|
WHERE id = ?2
|
||||||
RETURNING id, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp
|
RETURNING id, 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)
|
tx.query_row(q, params, Receipt::from_row).map_err(Error::SqlError)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -254,14 +257,13 @@ mod test_accounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_to_account() -> CustomResult<()> {
|
fn test_add_to_checking_account() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
let tx = conn.transaction().unwrap();
|
let tx = conn.transaction().unwrap();
|
||||||
let mtx = TransactionHelper::new(&tx);
|
|
||||||
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_checking_account(&tx, &sec, &user).unwrap();
|
let acc = create_checking_account(&tx, &sec, &user).unwrap();
|
||||||
let receipt = add_to_account(&mtx, &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, 33);
|
assert_eq!(receipt.amount, 33);
|
||||||
tx.commit().unwrap();
|
tx.commit().unwrap();
|
||||||
@@ -273,5 +275,25 @@ mod test_accounts {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_to_currency_emitter_account() -> CustomResult<()> {
|
||||||
|
let (mut conn, users, secs) = setup();
|
||||||
|
let tx = conn.transaction().unwrap();
|
||||||
|
let sec = secs.get(0).unwrap().id();
|
||||||
|
let user = users.get(0).unwrap().id();
|
||||||
|
let acc = create_security_emitter_account(&tx, &sec, &user).unwrap();
|
||||||
|
let receipt = add_to_account(&tx, &acc.id(), 33)?;
|
||||||
|
assert_eq!(receipt.account_id, acc.id());
|
||||||
|
assert_eq!(receipt.amount, 0);
|
||||||
|
tx.commit().unwrap();
|
||||||
|
|
||||||
|
let tx2 = conn.transaction().unwrap();
|
||||||
|
let acc = get_account_by_id(&tx2, &acc.id()).unwrap();
|
||||||
|
assert_eq!(acc.timestamp, receipt.timestamp);
|
||||||
|
assert_eq!(acc.amount, 0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{ffi, params, Params, Savepoint, Transaction};
|
use rusqlite::{Connection, ffi, params, Params, Savepoint, Transaction};
|
||||||
|
|
||||||
use crate::db::{accounts, securities};
|
use crate::db::{accounts, securities};
|
||||||
use crate::db::accounts::{Account, get_account_by_id};
|
use crate::db::accounts::{Account, get_account_by_id};
|
||||||
@@ -7,15 +7,19 @@ use crate::db::external_id::AccountId;
|
|||||||
use crate::db::securities::get_security_by_id;
|
use crate::db::securities::get_security_by_id;
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
fn _move_securities(tx: &TransactionHelper,
|
fn _move_securities(tx: &Savepoint,
|
||||||
cheque: &SecurityEmitterCheque,
|
cheque: &SecurityEmitterCheque,
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
account_id: &AccountId) -> CustomResult<()> {
|
||||||
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)?;
|
||||||
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 {
|
||||||
|
let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque);
|
||||||
|
return Err(Error::InvalidAmount(m));
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper,
|
fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
||||||
cheque: &SecurityEmitterCheque,
|
cheque: &SecurityEmitterCheque,
|
||||||
account_id: &AccountId) -> Result<(), Error> {
|
account_id: &AccountId) -> Result<(), Error> {
|
||||||
let q = "
|
let q = "
|
||||||
@@ -30,7 +34,7 @@ fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper,
|
|||||||
let ret = tx.execute(q, params![cheque.drawer_user_id,
|
let ret = tx.execute(q, params![cheque.drawer_user_id,
|
||||||
cheque.cheque_number,
|
cheque.cheque_number,
|
||||||
cheque.amount,
|
cheque.amount,
|
||||||
cheque.security_id]);
|
cheque.security_id]).map_err(Error::SqlError);
|
||||||
match ret {
|
match ret {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(Error::SqlError(rusqlite::Error::SqliteFailure(ffi::Error { code: _, extended_code: 2067 }, _))) =>
|
Err(Error::SqlError(rusqlite::Error::SqliteFailure(ffi::Error { code: _, extended_code: 2067 }, _))) =>
|
||||||
@@ -39,27 +43,41 @@ fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cash_cheque(tx: &Transaction,
|
fn cash_cheque_unsafe(savepoint: &Savepoint,
|
||||||
cheque: &SecurityEmitterCheque,
|
cheque: &SecurityEmitterCheque,
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
account_id: &AccountId) -> CustomResult<()> {
|
||||||
if !accounts::is_owner(&tx, &cheque.drawer_account_id, &cheque.drawer_user_id).map_err(Error::SqlError)? {
|
if cheque.amount < 0 {
|
||||||
|
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)? {
|
||||||
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(&tx, account_id, &cheque.payee_user_id).map_err(Error::SqlError)? {
|
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id).map_err(Error::SqlError)? {
|
||||||
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 myx = TransactionHelper::new(tx);
|
_add_cheque_to_cashed_cheques_log(&savepoint, cheque, account_id)?;
|
||||||
_move_securities(&myx, cheque, account_id)?;
|
|
||||||
_add_cheque_to_cashed_cheques_log(&myx, cheque, account_id)?;
|
|
||||||
|
|
||||||
// savepoint.rollback().map_err(Error::SqlError)?;
|
|
||||||
// create transaction loggetAllPurchaseHistory
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cash_cheque(tx: &mut Transaction,
|
||||||
|
cheque: &SecurityEmitterCheque,
|
||||||
|
account_id: &AccountId) -> CustomResult<()> {
|
||||||
|
let mut savepoint = tx.savepoint().map_err(Error::SqlError)?;
|
||||||
|
let ret = cash_cheque_unsafe(&savepoint, cheque, account_id);
|
||||||
|
match ret {
|
||||||
|
Err(_) => {
|
||||||
|
savepoint.rollback().map_err(Error::SqlError)?;
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
savepoint.commit().map_err(Error::SqlError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_caching_cheques {
|
mod test_caching_cheques {
|
||||||
@@ -115,6 +133,21 @@ mod test_caching_cheques {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_negative_cheque() -> CustomResult<()> {
|
||||||
|
let (mut conn, drawer, payee, payee_account) = setup_basic();
|
||||||
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
|
let cheque = SecurityEmitterCheque::new(
|
||||||
|
drawer, payee, -1000, 99);
|
||||||
|
|
||||||
|
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||||
|
match ret {
|
||||||
|
Err(Error::InvalidAmount(_)) => Ok(()),
|
||||||
|
_ => Err(Error::Testing("Expected double spending error here.".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_double_spend() -> CustomResult<()> {
|
fn test_double_spend() -> CustomResult<()> {
|
||||||
let (mut conn, drawer, payee, payee_account) = setup_basic();
|
let (mut conn, drawer, payee, payee_account) = setup_basic();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use rusqlite::{Params, Row, Transaction};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
SqlError(rusqlite::Error),
|
SqlError(rusqlite::Error),
|
||||||
|
DoubleBooking(String),
|
||||||
InvalidDrawer(String),
|
InvalidDrawer(String),
|
||||||
InvalidAmount(String),
|
InvalidAmount(String),
|
||||||
InvalidPayee(String),
|
InvalidPayee(String),
|
||||||
|
|||||||
Reference in new issue
Block a user