Files
packager/rust/src/models/inventory.rs

459 lines
12 KiB
Rust
Raw Normal View History

2023-08-29 21:34:00 +02:00
use super::Error;
2023-08-29 21:34:00 +02:00
use crate::Context;
2023-08-29 21:34:00 +02:00
use futures::{TryFutureExt, TryStreamExt};
use uuid::Uuid;
pub struct Inventory {
pub categories: Vec<Category>,
}
impl Inventory {
2023-08-29 21:34:00 +02:00
pub async fn load(ctx: &Context, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Self, Error> {
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let mut categories = sqlx::query_as!(
DbCategoryRow,
2023-08-29 21:34:00 +02:00
"SELECT
id,
name,
description
FROM inventory_items_categories
WHERE user_id = ?",
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch(pool)
.map_ok(|row: DbCategoryRow| row.try_into())
.try_collect::<Vec<Result<Category, Error>>>()
.await?
.into_iter()
.collect::<Result<Vec<Category>, Error>>()?;
for category in &mut categories {
2023-08-29 21:34:01 +02:00
category.populate_items(&ctx, &pool).await?;
2023-08-29 21:34:00 +02:00
}
Ok(Self { categories })
}
}
#[derive(Debug)]
pub struct Category {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub items: Option<Vec<Item>>,
}
pub struct DbCategoryRow {
pub id: String,
pub name: String,
pub description: Option<String>,
}
impl TryFrom<DbCategoryRow> for Category {
type Error = Error;
fn try_from(row: DbCategoryRow) -> Result<Self, Self::Error> {
Ok(Category {
id: Uuid::try_parse(&row.id)?,
name: row.name,
description: row.description,
items: None,
})
}
}
impl Category {
pub async fn _find(
2023-08-29 21:34:00 +02:00
ctx: &Context,
2023-08-29 21:34:00 +02:00
pool: &sqlx::Pool<sqlx::Sqlite>,
id: Uuid,
) -> Result<Option<Category>, Error> {
let id_param = id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
sqlx::query_as!(
DbCategoryRow,
"SELECT
id,
name,
description
FROM inventory_items_categories AS category
2023-08-29 21:34:00 +02:00
WHERE
category.id = ?
AND category.user_id = ?",
2023-08-29 21:34:00 +02:00
id_param,
2023-08-29 21:34:00 +02:00
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch_optional(pool)
.await?
.map(|row| row.try_into())
.transpose()
}
2023-08-29 21:34:00 +02:00
pub async fn save(
ctx: &Context,
pool: &sqlx::Pool<sqlx::Sqlite>,
name: &str,
) -> Result<Uuid, Error> {
2023-08-29 21:34:00 +02:00
let id = Uuid::new_v4();
let id_param = id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
sqlx::query!(
"INSERT INTO inventory_items_categories
2023-08-29 21:34:00 +02:00
(id, name, user_id)
2023-08-29 21:34:00 +02:00
VALUES
2023-08-29 21:34:00 +02:00
(?, ?, ?)",
2023-08-29 21:34:00 +02:00
id_param,
name,
2023-08-29 21:34:00 +02:00
user_id,
2023-08-29 21:34:00 +02:00
)
.execute(pool)
.await?;
Ok(id)
}
pub fn items(&self) -> &Vec<Item> {
self.items
.as_ref()
.expect("you need to call populate_items()")
}
pub fn total_weight(&self) -> i64 {
self.items().iter().map(|item| item.weight).sum()
}
2023-08-29 21:34:00 +02:00
pub async fn populate_items(
&mut self,
ctx: &Context,
pool: &sqlx::Pool<sqlx::Sqlite>,
) -> Result<(), Error> {
2023-08-29 21:34:00 +02:00
let id = self.id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let items = sqlx::query_as!(
DbInventoryItemsRow,
"SELECT
id,
name,
weight,
description,
category_id
FROM inventory_items
2023-08-29 21:34:00 +02:00
WHERE
category_id = ?
AND user_id = ?",
id,
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch(pool)
.map_ok(|row| row.try_into())
.try_collect::<Vec<Result<Item, Error>>>()
.await?
.into_iter()
.collect::<Result<Vec<Item>, Error>>()?;
self.items = Some(items);
Ok(())
}
}
pub struct Product {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub comment: Option<String>,
}
2023-08-29 21:34:00 +02:00
pub struct InventoryItem {
2023-08-29 21:34:00 +02:00
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub weight: i64,
pub category: Category,
pub product: Option<Product>,
}
struct DbInventoryItemRow {
pub id: String,
pub name: String,
pub description: Option<String>,
pub weight: i64,
pub category_id: String,
pub category_name: String,
pub category_description: Option<String>,
pub product_id: Option<String>,
pub product_name: Option<String>,
pub product_description: Option<String>,
pub product_comment: Option<String>,
}
impl TryFrom<DbInventoryItemRow> for InventoryItem {
type Error = Error;
fn try_from(row: DbInventoryItemRow) -> Result<Self, Self::Error> {
Ok(InventoryItem {
id: Uuid::try_parse(&row.id)?,
name: row.name,
description: row.description,
weight: row.weight,
category: Category {
id: Uuid::try_parse(&row.category_id)?,
name: row.category_name,
description: row.category_description,
items: None,
},
product: row
.product_id
.map(|id| -> Result<Product, Error> {
Ok(Product {
id: Uuid::try_parse(&id)?,
name: row.product_name.unwrap(),
description: row.product_description,
comment: row.product_comment,
})
})
.transpose()?,
})
}
}
impl InventoryItem {
2023-08-29 21:34:00 +02:00
pub async fn find(
ctx: &Context,
pool: &sqlx::Pool<sqlx::Sqlite>,
id: Uuid,
) -> Result<Option<Self>, Error> {
2023-08-29 21:34:00 +02:00
let id_param = id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
sqlx::query_as!(
DbInventoryItemRow,
"SELECT
item.id AS id,
item.name AS name,
item.description AS description,
weight,
category.id AS category_id,
category.name AS category_name,
category.description AS category_description,
product.id AS product_id,
product.name AS product_name,
product.description AS product_description,
product.comment AS product_comment
FROM inventory_items AS item
INNER JOIN inventory_items_categories as category
ON item.category_id = category.id
LEFT JOIN inventory_products AS product
ON item.product_id = product.id
2023-08-29 21:34:00 +02:00
WHERE
item.id = ?
AND item.user_id = ?",
2023-08-29 21:34:00 +02:00
id_param,
2023-08-29 21:34:00 +02:00
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch_optional(pool)
.await?
.map(|row| row.try_into())
.transpose()
}
2023-08-29 21:34:00 +02:00
pub async fn name_exists(
ctx: &Context,
pool: &sqlx::Pool<sqlx::Sqlite>,
name: &str,
) -> Result<bool, Error> {
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
Ok(sqlx::query!(
"SELECT id
FROM inventory_items
2023-08-29 21:34:00 +02:00
WHERE
name = ?
AND user_id = ?",
2023-08-29 21:34:00 +02:00
name,
2023-08-29 21:34:00 +02:00
user_id
2023-08-29 21:34:00 +02:00
)
.fetch_optional(pool)
.await?
.map(|_row| ())
.is_some())
}
2023-08-29 21:34:00 +02:00
pub async fn delete(
ctx: &Context,
pool: &sqlx::Pool<sqlx::Sqlite>,
id: Uuid,
) -> Result<bool, Error> {
2023-08-29 21:34:00 +02:00
let id_param = id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let results = sqlx::query!(
"DELETE FROM inventory_items
2023-08-29 21:34:00 +02:00
WHERE
id = ?
AND user_id = ?",
id_param,
user_id,
2023-08-29 21:34:00 +02:00
)
.execute(pool)
.await?;
Ok(results.rows_affected() != 0)
}
pub async fn update(
2023-08-29 21:34:00 +02:00
ctx: &Context,
2023-08-29 21:34:00 +02:00
pool: &sqlx::Pool<sqlx::Sqlite>,
id: Uuid,
name: &str,
weight: u32,
) -> Result<Uuid, Error> {
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let weight = i64::try_from(weight).unwrap();
let id_param = id.to_string();
Ok(sqlx::query!(
"UPDATE inventory_items AS item
SET
name = ?,
weight = ?
2023-08-29 21:34:00 +02:00
WHERE
item.id = ?
AND item.user_id = ?
2023-08-29 21:34:00 +02:00
RETURNING inventory_items.category_id AS id
",
name,
weight,
id_param,
2023-08-29 21:34:00 +02:00
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch_one(pool)
2023-08-29 21:34:00 +02:00
.map_ok(|row| Uuid::try_parse(&row.id))
2023-08-29 21:34:00 +02:00
.await??)
}
pub async fn save(
2023-08-29 21:34:00 +02:00
ctx: &Context,
2023-08-29 21:34:00 +02:00
pool: &sqlx::Pool<sqlx::Sqlite>,
name: &str,
category_id: Uuid,
weight: u32,
) -> Result<Uuid, Error> {
let id = Uuid::new_v4();
let id_param = id.to_string();
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let category_id_param = category_id.to_string();
sqlx::query!(
"INSERT INTO inventory_items
2023-08-29 21:34:00 +02:00
(id, name, description, weight, category_id, user_id)
2023-08-29 21:34:00 +02:00
VALUES
2023-08-29 21:34:00 +02:00
(?, ?, ?, ?, ?, ?)",
2023-08-29 21:34:00 +02:00
id_param,
name,
"",
weight,
2023-08-29 21:34:00 +02:00
category_id_param,
user_id,
2023-08-29 21:34:00 +02:00
)
.execute(pool)
.await?;
Ok(id)
}
2023-08-29 21:34:00 +02:00
2023-08-29 21:34:00 +02:00
pub async fn get_category_max_weight(
2023-08-29 21:34:00 +02:00
ctx: &Context,
2023-08-29 21:34:00 +02:00
pool: &sqlx::Pool<sqlx::Sqlite>,
category_id: Uuid,
) -> Result<i64, Error> {
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let category_id_param = category_id.to_string();
let weight = sqlx::query!(
"
SELECT COALESCE(MAX(i_item.weight), 0) as weight
FROM inventory_items_categories as category
INNER JOIN inventory_items as i_item
ON i_item.category_id = category.id
2023-08-29 21:34:00 +02:00
WHERE
category_id = ?
AND category.user_id = ?
2023-08-29 21:34:00 +02:00
",
2023-08-29 21:34:00 +02:00
category_id_param,
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch_one(pool)
.map_ok(|row| {
// convert to i64 because that the default integer type, but looks
// like COALESCE return i32?
2023-08-29 21:34:01 +02:00
i64::from(row.weight)
2023-08-29 21:34:00 +02:00
})
.await?;
Ok(weight)
}
}
2023-08-29 21:34:00 +02:00
#[derive(Debug)]
pub struct Item {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub weight: i64,
pub category_id: Uuid,
}
pub struct DbInventoryItemsRow {
pub id: String,
pub name: String,
pub weight: i64,
pub description: Option<String>,
pub category_id: String,
}
impl TryFrom<DbInventoryItemsRow> for Item {
type Error = Error;
fn try_from(row: DbInventoryItemsRow) -> Result<Self, Self::Error> {
Ok(Item {
id: Uuid::try_parse(&row.id)?,
name: row.name,
description: row.description, // TODO
weight: row.weight,
category_id: Uuid::try_parse(&row.category_id)?,
})
}
}
impl Item {
pub async fn _get_category_total_picked_weight(
2023-08-29 21:34:00 +02:00
ctx: &Context,
2023-08-29 21:34:00 +02:00
pool: &sqlx::Pool<sqlx::Sqlite>,
category_id: Uuid,
) -> Result<i64, Error> {
2023-08-29 21:34:00 +02:00
let user_id = ctx.user.id.to_string();
2023-08-29 21:34:00 +02:00
let category_id_param = category_id.to_string();
Ok(sqlx::query!(
"
SELECT COALESCE(SUM(i_item.weight), 0) as weight
FROM inventory_items_categories as category
INNER JOIN inventory_items as i_item
ON i_item.category_id = category.id
INNER JOIN trips_items as t_item
ON i_item.id = t_item.item_id
2023-08-29 21:34:00 +02:00
WHERE
category_id = ?
AND category.user_id = ?
AND t_item.pick = 1
2023-08-29 21:34:00 +02:00
",
2023-08-29 21:34:00 +02:00
category_id_param,
user_id,
2023-08-29 21:34:00 +02:00
)
.fetch_one(pool)
.map_ok(|row| {
// convert to i64 because that the default integer type, but looks
// like COALESCE return i32?
2023-08-29 21:34:01 +02:00
i64::from(row.weight)
2023-08-29 21:34:00 +02:00
})
.await?)
}
}