mmex_lib 0.1.2-beta.1

Library for interacting with Money Manager EX data and logic
Documentation
use rusqlite::Row;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use sea_query::{Expr, Query, SqliteQueryBuilder};
use std::str::FromStr;

use crate::domain::transactions::{
    SplitRepository, SplitTransaction, TransactionError, TransactionId,
};
use crate::domain::types::{CategoryId, Money};
use crate::infrastructure::db_executor::DbExecutor;

pub struct SplitMapper;

impl SplitMapper {
    pub fn map_row(row: &Row) -> rusqlite::Result<SplitTransaction> {
        let amount_val = if let Ok(val) = row.get::<_, f64>("SPLITTRANSAMOUNT") {
            Decimal::from_f64(val).unwrap_or(Decimal::ZERO)
        } else if let Ok(s) = row.get::<_, String>("SPLITTRANSAMOUNT") {
            Decimal::from_str(&s).unwrap_or(Decimal::ZERO)
        } else {
            Decimal::ZERO
        };

        Ok(SplitTransaction {
            id: row.get("SPLITTRANSID")?,
            transaction_id: TransactionId {
                v1: row.get("TRANSID")?,
            },
            category_id: row
                .get::<_, Option<i64>>("CATEGID")?
                .map(|v1| CategoryId { v1 }),
            amount: Money::from(amount_val),
            notes: row.get("NOTES")?,
        })
    }
}

pub struct SqlSplitRepository<'a, E: DbExecutor> {
    executor: &'a E,
}

impl<'a, E: DbExecutor> SqlSplitRepository<'a, E> {
    pub fn new(executor: &'a E) -> Self {
        Self { executor }
    }
}

impl<'a, E: DbExecutor> SplitRepository for SqlSplitRepository<'a, E> {
    fn find_for_transaction(
        &self,
        tx_id: TransactionId,
    ) -> Result<Vec<SplitTransaction>, TransactionError> {
        let (sql, _) = Query::select()
            .columns([
                "SPLITTRANSID",
                "TRANSID",
                "CATEGID",
                "SPLITTRANSAMOUNT",
                "NOTES",
            ])
            .from_as("SPLITTRANSACTIONS_V1", "s")
            .and_where(Expr::col("TRANSID").eq(tx_id.v1))
            .build(SqliteQueryBuilder);

        Ok(self
            .executor
            .query_map_ext(&sql, [tx_id.v1], |row| SplitMapper::map_row(row))?)
    }

    fn insert(&self, s: &SplitTransaction) -> Result<SplitTransaction, TransactionError> {
        let sql = "INSERT INTO SPLITTRANSACTIONS_V1 (TRANSID, CATEGID, SPLITTRANSAMOUNT, NOTES) VALUES (?, ?, ?, ?)";
        self.executor.execute_ext(
            sql,
            (
                s.transaction_id.v1,
                s.category_id.map(|id| id.v1),
                s.amount.v1.clone(),
                &s.notes,
            ),
        )?;
        let last_id: i64 = self
            .executor
            .query_row_ext("SELECT last_insert_rowid()", [], |r| r.get(0))?;
        let mut new_split = s.clone();
        new_split.id = last_id;
        Ok(new_split)
    }

    fn update(&self, s: &SplitTransaction) -> Result<(), TransactionError> {
        let sql = "UPDATE SPLITTRANSACTIONS_V1 SET TRANSID = ?, CATEGID = ?, SPLITTRANSAMOUNT = ?, NOTES = ? WHERE SPLITTRANSID = ?";
        self.executor.execute_ext(
            sql,
            (
                s.transaction_id.v1,
                s.category_id.map(|id| id.v1),
                s.amount.v1.clone(),
                &s.notes,
                s.id,
            ),
        )?;
        Ok(())
    }

    fn delete(&self, id: i64) -> Result<(), TransactionError> {
        self.executor.execute_ext(
            "DELETE FROM SPLITTRANSACTIONS_V1 WHERE SPLITTRANSID = ?",
            [id],
        )?;
        Ok(())
    }

    fn delete_for_transaction(&self, tx_id: TransactionId) -> Result<(), TransactionError> {
        self.executor.execute_ext(
            "DELETE FROM SPLITTRANSACTIONS_V1 WHERE TRANSID = ?",
            [tx_id.v1],
        )?;
        Ok(())
    }
}