From 16353c768328718e9c82a7a4e19ab2615a142377 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 18 May 2025 22:30:18 +0200 Subject: [PATCH] quanta + properties --- docs/Item.md | 38 ++++++++++++++ migrations/0002_quanta_properties.sql | 3 ++ src/core/flow.rs | 2 + src/core/item.rs | 2 +- src/core/transaction.rs | 24 ++++++++- src/core/variant.rs | 61 ++++++---------------- src/main.rs | 73 +++++++++++++-------------- src/routes/item/supply.rs | 6 +++ 8 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 migrations/0002_quanta_properties.sql diff --git a/docs/Item.md b/docs/Item.md index 44b5492..45d93e3 100644 --- a/docs/Item.md +++ b/docs/Item.md @@ -87,3 +87,41 @@ variants: needs: temperature: [5.0, 10.0] ``` + +### Quantization +Sometimes items are homogenous and can be subdivided. +This can be defined via the `unit` of an Item Variant. +You define the base unit (the smallest possible instance) of an Item Variant. In this case `ml`. +Optionally you can define various `conversions` to bigger units by specifiying the units name and how many of the base unit this unit is. +In this case we define `liter` to be 1000 `ml`. + +```yml +name: "Water" +variants: + regular: + name: "Regular Water" + unit: + by: "ml" + conversions: + liter: 1000 +``` + +### Properties +Some items might be more or less the same thing but come in various configurations. +To address such items you can define custom properties on Item Variants. +You can then use these properties to save and filter on metadata about your items. +This can be done by defining custom properties in a JSON schema like format in `properties`: + +```yml +name: "iPhone" +variants: + iphone: + name: "iPhone" + properties: + color: + enum: ["Black", "White", "Red"] + description: "The Phone Color" + ram: + type: integer + description: "The amount of RAM" +``` diff --git a/migrations/0002_quanta_properties.sql b/migrations/0002_quanta_properties.sql new file mode 100644 index 0000000..6087932 --- /dev/null +++ b/migrations/0002_quanta_properties.sql @@ -0,0 +1,3 @@ +ALTER TABLE transactions +ADD COLUMN quanta BIGINT, +ADD COLUMN properties JSONB; diff --git a/src/core/flow.rs b/src/core/flow.rs index ba1c1c0..7d49494 100644 --- a/src/core/flow.rs +++ b/src/core/flow.rs @@ -121,6 +121,8 @@ impl Flow { Some(&format!("flow::{}::{}", self.kind, self.id)), info.location.as_ref().map(|x| x.as_str()), info.note.as_ref().map(|x| x.as_str()), + info.quanta, + info.properties.clone() ) .await; ret.entry(item.item_variant_id().clone()) diff --git a/src/core/item.rs b/src/core/item.rs index 65c182e..c90cc9c 100644 --- a/src/core/item.rs +++ b/src/core/item.rs @@ -96,7 +96,7 @@ impl Item { for (variant_id, variant) in variants_lst { variants.insert( variant_id.as_str().unwrap().to_string(), - Variant::from_yml(&variant, variant_id.as_str().unwrap(), &id), + Variant::from_yml(variant, variant_id.as_str().unwrap(), &id), ); } diff --git a/src/core/transaction.rs b/src/core/transaction.rs index 2b95645..a5c8de9 100644 --- a/src/core/transaction.rs +++ b/src/core/transaction.rs @@ -25,6 +25,10 @@ pub struct Transaction { pub location: Option, /// Notes on Transaction pub note: Option, + /// Qantized Unit + pub quanta: Option, + /// Custom properties + pub properties: Option, /// Timestamp of the Transaction pub created: chrono::DateTime, /// Destination of the Item or who consumed it @@ -35,6 +39,18 @@ pub struct Transaction { pub consumed_timestamp: Option>, } +// TODO : typed origins / dests + +pub enum Origin { + Flow(String), + Custom(String) +} + +pub enum Destination { + Flow(String), + Custom(String) +} + impl Transaction { pub async fn new( item: &str, @@ -43,14 +59,18 @@ impl Transaction { origin: Option<&str>, location: Option<&str>, note: Option<&str>, + quanta: Option, + properties: Option ) -> Self { - sqlx::query_as("INSERT INTO transactions (item, variant, price, origin, location, note) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *") + sqlx::query_as("INSERT INTO transactions (item, variant, price, origin, location, note, quanta, properties) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *") .bind(item) .bind(variant) .bind(price) .bind(origin) .bind(location) .bind(note) + .bind(quanta) + .bind(properties) .fetch_one(get_pg!()).await.unwrap() } @@ -207,6 +227,8 @@ impl ToAPI for Transaction { "consumed": consumed, "note": self.note, "expired": self.is_expired().await, + "properties": self.properties, + "quanta": self.quanta }) } } diff --git a/src/core/variant.rs b/src/core/variant.rs index e14628b..c538df9 100644 --- a/src/core/variant.rs +++ b/src/core/variant.rs @@ -47,6 +47,16 @@ pub struct Variant { pub barcodes: Option>, /// Variant Need Conditions pub needs: Option, + /// Custom fields as JSON object schema + pub meta: Option, + /// Quantifiable + pub unit: Option +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Quanta { + pub by: String, + pub conversions: HashMap } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -57,51 +67,8 @@ pub struct VariantNeedCondition { impl Variant { /// Create variant from itemdb yaml - pub fn from_yml(json: &serde_yaml::Value, variant: &str, item: &str) -> Self { - Self { - item: item.to_string(), - variant: variant.to_string(), - name: json - .as_mapping() - .unwrap() - .get("name") - .unwrap() - .as_str() - .unwrap() - .to_string(), - min: json - .as_mapping() - .unwrap() - .get("min") - .map(|x| x.as_i64().unwrap()), - expiry: json - .as_mapping() - .unwrap() - .get("expiry") - .map(|x| x.as_i64().unwrap()), - barcodes: json.as_mapping().unwrap().get("barcodes").map(|x| { - x.as_sequence() - .unwrap() - .into_iter() - .map(|x| x.as_i64().unwrap()) - .collect() - }), - needs: json.as_mapping().unwrap().get("needs").map(|x| { - let temp_range = x - .as_mapping() - .unwrap() - .get("temperature") - .unwrap() - .as_sequence() - .unwrap(); - VariantNeedCondition { - temperature: [ - temp_range.get(0).unwrap().as_f64().unwrap(), - temp_range.get(1).unwrap().as_f64().unwrap(), - ], - } - }), - } + pub fn from_yml(json: serde_yaml::Value, variant: &str, item: &str) -> Self { + serde_yaml::from_value(json).unwrap() } /// Get a API id for this Item Variant. @@ -175,8 +142,10 @@ impl Variant { origin: Option<&str>, location: Option<&str>, note: Option<&str>, + quanta: Option, + properties: Option ) -> Transaction { - Transaction::new(&self.item, &self.variant, price, origin, location, note).await + Transaction::new(&self.item, &self.variant, price, origin, location, note, quanta, properties).await } /// Returns all Transactions of this Item Variant diff --git a/src/main.rs b/src/main.rs index b97670f..db8cfe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,44 +141,41 @@ async fn rocket() -> _ { integrity::verify_integrity(&config, &flows, &locations, &itemdb).await; rocket::build() - .mount( - "/", - route![ - routes::item::get_items_route, - routes::item::item_route, - routes::item::item_variants_page, - routes::item::supply_log_route, - routes::item::demand_log_route, - routes::item::supply_route, - routes::item::demand_route, - routes::item::transaction_route, - routes::item::inventory_route, - routes::item::inventory_route_variant, - routes::item::variant_stat_route, - routes::item::unique_field_route, - routes::item::location_info, - routes::item::locations_info, - routes::item::locations_list, - routes::item::location_inventory, - routes::flow::flow_info, - routes::flow::flows_list, - routes::item::expired_items_route, - routes::item::min_items_route, - routes::item::variant_price_history_by_origin, - routes::flow::end_flow_route, - routes::flow::continue_flow_route, - routes::flow::create_flow_route, - routes::item::location_condition_warn, - routes::item::move_transaction_route, - routes::item::variant_price_latest_by_origin, - routes::item::item_stat_route, - routes::flow::active_flows_route, - routes::flow::flow_api_route, - routes::flow::create_flow_note_route, - routes::flow::flow_notes_route, - routes::item::item_image_route - ], - ) + .mount("/", route![ + routes::item::get_items_route, + routes::item::item_route, + routes::item::item_variants_page, + routes::item::supply_log_route, + routes::item::demand_log_route, + routes::item::supply_route, + routes::item::demand_route, + routes::item::transaction_route, + routes::item::inventory_route, + routes::item::inventory_route_variant, + routes::item::variant_stat_route, + routes::item::unique_field_route, + routes::item::location_info, + routes::item::locations_info, + routes::item::locations_list, + routes::item::location_inventory, + routes::flow::flow_info, + routes::flow::flows_list, + routes::item::expired_items_route, + routes::item::min_items_route, + routes::item::variant_price_history_by_origin, + routes::flow::end_flow_route, + routes::flow::continue_flow_route, + routes::flow::create_flow_route, + routes::item::location_condition_warn, + routes::item::move_transaction_route, + routes::item::variant_price_latest_by_origin, + routes::item::item_stat_route, + routes::flow::active_flows_route, + routes::flow::flow_api_route, + routes::flow::create_flow_note_route, + routes::flow::flow_notes_route, + routes::item::item_image_route + ]) .manage(itemdb) .manage(locations) .manage(flows) diff --git a/src/routes/item/supply.rs b/src/routes/item/supply.rs index 24e3d50..3090332 100644 --- a/src/routes/item/supply.rs +++ b/src/routes/item/supply.rs @@ -21,6 +21,8 @@ pub struct SupplyForm { pub origin: Option, pub location: Option, pub note: Option, + pub quanta: Option, + pub properties: Option } #[post("/supply", data = "
")] @@ -39,12 +41,16 @@ pub async fn supply_route( .variant(&form.variant) .ok_or_else(variant_does_not_exist_error)?; + // TODO : check properties schema + let transaction = variant .supply( form.price, form.origin.as_deref(), form.location.as_deref(), form.note.as_deref(), + form.quanta, + form.properties.clone() ) .await;