diff options
Diffstat (limited to 'crates/rocie-server/src/storage/sql/insert')
3 files changed, 246 insertions, 3 deletions
diff --git a/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs new file mode 100644 index 0000000..62a2e11 --- /dev/null +++ b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs @@ -0,0 +1,241 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use sqlx::query; +use uuid::Uuid; + +use crate::{ + app::App, + storage::{ + migrate::get_current_date, + sql::{ + barcode::{Barcode, BarcodeId, UnitAmount}, + insert::{Operations, Transactionable}, + unit::Unit, + }, + }, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) enum Operation { + Buy { + buy_id: Uuid, + id: BarcodeId, + }, + Consume { + buy_id: Uuid, + id: BarcodeId, + amount: UnitAmount, + }, +} + +impl Transactionable for Operation { + type ApplyError = apply::Error; + type UndoError = undo::Error; + + async fn apply(self, txn: &mut sqlx::SqliteConnection) -> Result<(), apply::Error> { + match self { + Operation::Buy { buy_id, id } => { + let id = id.to_db(); + let buy_id = buy_id.to_string(); + let timestamp = get_current_date(); + + query!( + " + INSERT INTO buys (buy_id, barcode_id, timestamp) + VALUES (?, ?, ?) +", + buy_id, + id, + timestamp, + ) + .execute(txn) + .await?; + } + Operation::Consume { buy_id, id, amount } => { + let id = id.to_db(); + let buy_id = buy_id.to_string(); + + let old_amount = { + let record = query!( + " + SELECT used_amount + FROM buys + WHERE buy_id = ?; +", + buy_id + ) + .fetch_one(&mut *txn) + .await?; + + u32::try_from(record.used_amount.unwrap_or(0)) + .expect("Should be strictly positive") + }; + + // TODO: Check, that this does not overflow the maximum amount. <2025-09-21> + let new_amount = amount.value + old_amount; + + // TODO(@bpeetz): We need to add the amount. <2025-09-09> + query!( + " + UPDATE buys + SET used_amount = ? + WHERE barcode_id = ? AND buy_id = ? +", + new_amount, + id, + buy_id + ) + .execute(txn) + .await?; + } + } + Ok(()) + } + + async fn undo(self, txn: &mut sqlx::SqliteConnection) -> Result<(), undo::Error> { + match self { + Operation::Buy { buy_id, id } => { + let id = id.to_db(); + let buy_id = buy_id.to_string(); + + query!( + " + DELETE FROM buys + WHERE barcode_id = ? AND buy_id = ? +", + id, + buy_id + ) + .execute(txn) + .await?; + } + Operation::Consume { buy_id, id, amount } => { + todo!("We would need to subtract the amount."); + } + } + Ok(()) + } +} + +pub(crate) mod undo { + #[derive(thiserror::Error, Debug)] + pub(crate) enum Error { + #[error("Failed to execute undo sql statments: {0}")] + SqlError(#[from] sqlx::Error), + } +} +pub(crate) mod apply { + #[derive(thiserror::Error, Debug)] + pub(crate) enum Error { + #[error("Failed to execute apply sql statments: {0}")] + SqlError(#[from] sqlx::Error), + } +} + +impl Barcode { + pub(crate) fn buy(&self, ops: &mut Operations<Operation>) { + let id = Uuid::new_v4(); + ops.push(Operation::Buy { + buy_id: id, + id: self.id, + }); + } + + pub(crate) async fn consume( + &self, + app: &App, + amount: UnitAmount, + ops: &mut Operations<Operation>, + ) -> Result<(), consume::Error> { + assert_eq!( + self.amount.unit, amount.unit, + "We currently do not support unit conversions yet" + ); + + if amount.value > self.amount.value { + let foreign_amount_unit = Unit::from_id(app, amount.unit).await?; + + if let Some(foreign_amount_unit) = foreign_amount_unit { + let self_amount_unit = Unit::from_id(app, self.amount.unit) + .await? + .expect("This unit id should always exist"); + + return Err(consume::Error::ConsumedMoreThanAvailable { + consumed: Box::new((amount, foreign_amount_unit)), + available: Box::new((self.amount, self_amount_unit)), + }); + } + + return Err(consume::Error::UnitIdDoesNotExist(amount.unit)); + } + + let barcode_id = self.id.to_db(); + let buy_id = { + let record = query!( + " + SELECT buy_id + FROM buys + WHERE barcode_id = ? AND (used_amount IS NULL OR used_amount < ?) + ORDER BY timestamp DESC + LIMIT 1; +", + barcode_id, + self.amount.value + ) + .fetch_optional(&app.db) + .await?; + + if let Some(found) = record { + Uuid::from_str(&found.buy_id).expect("Was a uuid, should still be one") + } else { + return Err(consume::Error::NoMoreAvailable); + } + }; + + ops.push(Operation::Consume { + id: self.id, + amount, + buy_id, + }); + + Ok(()) + } +} + +pub(crate) mod consume { + use actix_web::ResponseError; + + use crate::storage::{ + self, + sql::{ + barcode::UnitAmount, + unit::{Unit, UnitId}, + }, + }; + + #[derive(thiserror::Error, Debug)] + pub(crate) enum Error { + #[error("Failed to execute apply sql statments: {0}")] + Sql(#[from] sqlx::Error), + + #[error("Failed to fetch an unit from a specified amount id value")] + UnitGet(#[from] storage::sql::get::unit::from_id::Error), + + #[error("The specified unit-id does not exist: {0}")] + UnitIdDoesNotExist(UnitId), + + #[error("No more of this product is available, buy more. ")] + NoMoreAvailable, + + #[error( + "Consumed more than available: consumed {} {}, but available: {} {}", consumed.0.value, consumed.1.short_name, available.0.value, available.1.short_name, + )] + ConsumedMoreThanAvailable { + consumed: Box<(UnitAmount, Unit)>, + available: Box<(UnitAmount, Unit)>, + }, + } + + impl ResponseError for Error {} +} diff --git a/crates/rocie-server/src/storage/sql/insert/mod.rs b/crates/rocie-server/src/storage/sql/insert/mod.rs index eec6ad2..e6728d9 100644 --- a/crates/rocie-server/src/storage/sql/insert/mod.rs +++ b/crates/rocie-server/src/storage/sql/insert/mod.rs @@ -7,6 +7,7 @@ use log::{debug, trace}; use serde::{Serialize, de::DeserializeOwned}; use sqlx::{SqliteConnection, query}; +pub(crate) mod barcode; pub(crate) mod product; pub(crate) mod unit; diff --git a/crates/rocie-server/src/storage/sql/insert/product/mod.rs b/crates/rocie-server/src/storage/sql/insert/product/mod.rs index b6dd604..e14d3f4 100644 --- a/crates/rocie-server/src/storage/sql/insert/product/mod.rs +++ b/crates/rocie-server/src/storage/sql/insert/product/mod.rs @@ -3,8 +3,9 @@ use sqlx::query; use uuid::Uuid; use crate::storage::sql::{ + barcode::Barcode, insert::{Operations, Transactionable}, - product::{Barcode, Product, ProductId}, + product::{Product, ProductId}, }; #[derive(Debug, Deserialize, Serialize)] @@ -47,7 +48,7 @@ impl Transactionable for Operation { .await?; } Operation::AssociateBarcode { id, barcode } => { - let barcode_id = i64::from(barcode.id); + let barcode_id = barcode.id.to_db(); let barcode_amount_value = i64::from(barcode.amount.value); let barcode_amount_unit = barcode.amount.unit; @@ -90,7 +91,7 @@ impl Transactionable for Operation { .await?; } Operation::AssociateBarcode { id, barcode } => { - let barcode_id = i64::from(barcode.id); + let barcode_id = barcode.id.to_db(); let barcode_amount_value = i64::from(barcode.amount.value); let barcode_amount_unit = barcode.amount.unit; |
