quanta + properties
Some checks failed
ci/woodpecker/push/build Pipeline failed

This commit is contained in:
JMARyA 2025-05-18 22:30:18 +02:00
parent 91d39a4d83
commit 16353c7683
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
8 changed files with 123 additions and 86 deletions

View file

@ -87,3 +87,41 @@ variants:
needs: needs:
temperature: [5.0, 10.0] 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"
```

View file

@ -0,0 +1,3 @@
ALTER TABLE transactions
ADD COLUMN quanta BIGINT,
ADD COLUMN properties JSONB;

View file

@ -121,6 +121,8 @@ impl Flow {
Some(&format!("flow::{}::{}", self.kind, self.id)), Some(&format!("flow::{}::{}", self.kind, self.id)),
info.location.as_ref().map(|x| x.as_str()), info.location.as_ref().map(|x| x.as_str()),
info.note.as_ref().map(|x| x.as_str()), info.note.as_ref().map(|x| x.as_str()),
info.quanta,
info.properties.clone()
) )
.await; .await;
ret.entry(item.item_variant_id().clone()) ret.entry(item.item_variant_id().clone())

View file

@ -96,7 +96,7 @@ impl Item {
for (variant_id, variant) in variants_lst { for (variant_id, variant) in variants_lst {
variants.insert( variants.insert(
variant_id.as_str().unwrap().to_string(), 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),
); );
} }

View file

@ -25,6 +25,10 @@ pub struct Transaction {
pub location: Option<String>, pub location: Option<String>,
/// Notes on Transaction /// Notes on Transaction
pub note: Option<String>, pub note: Option<String>,
/// Qantized Unit
pub quanta: Option<i64>,
/// Custom properties
pub properties: Option<serde_json::Value>,
/// Timestamp of the Transaction /// Timestamp of the Transaction
pub created: chrono::DateTime<chrono::Utc>, pub created: chrono::DateTime<chrono::Utc>,
/// Destination of the Item or who consumed it /// Destination of the Item or who consumed it
@ -35,6 +39,18 @@ pub struct Transaction {
pub consumed_timestamp: Option<chrono::DateTime<chrono::Utc>>, pub consumed_timestamp: Option<chrono::DateTime<chrono::Utc>>,
} }
// TODO : typed origins / dests
pub enum Origin {
Flow(String),
Custom(String)
}
pub enum Destination {
Flow(String),
Custom(String)
}
impl Transaction { impl Transaction {
pub async fn new( pub async fn new(
item: &str, item: &str,
@ -43,14 +59,18 @@ impl Transaction {
origin: Option<&str>, origin: Option<&str>,
location: Option<&str>, location: Option<&str>,
note: Option<&str>, note: Option<&str>,
quanta: Option<i64>,
properties: Option<serde_json::Value>
) -> Self { ) -> 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(item)
.bind(variant) .bind(variant)
.bind(price) .bind(price)
.bind(origin) .bind(origin)
.bind(location) .bind(location)
.bind(note) .bind(note)
.bind(quanta)
.bind(properties)
.fetch_one(get_pg!()).await.unwrap() .fetch_one(get_pg!()).await.unwrap()
} }
@ -207,6 +227,8 @@ impl ToAPI for Transaction {
"consumed": consumed, "consumed": consumed,
"note": self.note, "note": self.note,
"expired": self.is_expired().await, "expired": self.is_expired().await,
"properties": self.properties,
"quanta": self.quanta
}) })
} }
} }

View file

@ -47,6 +47,16 @@ pub struct Variant {
pub barcodes: Option<Vec<i64>>, pub barcodes: Option<Vec<i64>>,
/// Variant Need Conditions /// Variant Need Conditions
pub needs: Option<VariantNeedCondition>, pub needs: Option<VariantNeedCondition>,
/// Custom fields as JSON object schema
pub meta: Option<serde_json::Value>,
/// Quantifiable
pub unit: Option<Quanta>
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Quanta {
pub by: String,
pub conversions: HashMap<String, f64>
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -57,51 +67,8 @@ pub struct VariantNeedCondition {
impl Variant { impl Variant {
/// Create variant from itemdb yaml /// Create variant from itemdb yaml
pub fn from_yml(json: &serde_yaml::Value, variant: &str, item: &str) -> Self { pub fn from_yml(json: serde_yaml::Value, variant: &str, item: &str) -> Self {
Self { serde_yaml::from_value(json).unwrap()
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(),
],
}
}),
}
} }
/// Get a API id for this Item Variant. /// Get a API id for this Item Variant.
@ -175,8 +142,10 @@ impl Variant {
origin: Option<&str>, origin: Option<&str>,
location: Option<&str>, location: Option<&str>,
note: Option<&str>, note: Option<&str>,
quanta: Option<i64>,
properties: Option<serde_json::Value>
) -> Transaction { ) -> 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 /// Returns all Transactions of this Item Variant

View file

@ -141,44 +141,41 @@ async fn rocket() -> _ {
integrity::verify_integrity(&config, &flows, &locations, &itemdb).await; integrity::verify_integrity(&config, &flows, &locations, &itemdb).await;
rocket::build() rocket::build()
.mount( .mount("/", route![
"/", routes::item::get_items_route,
route![ routes::item::item_route,
routes::item::get_items_route, routes::item::item_variants_page,
routes::item::item_route, routes::item::supply_log_route,
routes::item::item_variants_page, routes::item::demand_log_route,
routes::item::supply_log_route, routes::item::supply_route,
routes::item::demand_log_route, routes::item::demand_route,
routes::item::supply_route, routes::item::transaction_route,
routes::item::demand_route, routes::item::inventory_route,
routes::item::transaction_route, routes::item::inventory_route_variant,
routes::item::inventory_route, routes::item::variant_stat_route,
routes::item::inventory_route_variant, routes::item::unique_field_route,
routes::item::variant_stat_route, routes::item::location_info,
routes::item::unique_field_route, routes::item::locations_info,
routes::item::location_info, routes::item::locations_list,
routes::item::locations_info, routes::item::location_inventory,
routes::item::locations_list, routes::flow::flow_info,
routes::item::location_inventory, routes::flow::flows_list,
routes::flow::flow_info, routes::item::expired_items_route,
routes::flow::flows_list, routes::item::min_items_route,
routes::item::expired_items_route, routes::item::variant_price_history_by_origin,
routes::item::min_items_route, routes::flow::end_flow_route,
routes::item::variant_price_history_by_origin, routes::flow::continue_flow_route,
routes::flow::end_flow_route, routes::flow::create_flow_route,
routes::flow::continue_flow_route, routes::item::location_condition_warn,
routes::flow::create_flow_route, routes::item::move_transaction_route,
routes::item::location_condition_warn, routes::item::variant_price_latest_by_origin,
routes::item::move_transaction_route, routes::item::item_stat_route,
routes::item::variant_price_latest_by_origin, routes::flow::active_flows_route,
routes::item::item_stat_route, routes::flow::flow_api_route,
routes::flow::active_flows_route, routes::flow::create_flow_note_route,
routes::flow::flow_api_route, routes::flow::flow_notes_route,
routes::flow::create_flow_note_route, routes::item::item_image_route
routes::flow::flow_notes_route, ])
routes::item::item_image_route
],
)
.manage(itemdb) .manage(itemdb)
.manage(locations) .manage(locations)
.manage(flows) .manage(flows)

View file

@ -21,6 +21,8 @@ pub struct SupplyForm {
pub origin: Option<String>, pub origin: Option<String>,
pub location: Option<String>, pub location: Option<String>,
pub note: Option<String>, pub note: Option<String>,
pub quanta: Option<i64>,
pub properties: Option<serde_json::Value>
} }
#[post("/supply", data = "<form>")] #[post("/supply", data = "<form>")]
@ -39,12 +41,16 @@ pub async fn supply_route(
.variant(&form.variant) .variant(&form.variant)
.ok_or_else(variant_does_not_exist_error)?; .ok_or_else(variant_does_not_exist_error)?;
// TODO : check properties schema
let transaction = variant let transaction = variant
.supply( .supply(
form.price, form.price,
form.origin.as_deref(), form.origin.as_deref(),
form.location.as_deref(), form.location.as_deref(),
form.note.as_deref(), form.note.as_deref(),
form.quanta,
form.properties.clone()
) )
.await; .await;