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:
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)),
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())

View file

@ -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),
);
}

View file

@ -25,6 +25,10 @@ pub struct Transaction {
pub location: Option<String>,
/// Notes on Transaction
pub note: Option<String>,
/// Qantized Unit
pub quanta: Option<i64>,
/// Custom properties
pub properties: Option<serde_json::Value>,
/// Timestamp of the Transaction
pub created: chrono::DateTime<chrono::Utc>,
/// Destination of the Item or who consumed it
@ -35,6 +39,18 @@ pub struct Transaction {
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 {
pub async fn new(
item: &str,
@ -43,14 +59,18 @@ impl Transaction {
origin: Option<&str>,
location: Option<&str>,
note: Option<&str>,
quanta: Option<i64>,
properties: Option<serde_json::Value>
) -> 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
})
}
}

View file

@ -47,6 +47,16 @@ pub struct Variant {
pub barcodes: Option<Vec<i64>>,
/// Variant Need Conditions
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)]
@ -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<i64>,
properties: Option<serde_json::Value>
) -> 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

View file

@ -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)

View file

@ -21,6 +21,8 @@ pub struct SupplyForm {
pub origin: Option<String>,
pub location: Option<String>,
pub note: Option<String>,
pub quanta: Option<i64>,
pub properties: Option<serde_json::Value>
}
#[post("/supply", data = "<form>")]
@ -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;