more refactors
This commit is contained in:
155
rust/src/main.rs
155
rust/src/main.rs
@@ -42,7 +42,7 @@ struct Args {
|
||||
pub struct ClientState {
|
||||
pub active_category_id: Option<Uuid>,
|
||||
pub edit_item: Option<Uuid>,
|
||||
pub trip_edit_attribute: Option<models::TripAttribute>,
|
||||
pub trip_edit_attribute: Option<models::trips::TripAttribute>,
|
||||
pub trip_type_edit: Option<Uuid>,
|
||||
}
|
||||
|
||||
@@ -279,9 +279,9 @@ async fn inventory_active(
|
||||
state.client_state.edit_item = inventory_query.edit_item;
|
||||
state.client_state.active_category_id = Some(id);
|
||||
|
||||
let inventory = models::Inventory::load(&state.database_pool).await?;
|
||||
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
||||
|
||||
let active_category: Option<&models::Category> = state
|
||||
let active_category: Option<&models::inventory::Category> = state
|
||||
.client_state
|
||||
.active_category_id
|
||||
.map(|id| {
|
||||
@@ -312,7 +312,7 @@ async fn inventory_inactive(
|
||||
state.client_state.edit_item = inventory_query.edit_item;
|
||||
state.client_state.active_category_id = None;
|
||||
|
||||
let inventory = models::Inventory::load(&state.database_pool).await?;
|
||||
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
||||
|
||||
Ok(view::Root::build(
|
||||
&view::inventory::Inventory::build(
|
||||
@@ -346,7 +346,8 @@ async fn inventory_item_validate_name(
|
||||
State(state): State<AppState>,
|
||||
Form(new_item): Form<NewItemName>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let exists = models::InventoryItem::name_exists(&state.database_pool, &new_item.name).await?;
|
||||
let exists =
|
||||
models::inventory::InventoryItem::name_exists(&state.database_pool, &new_item.name).await?;
|
||||
|
||||
Ok(view::inventory::InventoryNewItemFormName::build(
|
||||
Some(&new_item.name),
|
||||
@@ -365,7 +366,7 @@ async fn inventory_item_create(
|
||||
}));
|
||||
}
|
||||
|
||||
let _new_id = models::InventoryItem::save(
|
||||
let _new_id = models::inventory::InventoryItem::save(
|
||||
&state.database_pool,
|
||||
&new_item.name,
|
||||
new_item.category_id,
|
||||
@@ -374,11 +375,11 @@ async fn inventory_item_create(
|
||||
.await?;
|
||||
|
||||
if is_htmx(&headers) {
|
||||
let inventory = models::Inventory::load(&state.database_pool).await?;
|
||||
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
||||
|
||||
// it's impossible to NOT find the item here, as we literally just added
|
||||
// it.
|
||||
let active_category: Option<&models::Category> = Some(
|
||||
let active_category: Option<&models::inventory::Category> = Some(
|
||||
inventory
|
||||
.categories
|
||||
.iter()
|
||||
@@ -418,7 +419,7 @@ async fn inventory_item_delete(
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Redirect, Error> {
|
||||
let deleted = models::InventoryItem::delete(&state.database_pool, id).await?;
|
||||
let deleted = models::inventory::InventoryItem::delete(&state.database_pool, id).await?;
|
||||
|
||||
if !deleted {
|
||||
Err(Error::Request(RequestError::NotFound {
|
||||
@@ -448,8 +449,13 @@ async fn inventory_item_edit(
|
||||
}));
|
||||
}
|
||||
|
||||
let id =
|
||||
models::Item::update(&state.database_pool, id, &edit_item.name, edit_item.weight).await?;
|
||||
let id = models::inventory::InventoryItem::update(
|
||||
&state.database_pool,
|
||||
id,
|
||||
&edit_item.name,
|
||||
edit_item.weight,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Redirect::to(&format!("/inventory/category/{id}/", id = id)))
|
||||
}
|
||||
@@ -458,7 +464,7 @@ async fn inventory_item_cancel(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Redirect, Error> {
|
||||
let id = models::Item::find(&state.database_pool, id)
|
||||
let id = models::inventory::InventoryItem::find(&state.database_pool, id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("item with id {id} not found"),
|
||||
@@ -466,7 +472,7 @@ async fn inventory_item_cancel(
|
||||
|
||||
Ok(Redirect::to(&format!(
|
||||
"/inventory/category/{id}/",
|
||||
id = id.category_id
|
||||
id = id.category.id
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -490,7 +496,7 @@ async fn trip_create(
|
||||
}));
|
||||
}
|
||||
|
||||
let new_id = models::Trip::save(
|
||||
let new_id = models::trips::Trip::save(
|
||||
&state.database_pool,
|
||||
&new_trip.name,
|
||||
new_trip.date_start,
|
||||
@@ -502,7 +508,7 @@ async fn trip_create(
|
||||
}
|
||||
|
||||
async fn trips(State(state): State<AppState>) -> Result<impl IntoResponse, Error> {
|
||||
let trips = models::Trip::all(&state.database_pool).await?;
|
||||
let trips = models::trips::Trip::all(&state.database_pool).await?;
|
||||
|
||||
Ok(view::Root::build(
|
||||
&view::trip::TripManager::build(trips),
|
||||
@@ -512,7 +518,7 @@ async fn trips(State(state): State<AppState>) -> Result<impl IntoResponse, Error
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TripQuery {
|
||||
edit: Option<models::TripAttribute>,
|
||||
edit: Option<models::trips::TripAttribute>,
|
||||
category: Option<Uuid>,
|
||||
}
|
||||
|
||||
@@ -524,12 +530,11 @@ async fn trip(
|
||||
state.client_state.trip_edit_attribute = trip_query.edit;
|
||||
state.client_state.active_category_id = trip_query.category;
|
||||
|
||||
let mut trip: models::Trip =
|
||||
models::Trip::find(&state.database_pool, id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("trip with id {id} not found"),
|
||||
}))?;
|
||||
let mut trip: models::trips::Trip = models::trips::Trip::find(&state.database_pool, id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("trip with id {id} not found"),
|
||||
}))?;
|
||||
|
||||
trip.load_trips_types(&state.database_pool).await?;
|
||||
|
||||
@@ -538,7 +543,7 @@ async fn trip(
|
||||
|
||||
trip.load_categories(&state.database_pool).await?;
|
||||
|
||||
let active_category: Option<&models::TripCategory> = state
|
||||
let active_category: Option<&models::trips::TripCategory> = state
|
||||
.client_state
|
||||
.active_category_id
|
||||
.map(|id| {
|
||||
@@ -565,7 +570,8 @@ async fn trip_type_remove(
|
||||
State(state): State<AppState>,
|
||||
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Redirect, Error> {
|
||||
let found = models::Trip::trip_type_remove(&state.database_pool, trip_id, type_id).await?;
|
||||
let found =
|
||||
models::trips::Trip::trip_type_remove(&state.database_pool, trip_id, type_id).await?;
|
||||
|
||||
if !found {
|
||||
Err(Error::Request(RequestError::NotFound {
|
||||
@@ -580,7 +586,7 @@ async fn trip_type_add(
|
||||
State(state): State<AppState>,
|
||||
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Redirect, Error> {
|
||||
models::Trip::trip_type_add(&state.database_pool, trip_id, type_id).await?;
|
||||
models::trips::Trip::trip_type_add(&state.database_pool, trip_id, type_id).await?;
|
||||
|
||||
Ok(Redirect::to(&format!("/trips/{trip_id}/")))
|
||||
}
|
||||
@@ -596,9 +602,12 @@ async fn trip_comment_set(
|
||||
Path(trip_id): Path<Uuid>,
|
||||
Form(comment_update): Form<CommentUpdate>,
|
||||
) -> Result<Redirect, Error> {
|
||||
let found =
|
||||
models::Trip::set_comment(&state.database_pool, trip_id, &comment_update.new_comment)
|
||||
.await?;
|
||||
let found = models::trips::Trip::set_comment(
|
||||
&state.database_pool,
|
||||
trip_id,
|
||||
&comment_update.new_comment,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !found {
|
||||
Err(Error::Request(RequestError::NotFound {
|
||||
@@ -617,17 +626,17 @@ struct TripUpdate {
|
||||
|
||||
async fn trip_edit_attribute(
|
||||
State(state): State<AppState>,
|
||||
Path((trip_id, attribute)): Path<(Uuid, models::TripAttribute)>,
|
||||
Path((trip_id, attribute)): Path<(Uuid, models::trips::TripAttribute)>,
|
||||
Form(trip_update): Form<TripUpdate>,
|
||||
) -> Result<Redirect, Error> {
|
||||
if attribute == models::TripAttribute::Name {
|
||||
if attribute == models::trips::TripAttribute::Name {
|
||||
if trip_update.new_value.is_empty() {
|
||||
return Err(Error::Request(RequestError::EmptyFormElement {
|
||||
name: "name".to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
models::Trip::set_attribute(
|
||||
models::trips::Trip::set_attribute(
|
||||
&state.database_pool,
|
||||
trip_id,
|
||||
attribute,
|
||||
@@ -642,10 +651,10 @@ async fn trip_item_set_state(
|
||||
state: &AppState,
|
||||
trip_id: Uuid,
|
||||
item_id: Uuid,
|
||||
key: models::TripItemStateKey,
|
||||
key: models::trips::TripItemStateKey,
|
||||
value: bool,
|
||||
) -> Result<(), Error> {
|
||||
models::TripItem::set_state(&state.database_pool, trip_id, item_id, key, value).await?;
|
||||
models::trips::TripItem::set_state(&state.database_pool, trip_id, item_id, key, value).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -654,7 +663,7 @@ async fn trip_row(
|
||||
trip_id: Uuid,
|
||||
item_id: Uuid,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let item = models::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
let item = models::trips::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::Request(RequestError::NotFound {
|
||||
@@ -665,16 +674,21 @@ async fn trip_row(
|
||||
let item_row = view::trip::TripItemListRow::build(
|
||||
trip_id,
|
||||
&item,
|
||||
models::Item::get_category_max_weight(&state.database_pool, item.item.category_id).await?,
|
||||
models::inventory::Item::get_category_max_weight(
|
||||
&state.database_pool,
|
||||
item.item.category_id,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let category = models::TripCategory::find(&state.database_pool, trip_id, item.item.category_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::Request(RequestError::NotFound {
|
||||
message: format!("category with id {} not found", item.item.category_id),
|
||||
})
|
||||
})?;
|
||||
let category =
|
||||
models::trips::TripCategory::find(&state.database_pool, trip_id, item.item.category_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::Request(RequestError::NotFound {
|
||||
message: format!("category with id {} not found", item.item.category_id),
|
||||
})
|
||||
})?;
|
||||
|
||||
// TODO biggest_category_weight?
|
||||
let category_row = view::trip::TripCategoryListRow::build(trip_id, &category, true, 0, true);
|
||||
@@ -692,7 +706,7 @@ async fn trip_item_set_pick(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pick,
|
||||
models::trips::TripItemStateKey::Pick,
|
||||
true,
|
||||
)
|
||||
.await?,
|
||||
@@ -708,7 +722,7 @@ async fn trip_item_set_pick_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pick,
|
||||
models::trips::TripItemStateKey::Pick,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
@@ -730,7 +744,7 @@ async fn trip_item_set_unpick(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pick,
|
||||
models::trips::TripItemStateKey::Pick,
|
||||
false,
|
||||
)
|
||||
.await?,
|
||||
@@ -746,7 +760,7 @@ async fn trip_item_set_unpick_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pick,
|
||||
models::trips::TripItemStateKey::Pick,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -768,7 +782,7 @@ async fn trip_item_set_pack(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
true,
|
||||
)
|
||||
.await?,
|
||||
@@ -784,7 +798,7 @@ async fn trip_item_set_pack_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
@@ -806,7 +820,7 @@ async fn trip_item_set_unpack(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
false,
|
||||
)
|
||||
.await?,
|
||||
@@ -822,7 +836,7 @@ async fn trip_item_set_unpack_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -839,7 +853,7 @@ async fn trip_total_weight_htmx(
|
||||
Path(trip_id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let total_weight =
|
||||
models::Trip::find_total_picked_weight(&state.database_pool, trip_id).await?;
|
||||
models::trips::Trip::find_total_picked_weight(&state.database_pool, trip_id).await?;
|
||||
Ok(view::trip::TripInfoTotalWeightRow::build(
|
||||
trip_id,
|
||||
total_weight,
|
||||
@@ -862,7 +876,8 @@ async fn inventory_category_create(
|
||||
}));
|
||||
}
|
||||
|
||||
let _new_id = models::Category::save(&state.database_pool, &new_category.name).await?;
|
||||
let _new_id =
|
||||
models::inventory::Category::save(&state.database_pool, &new_category.name).await?;
|
||||
|
||||
Ok(Redirect::to("/inventory/"))
|
||||
}
|
||||
@@ -870,9 +885,9 @@ async fn inventory_category_create(
|
||||
async fn trip_state_set(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path((trip_id, new_state)): Path<(Uuid, models::TripState)>,
|
||||
Path((trip_id, new_state)): Path<(Uuid, models::trips::TripState)>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let exists = models::Trip::set_state(&state.database_pool, trip_id, &new_state).await?;
|
||||
let exists = models::trips::Trip::set_state(&state.database_pool, trip_id, &new_state).await?;
|
||||
|
||||
if !exists {
|
||||
return Err(Error::Request(RequestError::NotFound {
|
||||
@@ -905,7 +920,8 @@ async fn trips_types(
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
state.client_state.trip_type_edit = trip_type_query.edit;
|
||||
|
||||
let trip_types: Vec<models::TripsType> = models::TripsType::all(&state.database_pool).await?;
|
||||
let trip_types: Vec<models::trips::TripsType> =
|
||||
models::trips::TripsType::all(&state.database_pool).await?;
|
||||
|
||||
Ok(view::Root::build(
|
||||
&view::trip::types::TypeList::build(&state.client_state, trip_types),
|
||||
@@ -929,7 +945,7 @@ async fn trip_type_create(
|
||||
}));
|
||||
}
|
||||
|
||||
let _new_id = models::TripsType::save(&state.database_pool, &new_trip_type.name).await?;
|
||||
let _new_id = models::trips::TripsType::save(&state.database_pool, &new_trip_type.name).await?;
|
||||
|
||||
Ok(Redirect::to("/trips/types/"))
|
||||
}
|
||||
@@ -951,9 +967,12 @@ async fn trips_types_edit_name(
|
||||
}));
|
||||
}
|
||||
|
||||
let exists =
|
||||
models::TripsType::set_name(&state.database_pool, trip_type_id, &trip_update.new_value)
|
||||
.await?;
|
||||
let exists = models::trips::TripsType::set_name(
|
||||
&state.database_pool,
|
||||
trip_type_id,
|
||||
&trip_update.new_value,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !exists {
|
||||
return Err(Error::Request(RequestError::NotFound {
|
||||
@@ -968,7 +987,7 @@ async fn inventory_item(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let item = models::InventoryItem::find(&state.database_pool, id)
|
||||
let item = models::inventory::InventoryItem::find(&state.database_pool, id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("inventory item with id {id} not found"),
|
||||
@@ -984,7 +1003,7 @@ async fn trip_category_select(
|
||||
State(state): State<AppState>,
|
||||
Path((trip_id, category_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut trip = models::Trip::find(&state.database_pool, trip_id)
|
||||
let mut trip = models::trips::Trip::find(&state.database_pool, trip_id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("trip with id {trip_id} not found"),
|
||||
@@ -1016,9 +1035,9 @@ async fn inventory_category_select(
|
||||
State(state): State<AppState>,
|
||||
Path(category_id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let inventory = models::Inventory::load(&state.database_pool).await?;
|
||||
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
||||
|
||||
let active_category: Option<&models::Category> = Some(
|
||||
let active_category: Option<&models::inventory::Category> = Some(
|
||||
inventory
|
||||
.categories
|
||||
.iter()
|
||||
@@ -1050,7 +1069,7 @@ async fn trip_packagelist(
|
||||
State(state): State<AppState>,
|
||||
Path(trip_id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut trip = models::Trip::find(&state.database_pool, trip_id)
|
||||
let mut trip = models::trips::Trip::find(&state.database_pool, trip_id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("trip with id {trip_id} not found"),
|
||||
@@ -1072,12 +1091,12 @@ async fn trip_item_packagelist_set_pack_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let item = models::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
let item = models::trips::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("an item with id {item_id} does not exist"),
|
||||
@@ -1096,14 +1115,14 @@ async fn trip_item_packagelist_set_unpack_htmx(
|
||||
&state,
|
||||
trip_id,
|
||||
item_id,
|
||||
models::TripItemStateKey::Pack,
|
||||
models::trips::TripItemStateKey::Pack,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// note that this cannot fail due to a missing item, as trip_item_set_state would already
|
||||
// return 404. but error handling cannot hurt ;)
|
||||
let item = models::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
let item = models::trips::TripItem::find(&state.database_pool, trip_id, item_id)
|
||||
.await?
|
||||
.ok_or(Error::Request(RequestError::NotFound {
|
||||
message: format!("an item with id {item_id} does not exist"),
|
||||
|
||||
412
rust/src/models/inventory.rs
Normal file
412
rust/src/models/inventory.rs
Normal file
@@ -0,0 +1,412 @@
|
||||
use super::Error;
|
||||
|
||||
use futures::{TryFutureExt, TryStreamExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct Inventory {
|
||||
pub categories: Vec<Category>,
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
pub async fn load(pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Self, Error> {
|
||||
let mut categories = sqlx::query_as!(
|
||||
DbCategoryRow,
|
||||
"SELECT id,name,description FROM inventory_items_categories"
|
||||
)
|
||||
.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 {
|
||||
category.populate_items(pool).await?;
|
||||
}
|
||||
|
||||
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(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
) -> Result<Option<Category>, Error> {
|
||||
let id_param = id.to_string();
|
||||
sqlx::query_as!(
|
||||
DbCategoryRow,
|
||||
"SELECT
|
||||
id,
|
||||
name,
|
||||
description
|
||||
FROM inventory_items_categories AS category
|
||||
WHERE category.id = ?",
|
||||
id_param,
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.map(|row| row.try_into())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn save(pool: &sqlx::Pool<sqlx::Sqlite>, name: &str) -> Result<Uuid, Error> {
|
||||
let id = Uuid::new_v4();
|
||||
let id_param = id.to_string();
|
||||
sqlx::query!(
|
||||
"INSERT INTO inventory_items_categories
|
||||
(id, name)
|
||||
VALUES
|
||||
(?, ?)",
|
||||
id_param,
|
||||
name,
|
||||
)
|
||||
.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()
|
||||
}
|
||||
|
||||
pub async fn populate_items(&mut self, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<(), Error> {
|
||||
let id = self.id.to_string();
|
||||
let items = sqlx::query_as!(
|
||||
DbInventoryItemsRow,
|
||||
"SELECT
|
||||
id,
|
||||
name,
|
||||
weight,
|
||||
description,
|
||||
category_id
|
||||
FROM inventory_items
|
||||
WHERE category_id = ?",
|
||||
id
|
||||
)
|
||||
.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>,
|
||||
}
|
||||
|
||||
pub struct inventoryItem {
|
||||
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 {
|
||||
pub async fn find(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<Option<Self>, Error> {
|
||||
let id_param = id.to_string();
|
||||
|
||||
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
|
||||
WHERE item.id = ?",
|
||||
id_param,
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.map(|row| row.try_into())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn name_exists(pool: &sqlx::Pool<sqlx::Sqlite>, name: &str) -> Result<bool, Error> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT id
|
||||
FROM inventory_items
|
||||
WHERE name = ?",
|
||||
name,
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.map(|_row| ())
|
||||
.is_some())
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<bool, Error> {
|
||||
let id_param = id.to_string();
|
||||
let results = sqlx::query!(
|
||||
"DELETE FROM inventory_items
|
||||
WHERE id = ?",
|
||||
id_param
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(results.rows_affected() != 0)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
name: &str,
|
||||
weight: u32,
|
||||
) -> Result<Uuid, Error> {
|
||||
let weight = i64::try_from(weight).unwrap();
|
||||
|
||||
let id_param = id.to_string();
|
||||
Ok(sqlx::query!(
|
||||
"UPDATE inventory_items AS item
|
||||
SET
|
||||
name = ?,
|
||||
weight = ?
|
||||
WHERE item.id = ?
|
||||
RETURNING inventory_items.category_id AS id
|
||||
",
|
||||
name,
|
||||
weight,
|
||||
id_param,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.map_ok(|row| Uuid::try_parse(&row.id.unwrap()))
|
||||
.await??)
|
||||
}
|
||||
|
||||
pub async fn save(
|
||||
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();
|
||||
let category_id_param = category_id.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO inventory_items
|
||||
(id, name, description, weight, category_id)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)",
|
||||
id_param,
|
||||
name,
|
||||
"",
|
||||
weight,
|
||||
category_id_param
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
pub async fn get_category_max_weight(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
category_id: Uuid,
|
||||
) -> Result<i64, Error> {
|
||||
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
|
||||
WHERE category_id = ?
|
||||
",
|
||||
category_id_param
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.map_ok(|row| {
|
||||
// convert to i64 because that the default integer type, but looks
|
||||
// like COALESCE return i32?
|
||||
//
|
||||
// We can be certain that the row exists, as we COALESCE it
|
||||
row.weight.unwrap() as i64
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(weight)
|
||||
}
|
||||
}
|
||||
|
||||
// #[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 find(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<Option<Item>, Error> {
|
||||
// let id_param = id.to_string();
|
||||
// sqlx::query_as!(
|
||||
// DbInventoryItemsRow,
|
||||
// "SELECT
|
||||
// id,
|
||||
// name,
|
||||
// weight,
|
||||
// description,
|
||||
// category_id
|
||||
// FROM inventory_items AS item
|
||||
// WHERE item.id = ?",
|
||||
// id_param,
|
||||
// )
|
||||
// .fetch_optional(pool)
|
||||
// .await?
|
||||
// .map(|row| row.try_into())
|
||||
// .transpose()
|
||||
// }
|
||||
|
||||
// pub async fn _get_category_total_picked_weight(
|
||||
// pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
// category_id: Uuid,
|
||||
// ) -> Result<i64, Error> {
|
||||
// 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
|
||||
// WHERE category_id = ?
|
||||
// AND t_item.pick = 1
|
||||
// ",
|
||||
// category_id_param
|
||||
// )
|
||||
// .fetch_one(pool)
|
||||
// .map_ok(|row| {
|
||||
// // convert to i64 because that the default integer type, but looks
|
||||
// // like COALESCE return i32?
|
||||
// //
|
||||
// // We can be certain that the row exists, as we COALESCE it
|
||||
// row.weight.unwrap() as i64
|
||||
// })
|
||||
// .await?)
|
||||
// }
|
||||
// }
|
||||
File diff suppressed because it is too large
Load Diff
951
rust/src/models/trips.rs
Normal file
951
rust/src/models/trips.rs
Normal file
@@ -0,0 +1,951 @@
|
||||
use std::fmt;
|
||||
|
||||
use super::{
|
||||
consts,
|
||||
error::{DatabaseError, Error, QueryError},
|
||||
inventory,
|
||||
};
|
||||
|
||||
use futures::{TryFutureExt, TryStreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_variant::to_variant_name;
|
||||
use time;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(sqlx::Type, PartialEq, PartialOrd, Deserialize)]
|
||||
pub enum TripState {
|
||||
Init,
|
||||
Planning,
|
||||
Planned,
|
||||
Active,
|
||||
Review,
|
||||
Done,
|
||||
}
|
||||
|
||||
impl TripState {
|
||||
pub fn new() -> Self {
|
||||
TripState::Init
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Option<Self> {
|
||||
match self {
|
||||
Self::Init => Some(Self::Planning),
|
||||
Self::Planning => Some(Self::Planned),
|
||||
Self::Planned => Some(Self::Active),
|
||||
Self::Active => Some(Self::Review),
|
||||
Self::Review => Some(Self::Done),
|
||||
Self::Done => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&self) -> Option<Self> {
|
||||
match self {
|
||||
Self::Init => None,
|
||||
Self::Planning => Some(Self::Init),
|
||||
Self::Planned => Some(Self::Planning),
|
||||
Self::Active => Some(Self::Planned),
|
||||
Self::Review => Some(Self::Active),
|
||||
Self::Done => Some(Self::Review),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TripState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Init => "Init",
|
||||
Self::Planning => "Planning",
|
||||
Self::Planned => "Planned",
|
||||
Self::Active => "Active",
|
||||
Self::Review => "Review",
|
||||
Self::Done => "Done",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&str> for TripState {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
"Init" => Self::Init,
|
||||
"Planning" => Self::Planning,
|
||||
"Planned" => Self::Planned,
|
||||
"Active" => Self::Active,
|
||||
"Review" => Self::Review,
|
||||
"Done" => Self::Done,
|
||||
_ => {
|
||||
return Err(Error::Database(DatabaseError::Enum {
|
||||
description: format!("{value} is not a valid value for TripState"),
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub enum TripItemStateKey {
|
||||
Pick,
|
||||
Pack,
|
||||
}
|
||||
|
||||
impl fmt::Display for TripItemStateKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Pick => "pick",
|
||||
Self::Pack => "pack",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TripCategory {
|
||||
pub category: inventory::Category,
|
||||
pub items: Option<Vec<TripItem>>,
|
||||
}
|
||||
|
||||
impl TripCategory {
|
||||
pub fn total_picked_weight(&self) -> i64 {
|
||||
self.items
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|item| item.picked)
|
||||
.map(|item| item.item.weight)
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub async fn find(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
category_id: Uuid,
|
||||
) -> Result<Option<TripCategory>, Error> {
|
||||
let mut category: Option<TripCategory> = None;
|
||||
|
||||
let trip_id_param = trip_id.to_string();
|
||||
let category_id_param = category_id.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description AS category_description,
|
||||
inner.trip_id AS trip_id,
|
||||
inner.item_id AS item_id,
|
||||
inner.item_name AS item_name,
|
||||
inner.item_description AS item_description,
|
||||
inner.item_weight AS item_weight,
|
||||
inner.item_is_picked AS item_is_picked,
|
||||
inner.item_is_packed AS item_is_packed,
|
||||
inner.item_is_new AS item_is_new
|
||||
FROM inventory_items_categories AS category
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
trip.trip_id AS trip_id,
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description as category_description,
|
||||
item.id as item_id,
|
||||
item.name as item_name,
|
||||
item.description as item_description,
|
||||
item.weight as item_weight,
|
||||
trip.pick as item_is_picked,
|
||||
trip.pack as item_is_packed,
|
||||
trip.new as item_is_new
|
||||
FROM trips_items as trip
|
||||
INNER JOIN inventory_items as item
|
||||
ON item.id = trip.item_id
|
||||
INNER JOIN inventory_items_categories as category
|
||||
ON category.id = item.category_id
|
||||
WHERE trip.trip_id = ?
|
||||
) AS inner
|
||||
ON inner.category_id = category.id
|
||||
WHERE category.id = ?
|
||||
",
|
||||
trip_id_param,
|
||||
category_id_param
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| -> Result<(), Error> {
|
||||
match &category {
|
||||
Some(_) => (),
|
||||
None => {
|
||||
category = Some(TripCategory {
|
||||
category: inventory::Category {
|
||||
id: Uuid::try_parse(&row.category_id)?,
|
||||
name: row.category_name,
|
||||
description: row.category_description,
|
||||
items: None,
|
||||
},
|
||||
items: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
match row.item_id {
|
||||
None => {
|
||||
// we have an empty (unused) category which has NULL values
|
||||
// for the item_id column
|
||||
category.as_mut().unwrap().items = Some(vec![]);
|
||||
category.as_mut().unwrap().category.items = Some(vec![]);
|
||||
}
|
||||
Some(item_id) => {
|
||||
let item = TripItem {
|
||||
item: inventory::Item {
|
||||
id: Uuid::try_parse(&item_id)?,
|
||||
name: row.item_name.unwrap(),
|
||||
description: row.item_description,
|
||||
weight: row.item_weight.unwrap(),
|
||||
category_id: category.as_ref().unwrap().category.id,
|
||||
},
|
||||
picked: row.item_is_picked.unwrap(),
|
||||
packed: row.item_is_packed.unwrap(),
|
||||
new: row.item_is_new.unwrap(),
|
||||
};
|
||||
|
||||
match &mut category.as_mut().unwrap().items {
|
||||
None => category.as_mut().unwrap().items = Some(vec![item]),
|
||||
Some(ref mut items) => items.push(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.try_collect::<Vec<Result<(), Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<(), Error>>()?;
|
||||
|
||||
// this may be None if there are no results (which
|
||||
// means that the category was not found)
|
||||
Ok(category)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TripItem {
|
||||
pub item: inventory::Item,
|
||||
pub picked: bool,
|
||||
pub packed: bool,
|
||||
pub new: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct DbTripsItemsRow {
|
||||
pub(crate) picked: bool,
|
||||
pub(crate) packed: bool,
|
||||
pub(crate) new: bool,
|
||||
pub(crate) id: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) weight: i64,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) category_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<DbTripsItemsRow> for TripItem {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(row: DbTripsItemsRow) -> Result<Self, Self::Error> {
|
||||
Ok(TripItem {
|
||||
picked: row.picked,
|
||||
packed: row.packed,
|
||||
new: row.new,
|
||||
item: inventory::Item {
|
||||
id: Uuid::try_parse(&row.id)?,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
weight: row.weight,
|
||||
category_id: Uuid::try_parse(&row.category_id)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TripItem {
|
||||
pub async fn find(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
item_id: Uuid,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let item_id_param = item_id.to_string();
|
||||
let trip_id_param = trip_id.to_string();
|
||||
sqlx::query_as!(
|
||||
DbTripsItemsRow,
|
||||
"
|
||||
SELECT
|
||||
t_item.item_id AS id,
|
||||
t_item.pick AS picked,
|
||||
t_item.pack AS packed,
|
||||
t_item.new AS new,
|
||||
i_item.name AS name,
|
||||
i_item.description AS description,
|
||||
i_item.weight AS weight,
|
||||
i_item.category_id AS category_id
|
||||
FROM trips_items AS t_item
|
||||
INNER JOIN inventory_items AS i_item
|
||||
ON i_item.id = t_item.item_id
|
||||
WHERE t_item.item_id = ?
|
||||
AND t_item.trip_id = ?
|
||||
",
|
||||
item_id_param,
|
||||
trip_id_param,
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.map(|row| row.try_into())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn set_state(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
item_id: Uuid,
|
||||
key: TripItemStateKey,
|
||||
value: bool,
|
||||
) -> Result<(), Error> {
|
||||
let result = sqlx::query(&format!(
|
||||
"UPDATE trips_items
|
||||
SET {key} = ?
|
||||
WHERE trip_id = ?
|
||||
AND item_id = ?",
|
||||
key = to_variant_name(&key).unwrap()
|
||||
))
|
||||
.bind(value)
|
||||
.bind(trip_id.to_string())
|
||||
.bind(item_id.to_string())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
(result.rows_affected() != 0).then_some(()).ok_or_else(|| {
|
||||
Error::Query(QueryError::NotFound {
|
||||
description: format!("item {item_id} not found for trip {trip_id}"),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DbTripRow {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub date_start: String,
|
||||
pub date_end: String,
|
||||
pub state: String,
|
||||
pub location: Option<String>,
|
||||
pub temp_min: Option<i64>,
|
||||
pub temp_max: Option<i64>,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<DbTripRow> for Trip {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(row: DbTripRow) -> Result<Self, Self::Error> {
|
||||
Ok(Trip {
|
||||
id: Uuid::try_parse(&row.id)?,
|
||||
name: row.name,
|
||||
date_start: time::Date::parse(&row.date_start, consts::DATE_FORMAT)?,
|
||||
date_end: time::Date::parse(&row.date_end, consts::DATE_FORMAT)?,
|
||||
state: row.state.as_str().try_into()?,
|
||||
location: row.location,
|
||||
temp_min: row.temp_min,
|
||||
temp_max: row.temp_max,
|
||||
comment: row.comment,
|
||||
types: None,
|
||||
categories: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Trip {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub date_start: time::Date,
|
||||
pub date_end: time::Date,
|
||||
pub state: TripState,
|
||||
pub location: Option<String>,
|
||||
pub temp_min: Option<i64>,
|
||||
pub temp_max: Option<i64>,
|
||||
pub comment: Option<String>,
|
||||
pub(crate) types: Option<Vec<TripType>>,
|
||||
pub(crate) categories: Option<Vec<TripCategory>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TripAttribute {
|
||||
#[serde(rename = "name")]
|
||||
Name,
|
||||
#[serde(rename = "date_start")]
|
||||
DateStart,
|
||||
#[serde(rename = "date_end")]
|
||||
DateEnd,
|
||||
#[serde(rename = "location")]
|
||||
Location,
|
||||
#[serde(rename = "temp_min")]
|
||||
TempMin,
|
||||
#[serde(rename = "temp_max")]
|
||||
TempMax,
|
||||
}
|
||||
|
||||
pub(crate) struct DbTripWeightRow {
|
||||
pub total_weight: Option<i32>,
|
||||
}
|
||||
|
||||
impl Trip {
|
||||
pub async fn all(pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Vec<Trip>, Error> {
|
||||
sqlx::query_as!(
|
||||
DbTripRow,
|
||||
"SELECT
|
||||
id,
|
||||
name,
|
||||
CAST (date_start AS TEXT) date_start,
|
||||
CAST (date_end AS TEXT) date_end,
|
||||
state,
|
||||
location,
|
||||
temp_min,
|
||||
temp_max,
|
||||
comment
|
||||
FROM trips",
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| row.try_into())
|
||||
.try_collect::<Vec<Result<Trip, Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<Trip>, Error>>()
|
||||
}
|
||||
|
||||
pub async fn find(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let trip_id_param = trip_id.to_string();
|
||||
sqlx::query_as!(
|
||||
DbTripRow,
|
||||
"SELECT
|
||||
id,
|
||||
name,
|
||||
CAST (date_start AS TEXT) date_start,
|
||||
CAST (date_end AS TEXT) date_end,
|
||||
state,
|
||||
location,
|
||||
temp_min,
|
||||
temp_max,
|
||||
comment
|
||||
FROM trips
|
||||
WHERE id = ?",
|
||||
trip_id_param
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.map(|row| row.try_into())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn trip_type_remove(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
type_id: Uuid,
|
||||
) -> Result<bool, Error> {
|
||||
let id_param = id.to_string();
|
||||
let type_id_param = type_id.to_string();
|
||||
|
||||
let results = sqlx::query!(
|
||||
"DELETE FROM trips_to_trips_types AS ttt
|
||||
WHERE ttt.trip_id = ?
|
||||
AND ttt.trip_type_id = ?
|
||||
",
|
||||
id_param,
|
||||
type_id_param,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(results.rows_affected() != 0)
|
||||
}
|
||||
|
||||
pub async fn trip_type_add(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
type_id: Uuid,
|
||||
) -> Result<(), Error> {
|
||||
let trip_id_param = id.to_string();
|
||||
let type_id_param = type_id.to_string();
|
||||
sqlx::query!(
|
||||
"INSERT INTO trips_to_trips_types
|
||||
(trip_id, trip_type_id)
|
||||
VALUES
|
||||
(?, ?)
|
||||
",
|
||||
trip_id_param,
|
||||
type_id_param,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_state(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
new_state: &TripState,
|
||||
) -> Result<bool, Error> {
|
||||
let trip_id_param = id.to_string();
|
||||
let result = sqlx::query!(
|
||||
"UPDATE trips
|
||||
SET state = ?
|
||||
WHERE id = ?",
|
||||
new_state,
|
||||
trip_id_param,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected() != 0)
|
||||
}
|
||||
|
||||
pub async fn set_comment(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
new_comment: &str,
|
||||
) -> Result<bool, Error> {
|
||||
let trip_id_param = id.to_string();
|
||||
let result = sqlx::query!(
|
||||
"UPDATE trips
|
||||
SET comment = ?
|
||||
WHERE id = ?",
|
||||
new_comment,
|
||||
trip_id_param,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected() != 0)
|
||||
}
|
||||
|
||||
pub async fn set_attribute(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
attribute: TripAttribute,
|
||||
value: &str,
|
||||
) -> Result<(), Error> {
|
||||
let result = sqlx::query(&format!(
|
||||
"UPDATE trips
|
||||
SET {attribute} = ?
|
||||
WHERE id = ?",
|
||||
attribute = to_variant_name(&attribute).unwrap()
|
||||
))
|
||||
.bind(value)
|
||||
.bind(trip_id.to_string())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
(result.rows_affected() != 0).then_some(()).ok_or_else(|| {
|
||||
Error::Query(QueryError::NotFound {
|
||||
description: format!("trip {trip_id} not found"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
name: &str,
|
||||
date_start: time::Date,
|
||||
date_end: time::Date,
|
||||
) -> Result<Uuid, Error> {
|
||||
let id = Uuid::new_v4();
|
||||
let id_param = id.to_string();
|
||||
let date_start = date_start.format(consts::DATE_FORMAT)?;
|
||||
let date_end = date_end.format(consts::DATE_FORMAT)?;
|
||||
|
||||
let trip_state = TripState::new();
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO trips
|
||||
(id, name, date_start, date_end, state)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)",
|
||||
id_param,
|
||||
name,
|
||||
date_start,
|
||||
date_end,
|
||||
trip_state,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub async fn find_total_picked_weight(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
) -> Result<i64, Error> {
|
||||
let trip_id_param = trip_id.to_string();
|
||||
let weight = sqlx::query_as!(
|
||||
DbTripWeightRow,
|
||||
"
|
||||
SELECT
|
||||
CAST(IFNULL(SUM(i_item.weight), 0) AS INTEGER) AS total_weight
|
||||
FROM trips AS trip
|
||||
INNER JOIN trips_items AS t_item
|
||||
ON t_item.trip_id = trip.id
|
||||
INNER JOIN inventory_items AS i_item
|
||||
ON t_item.item_id = i_item.id
|
||||
WHERE
|
||||
trip.id = ?
|
||||
AND t_item.pick = true
|
||||
",
|
||||
trip_id_param
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.map_ok(|row| row.total_weight.unwrap() as i64)
|
||||
.await?;
|
||||
|
||||
Ok(weight)
|
||||
}
|
||||
|
||||
pub fn types(&self) -> &Vec<TripType> {
|
||||
self.types
|
||||
.as_ref()
|
||||
.expect("you need to call load_trips_types()")
|
||||
}
|
||||
|
||||
pub fn categories(&self) -> &Vec<TripCategory> {
|
||||
self.categories
|
||||
.as_ref()
|
||||
.expect("you need to call load_trips_types()")
|
||||
}
|
||||
|
||||
pub fn total_picked_weight(&self) -> i64 {
|
||||
self.categories()
|
||||
.iter()
|
||||
.map(|category| -> i64 {
|
||||
category
|
||||
.items
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|item| Some(item.item.weight).filter(|_| item.picked))
|
||||
.sum::<i64>()
|
||||
})
|
||||
.sum::<i64>()
|
||||
}
|
||||
|
||||
pub async fn load_trips_types(&mut self, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<(), Error> {
|
||||
let id = self.id.to_string();
|
||||
let types = sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
type.id as id,
|
||||
type.name as name,
|
||||
inner.id IS NOT NULL AS active
|
||||
FROM trips_types AS type
|
||||
LEFT JOIN (
|
||||
SELECT type.id as id, type.name as name
|
||||
FROM trips as trip
|
||||
INNER JOIN trips_to_trips_types as ttt
|
||||
ON ttt.trip_id = trip.id
|
||||
INNER JOIN trips_types AS type
|
||||
ON type.id == ttt.trip_type_id
|
||||
WHERE trip.id = ?
|
||||
) AS inner
|
||||
ON inner.id = type.id
|
||||
",
|
||||
id
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| -> Result<TripType, Error> {
|
||||
Ok(TripType {
|
||||
id: Uuid::try_parse(&row.id)?,
|
||||
name: row.name,
|
||||
active: match row.active {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
})
|
||||
})
|
||||
.try_collect::<Vec<Result<TripType, Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<TripType>, Error>>()?;
|
||||
|
||||
self.types = Some(types);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sync_trip_items_with_inventory(
|
||||
&mut self,
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
) -> Result<(), Error> {
|
||||
// we need to get all items that are part of the inventory but not
|
||||
// part of the trip items
|
||||
//
|
||||
// then, we know which items we need to sync. there are different
|
||||
// states for them:
|
||||
//
|
||||
// * if the trip is new (it's state is INITIAL), we can just forward
|
||||
// as-is
|
||||
// * if the trip is new, we have to make these new items prominently
|
||||
// visible so the user knows that there might be new items to
|
||||
// consider
|
||||
let trip_id = self.id.to_string();
|
||||
let unsynced_items: Vec<Uuid> = sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
i_item.id AS item_id
|
||||
FROM inventory_items AS i_item
|
||||
LEFT JOIN (
|
||||
SELECT t_item.item_id as item_id
|
||||
FROM trips_items AS t_item
|
||||
WHERE t_item.trip_id = ?
|
||||
) AS t_item
|
||||
ON t_item.item_id = i_item.id
|
||||
WHERE t_item.item_id IS NULL
|
||||
",
|
||||
trip_id
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| -> Result<Uuid, Error> { Ok(Uuid::try_parse(&row.item_id)?) })
|
||||
.try_collect::<Vec<Result<Uuid, Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<Uuid>, Error>>()?;
|
||||
|
||||
// looks like there is currently no nice way to do multiple inserts
|
||||
// with sqlx. whatever, this won't matter
|
||||
|
||||
// only mark as new when the trip is already underway
|
||||
let mark_as_new = self.state != TripState::new();
|
||||
|
||||
for unsynced_item in &unsynced_items {
|
||||
let item_id = unsynced_item.to_string();
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO trips_items
|
||||
(
|
||||
item_id,
|
||||
trip_id,
|
||||
pick,
|
||||
pack,
|
||||
new
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
",
|
||||
item_id,
|
||||
trip_id,
|
||||
false,
|
||||
false,
|
||||
mark_as_new,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tracing::info!("unsynced items: {:?}", &unsynced_items);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_categories(&mut self, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<(), Error> {
|
||||
let mut categories: Vec<TripCategory> = vec![];
|
||||
// we can ignore the return type as we collect into `categories`
|
||||
// in the `map_ok()` closure
|
||||
let id = self.id.to_string();
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description AS category_description,
|
||||
inner.trip_id AS trip_id,
|
||||
inner.item_id AS item_id,
|
||||
inner.item_name AS item_name,
|
||||
inner.item_description AS item_description,
|
||||
inner.item_weight AS item_weight,
|
||||
inner.item_is_picked AS item_is_picked,
|
||||
inner.item_is_packed AS item_is_packed,
|
||||
inner.item_is_new AS item_is_new
|
||||
FROM inventory_items_categories AS category
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
trip.trip_id AS trip_id,
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description as category_description,
|
||||
item.id as item_id,
|
||||
item.name as item_name,
|
||||
item.description as item_description,
|
||||
item.weight as item_weight,
|
||||
trip.pick as item_is_picked,
|
||||
trip.pack as item_is_packed,
|
||||
trip.new as item_is_new
|
||||
FROM trips_items as trip
|
||||
INNER JOIN inventory_items as item
|
||||
ON item.id = trip.item_id
|
||||
INNER JOIN inventory_items_categories as category
|
||||
ON category.id = item.category_id
|
||||
WHERE trip.trip_id = ?
|
||||
) AS inner
|
||||
ON inner.category_id = category.id
|
||||
",
|
||||
id
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| -> Result<(), Error> {
|
||||
let mut category = TripCategory {
|
||||
category: inventory::Category {
|
||||
id: Uuid::try_parse(&row.category_id)?,
|
||||
name: row.category_name,
|
||||
description: row.category_description,
|
||||
|
||||
items: None,
|
||||
},
|
||||
items: None,
|
||||
};
|
||||
|
||||
match row.item_id {
|
||||
None => {
|
||||
// we have an empty (unused) category which has NULL values
|
||||
// for the item_id column
|
||||
category.items = Some(vec![]);
|
||||
categories.push(category);
|
||||
}
|
||||
Some(item_id) => {
|
||||
let item = TripItem {
|
||||
item: inventory::Item {
|
||||
id: Uuid::try_parse(&item_id)?,
|
||||
name: row.item_name.unwrap(),
|
||||
description: row.item_description,
|
||||
weight: row.item_weight.unwrap(),
|
||||
category_id: category.category.id,
|
||||
},
|
||||
picked: row.item_is_picked.unwrap(),
|
||||
packed: row.item_is_packed.unwrap(),
|
||||
new: row.item_is_new.unwrap(),
|
||||
};
|
||||
|
||||
if let Some(&mut ref mut c) = categories
|
||||
.iter_mut()
|
||||
.find(|c| c.category.id == category.category.id)
|
||||
{
|
||||
// we always populate c.items when we add a new category, so
|
||||
// it's safe to unwrap here
|
||||
c.items.as_mut().unwrap().push(item);
|
||||
} else {
|
||||
category.items = Some(vec![item]);
|
||||
categories.push(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.try_collect::<Vec<Result<(), Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<(), Error>>()?;
|
||||
|
||||
self.categories = Some(categories);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TripType {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl TripsType {
|
||||
pub async fn all(pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Vec<Self>, Error> {
|
||||
sqlx::query_as!(
|
||||
DbTripsTypesRow,
|
||||
"SELECT
|
||||
id,
|
||||
name
|
||||
FROM trips_types",
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| row.try_into())
|
||||
.try_collect::<Vec<Result<Self, Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<Self>, Error>>()
|
||||
}
|
||||
|
||||
pub async fn save(pool: &sqlx::Pool<sqlx::Sqlite>, name: &str) -> Result<Uuid, Error> {
|
||||
let id = Uuid::new_v4();
|
||||
let id_param = id.to_string();
|
||||
sqlx::query!(
|
||||
"INSERT INTO trips_types
|
||||
(id, name)
|
||||
VALUES
|
||||
(?, ?)",
|
||||
id_param,
|
||||
name,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub async fn set_name(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
new_name: &str,
|
||||
) -> Result<bool, Error> {
|
||||
let id_param = id.to_string();
|
||||
|
||||
let result = sqlx::query!(
|
||||
"UPDATE trips_types
|
||||
SET name = ?
|
||||
WHERE id = ?",
|
||||
new_name,
|
||||
id_param,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected() != 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DbTripsTypesRow {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TripTypeAttribute {
|
||||
#[serde(rename = "name")]
|
||||
Name,
|
||||
}
|
||||
|
||||
pub struct TripsType {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl TryFrom<DbTripsTypesRow> for TripsType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(row: DbTripsTypesRow) -> Result<Self, Self::Error> {
|
||||
Ok(TripsType {
|
||||
id: Uuid::try_parse(&row.id)?,
|
||||
name: row.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ pub struct Inventory;
|
||||
|
||||
impl Inventory {
|
||||
pub fn build(
|
||||
active_category: Option<&models::Category>,
|
||||
categories: &Vec<models::Category>,
|
||||
active_category: Option<&models::inventory::Category>,
|
||||
categories: &Vec<models::inventory::Category>,
|
||||
edit_item_id: Option<Uuid>,
|
||||
) -> Markup {
|
||||
html!(
|
||||
@@ -37,12 +37,12 @@ pub struct InventoryCategoryList;
|
||||
|
||||
impl InventoryCategoryList {
|
||||
pub fn build(
|
||||
active_category: Option<&models::Category>,
|
||||
categories: &Vec<models::Category>,
|
||||
active_category: Option<&models::inventory::Category>,
|
||||
categories: &Vec<models::inventory::Category>,
|
||||
) -> Markup {
|
||||
let biggest_category_weight: i64 = categories
|
||||
.iter()
|
||||
.map(models::Category::total_weight)
|
||||
.map(models::inventory::Category::total_weight)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
|
||||
@@ -129,7 +129,7 @@ impl InventoryCategoryList {
|
||||
}
|
||||
td ."border" ."p-0" ."m-0" {
|
||||
p ."p-2" ."m-2" {
|
||||
(categories.iter().map(models::Category::total_weight).sum::<i64>().to_string())
|
||||
(categories.iter().map(models::inventory::Category::total_weight).sum::<i64>().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ impl InventoryCategoryList {
|
||||
pub struct InventoryItemList;
|
||||
|
||||
impl InventoryItemList {
|
||||
pub fn build(edit_item_id: Option<Uuid>, items: &Vec<models::Item>) -> Markup {
|
||||
pub fn build(edit_item_id: Option<Uuid>, items: &Vec<models::inventory::Item>) -> Markup {
|
||||
let biggest_item_weight: i64 = items.iter().map(|item| item.weight).max().unwrap_or(1);
|
||||
html!(
|
||||
div #items {
|
||||
@@ -414,8 +414,8 @@ pub struct InventoryNewItemFormCategory;
|
||||
|
||||
impl InventoryNewItemFormCategory {
|
||||
pub fn build(
|
||||
active_category: Option<&models::Category>,
|
||||
categories: &Vec<models::Category>,
|
||||
active_category: Option<&models::inventory::Category>,
|
||||
categories: &Vec<models::inventory::Category>,
|
||||
) -> Markup {
|
||||
html!(
|
||||
div
|
||||
@@ -455,8 +455,8 @@ pub struct InventoryNewItemForm;
|
||||
|
||||
impl InventoryNewItemForm {
|
||||
pub fn build(
|
||||
active_category: Option<&models::Category>,
|
||||
categories: &Vec<models::Category>,
|
||||
active_category: Option<&models::inventory::Category>,
|
||||
categories: &Vec<models::inventory::Category>,
|
||||
) -> Markup {
|
||||
html!(
|
||||
form
|
||||
@@ -558,7 +558,7 @@ impl InventoryNewCategoryForm {
|
||||
pub struct InventoryItem;
|
||||
|
||||
impl InventoryItem {
|
||||
pub fn build(_state: &ClientState, item: &models::InventoryItem) -> Markup {
|
||||
pub fn build(_state: &ClientState, item: &models::inventory::InventoryItem) -> Markup {
|
||||
html!(
|
||||
div ."p-8" {
|
||||
table
|
||||
|
||||
@@ -12,7 +12,7 @@ pub mod packagelist;
|
||||
pub mod types;
|
||||
|
||||
impl TripManager {
|
||||
pub fn build(trips: Vec<models::Trip>) -> Markup {
|
||||
pub fn build(trips: Vec<models::trips::Trip>) -> Markup {
|
||||
html!(
|
||||
div
|
||||
."p-8"
|
||||
@@ -44,21 +44,21 @@ impl From<InputType> for &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn trip_state_icon(state: &models::TripState) -> &'static str {
|
||||
fn trip_state_icon(state: &models::trips::TripState) -> &'static str {
|
||||
match state {
|
||||
models::TripState::Init => "mdi-magic-staff",
|
||||
models::TripState::Planning => "mdi-text-box-outline",
|
||||
models::TripState::Planned => "mdi-clock-outline",
|
||||
models::TripState::Active => "mdi-play",
|
||||
models::TripState::Review => "mdi-magnify",
|
||||
models::TripState::Done => "mdi-check",
|
||||
models::trips::TripState::Init => "mdi-magic-staff",
|
||||
models::trips::TripState::Planning => "mdi-text-box-outline",
|
||||
models::trips::TripState::Planned => "mdi-clock-outline",
|
||||
models::trips::TripState::Active => "mdi-play",
|
||||
models::trips::TripState::Review => "mdi-magnify",
|
||||
models::trips::TripState::Done => "mdi-check",
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TripTable;
|
||||
|
||||
impl TripTable {
|
||||
pub fn build(trips: Vec<models::Trip>) -> Markup {
|
||||
pub fn build(trips: Vec<models::trips::Trip>) -> Markup {
|
||||
html!(
|
||||
table
|
||||
."table"
|
||||
@@ -222,9 +222,9 @@ pub struct Trip;
|
||||
|
||||
impl Trip {
|
||||
pub fn build(
|
||||
trip: &models::Trip,
|
||||
trip_edit_attribute: Option<models::TripAttribute>,
|
||||
active_category: Option<&models::TripCategory>,
|
||||
trip: &models::trips::Trip,
|
||||
trip_edit_attribute: Option<models::trips::TripAttribute>,
|
||||
active_category: Option<&models::trips::TripCategory>,
|
||||
) -> Markup {
|
||||
html!(
|
||||
div ."p-8" ."flex" ."flex-col" ."gap-8" {
|
||||
@@ -257,10 +257,10 @@ impl Trip {
|
||||
}
|
||||
}
|
||||
div ."flex" ."flex-row" ."items-center" ."gap-x-3" {
|
||||
@if trip_edit_attribute.as_ref().map_or(false, |a| *a == models::TripAttribute::Name) {
|
||||
@if trip_edit_attribute.as_ref().map_or(false, |a| *a == models::trips::TripAttribute::Name) {
|
||||
form
|
||||
id="edit-trip"
|
||||
action=(format!("edit/{}/submit", to_variant_name(&models::TripAttribute::Name).unwrap()))
|
||||
action=(format!("edit/{}/submit", to_variant_name(&models::trips::TripAttribute::Name).unwrap()))
|
||||
hx-boost="true"
|
||||
target="_self"
|
||||
method="post"
|
||||
@@ -316,7 +316,7 @@ impl Trip {
|
||||
h1 ."text-2xl" { (trip.name) }
|
||||
span {
|
||||
a
|
||||
href={"?edit=" (to_variant_name(&models::TripAttribute::Name).unwrap())}
|
||||
href={"?edit=" (to_variant_name(&models::trips::TripAttribute::Name).unwrap())}
|
||||
hx-boost="true"
|
||||
{
|
||||
span
|
||||
@@ -357,8 +357,8 @@ impl TripInfoRow {
|
||||
pub fn build(
|
||||
name: &str,
|
||||
value: Option<impl std::fmt::Display>,
|
||||
attribute_key: &models::TripAttribute,
|
||||
edit_attribute: Option<&models::TripAttribute>,
|
||||
attribute_key: &models::trips::TripAttribute,
|
||||
edit_attribute: Option<&models::trips::TripAttribute>,
|
||||
input_type: InputType,
|
||||
) -> Markup {
|
||||
let edit = edit_attribute.map_or(false, |a| a == attribute_key);
|
||||
@@ -497,7 +497,7 @@ impl TripInfoTotalWeightRow {
|
||||
pub struct TripInfoStateRow;
|
||||
|
||||
impl TripInfoStateRow {
|
||||
pub fn build(trip_state: &models::TripState) -> Markup {
|
||||
pub fn build(trip_state: &models::trips::TripState) -> Markup {
|
||||
let prev_state = trip_state.prev();
|
||||
let next_state = trip_state.next();
|
||||
html!(
|
||||
@@ -602,8 +602,8 @@ pub struct TripInfo;
|
||||
|
||||
impl TripInfo {
|
||||
pub fn build(
|
||||
trip_edit_attribute: Option<models::TripAttribute>,
|
||||
trip: &models::Trip,
|
||||
trip_edit_attribute: Option<models::trips::TripAttribute>,
|
||||
trip: &models::trips::Trip,
|
||||
) -> Markup {
|
||||
html!(
|
||||
table
|
||||
@@ -617,31 +617,31 @@ impl TripInfo {
|
||||
tbody {
|
||||
(TripInfoRow::build("Location",
|
||||
trip.location.as_ref(),
|
||||
&models::TripAttribute::Location,
|
||||
&models::trips::TripAttribute::Location,
|
||||
trip_edit_attribute.as_ref(),
|
||||
InputType::Text,
|
||||
))
|
||||
(TripInfoRow::build("Start date",
|
||||
Some(trip.date_start),
|
||||
&models::TripAttribute::DateStart,
|
||||
&models::trips::TripAttribute::DateStart,
|
||||
trip_edit_attribute.as_ref(),
|
||||
InputType::Date,
|
||||
))
|
||||
(TripInfoRow::build("End date",
|
||||
Some(trip.date_end),
|
||||
&models::TripAttribute::DateEnd,
|
||||
&models::trips::TripAttribute::DateEnd,
|
||||
trip_edit_attribute.as_ref(),
|
||||
InputType::Date,
|
||||
))
|
||||
(TripInfoRow::build("Temp (min)",
|
||||
trip.temp_min,
|
||||
&models::TripAttribute::TempMin,
|
||||
&models::trips::TripAttribute::TempMin,
|
||||
trip_edit_attribute.as_ref(),
|
||||
InputType::Number,
|
||||
))
|
||||
(TripInfoRow::build("Temp (max)",
|
||||
trip.temp_max,
|
||||
&models::TripAttribute::TempMax,
|
||||
&models::trips::TripAttribute::TempMax,
|
||||
trip_edit_attribute.as_ref(),
|
||||
InputType::Number,
|
||||
))
|
||||
@@ -672,8 +672,8 @@ impl TripInfo {
|
||||
// the margins
|
||||
{
|
||||
@let types = trip.types();
|
||||
@let active_triptypes = types.iter().filter(|t| t.active).collect::<Vec<&models::TripType>>();
|
||||
@let inactive_triptypes = types.iter().filter(|t| !t.active).collect::<Vec<&models::TripType>>();
|
||||
@let active_triptypes = types.iter().filter(|t| t.active).collect::<Vec<&models::trips::TripType>>();
|
||||
@let inactive_triptypes = types.iter().filter(|t| !t.active).collect::<Vec<&models::trips::TripType>>();
|
||||
|
||||
@if !active_triptypes.is_empty() {
|
||||
div
|
||||
@@ -775,7 +775,7 @@ impl TripInfo {
|
||||
pub struct TripComment;
|
||||
|
||||
impl TripComment {
|
||||
pub fn build(trip: &models::Trip) -> Markup {
|
||||
pub fn build(trip: &models::trips::Trip) -> Markup {
|
||||
html!(
|
||||
div
|
||||
x-data="{ save_active: false }"
|
||||
@@ -830,7 +830,10 @@ impl TripComment {
|
||||
pub struct TripItems;
|
||||
|
||||
impl TripItems {
|
||||
pub fn build(active_category: Option<&models::TripCategory>, trip: &models::Trip) -> Markup {
|
||||
pub fn build(
|
||||
active_category: Option<&models::trips::TripCategory>,
|
||||
trip: &models::trips::Trip,
|
||||
) -> Markup {
|
||||
html!(
|
||||
div #trip-items ."grid" ."grid-cols-4" ."gap-3" {
|
||||
div ."col-span-2" {
|
||||
@@ -856,7 +859,7 @@ pub struct TripCategoryListRow;
|
||||
impl TripCategoryListRow {
|
||||
pub fn build(
|
||||
trip_id: Uuid,
|
||||
category: &models::TripCategory,
|
||||
category: &models::trips::TripCategory,
|
||||
active: bool,
|
||||
biggest_category_weight: i64,
|
||||
htmx_swap: bool,
|
||||
@@ -962,12 +965,15 @@ impl TripCategoryListRow {
|
||||
pub struct TripCategoryList;
|
||||
|
||||
impl TripCategoryList {
|
||||
pub fn build(active_category: Option<&models::TripCategory>, trip: &models::Trip) -> Markup {
|
||||
pub fn build(
|
||||
active_category: Option<&models::trips::TripCategory>,
|
||||
trip: &models::trips::Trip,
|
||||
) -> Markup {
|
||||
let categories = trip.categories();
|
||||
|
||||
let biggest_category_weight: i64 = categories
|
||||
.iter()
|
||||
.map(models::TripCategory::total_picked_weight)
|
||||
.map(models::trips::TripCategory::total_picked_weight)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
|
||||
@@ -1002,7 +1008,7 @@ impl TripCategoryList {
|
||||
}
|
||||
td ."border" ."p-0" ."m-0" {
|
||||
p ."p-2" ."m-2" {
|
||||
(categories.iter().map(models::TripCategory::total_picked_weight).sum::<i64>().to_string())
|
||||
(categories.iter().map(models::trips::TripCategory::total_picked_weight).sum::<i64>().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1021,7 @@ impl TripCategoryList {
|
||||
pub struct TripItemList;
|
||||
|
||||
impl TripItemList {
|
||||
pub fn build(trip_id: Uuid, items: &Vec<models::TripItem>) -> Markup {
|
||||
pub fn build(trip_id: Uuid, items: &Vec<models::trips::TripItem>) -> Markup {
|
||||
let biggest_item_weight: i64 = items.iter().map(|item| item.item.weight).max().unwrap_or(1);
|
||||
|
||||
html!(
|
||||
@@ -1053,7 +1059,11 @@ impl TripItemList {
|
||||
pub struct TripItemListRow;
|
||||
|
||||
impl TripItemListRow {
|
||||
pub fn build(trip_id: Uuid, item: &models::TripItem, biggest_item_weight: i64) -> Markup {
|
||||
pub fn build(
|
||||
trip_id: Uuid,
|
||||
item: &models::trips::TripItem,
|
||||
biggest_item_weight: i64,
|
||||
) -> Markup {
|
||||
html!(
|
||||
tr ."h-10" {
|
||||
td
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::models;
|
||||
pub struct TripPackageListRow;
|
||||
|
||||
impl TripPackageListRow {
|
||||
pub fn build(trip_id: Uuid, item: &models::TripItem) -> Markup {
|
||||
pub fn build(trip_id: Uuid, item: &models::trips::TripItem) -> Markup {
|
||||
html!(
|
||||
li
|
||||
."flex"
|
||||
@@ -82,7 +82,7 @@ impl TripPackageListRow {
|
||||
pub struct TripPackageListCategoryBlock;
|
||||
|
||||
impl TripPackageListCategoryBlock {
|
||||
pub fn build(trip: &models::Trip, category: &models::TripCategory) -> Markup {
|
||||
pub fn build(trip: &models::trips::Trip, category: &models::trips::TripCategory) -> Markup {
|
||||
let empty = !category
|
||||
.items
|
||||
.as_ref()
|
||||
@@ -138,7 +138,7 @@ impl TripPackageListCategoryBlock {
|
||||
pub struct TripPackageList;
|
||||
|
||||
impl TripPackageList {
|
||||
pub fn build(trip: &models::Trip) -> Markup {
|
||||
pub fn build(trip: &models::trips::Trip) -> Markup {
|
||||
// let all_packed = trip.categories().iter().all(|category| {
|
||||
// category
|
||||
// .items
|
||||
|
||||
@@ -5,7 +5,7 @@ use maud::{html, Markup};
|
||||
pub struct TypeList;
|
||||
|
||||
impl TypeList {
|
||||
pub fn build(state: &ClientState, trip_types: Vec<models::TripsType>) -> Markup {
|
||||
pub fn build(state: &ClientState, trip_types: Vec<models::trips::TripsType>) -> Markup {
|
||||
html!(
|
||||
div ."p-8" ."flex" ."flex-col" ."gap-8" {
|
||||
h1 ."text-2xl" {"Trip Types"}
|
||||
|
||||
Reference in New Issue
Block a user