Compare commits

...

10 commits

Author SHA1 Message Date
31d5fd2b83
fix 2024-10-20 22:13:10 +02:00
1cc0b61c35
update 2024-10-20 04:04:50 +02:00
2823eea3ee
fix 2024-10-19 20:13:37 +02:00
4c0769199c
image 2024-10-18 10:57:38 +02:00
6c6ee93fbf
update 2024-10-17 11:33:03 +02:00
b057c46cbe
add barcodes 2024-10-08 10:42:21 +02:00
64e23cc4ab
fix 2024-10-07 21:53:11 +02:00
3eea2be3e8
fix 2024-10-07 21:35:50 +02:00
b4df75dcb6
fix 2024-10-07 21:28:07 +02:00
d5a82fff6a
fix 2024-10-07 21:23:16 +02:00
9 changed files with 119 additions and 36 deletions

View file

@ -64,3 +64,14 @@ variants:
``` ```
This will mark any item variant as expired if it's older than 30 days. This will mark any item variant as expired if it's older than 30 days.
### Barcodes
You can associate barcodes with your item variants. This is useful for Quick Adding Items.
```yml
name: "Water"
variants:
regular:
name: "Regular Water"
barcodes: [12345678]
```

View file

@ -6,7 +6,7 @@ CREATE TABLE flows (
ended timestamptz, ended timestamptz,
"next" UUID, "next" UUID,
produced UUID[], produced UUID[],
FOREIGN KEY "next" REFERENCES flows(id) FOREIGN KEY("next") REFERENCES flows(id)
); );
CREATE TABLE flow_notes ( CREATE TABLE flow_notes (
@ -14,7 +14,7 @@ CREATE TABLE flow_notes (
time timestamptz NOT NULL DEFAULT current_timestamp, time timestamptz NOT NULL DEFAULT current_timestamp,
content TEXT NOT NULL, content TEXT NOT NULL,
on_flow UUID NOT NULL, on_flow UUID NOT NULL,
FOREIGN KEY on_flow REFERENCES flows(id) FOREIGN KEY(on_flow) REFERENCES flows(id)
); );
CREATE TABLE transactions ( CREATE TABLE transactions (
@ -22,7 +22,7 @@ CREATE TABLE transactions (
created timestamptz NOT NULL DEFAULT current_timestamp, created timestamptz NOT NULL DEFAULT current_timestamp,
item TEXT NOT NULL, item TEXT NOT NULL,
variant TEXT NOT NULL, variant TEXT NOT NULL,
price NUMERIC(2), price DOUBLE PRECISION,
origin TEXT, origin TEXT,
"location" TEXT, "location" TEXT,
note TEXT, note TEXT,

View file

@ -1,3 +0,0 @@
[default]
address = "0.0.0.0"
port = 8080

View file

@ -15,27 +15,33 @@
"type": "string" "type": "string"
}, },
"variants": { "variants": {
"type": "array", "type": "object",
"minItems": 1, "minProperties": 1,
"items": { "title": "Item Variant",
"type": "object", "description": "A Variant of an Item",
"title": "Item Variant", "properties": {
"description": "A Variant of an Item", "name": {
"properties": { "type": "string",
"name": { "title": "Variant Name",
"type": "string", "description": "The name of the Variant"
"title": "Variant Name", },
"description": "The name of the Variant" "min": {
}, "type": "number",
"min": { "title": "Minimum inventory",
"description": "The minimum amount of inventory for an Item. Thre actual inventory amount should always be higher than that."
},
"expiry": {
"type": "number",
"title": "Expiry days",
"description": "Number of days until this item variant is considered expired."
},
"barcodes": {
"type": "array",
"title": "Associated Barcodes",
"description": "Associated Barcodes",
"items": {
"type": "number", "type": "number",
"title": "Minimum inventory", "title": "Barcode"
"description": "The minimum amount of inventory for an Item. Thre actual inventory amount should always be higher than that."
},
"expiry": {
"type": "number",
"title": "Expiry days",
"description": "Number of days until this item variant is considered expired."
} }
} }
} }

View file

@ -27,7 +27,7 @@ impl ItemDB {
/// Retrieves an item by name /// Retrieves an item by name
pub fn get_item(&self, item: &str) -> Option<&Item> { pub fn get_item(&self, item: &str) -> Option<&Item> {
self.index.get(item) self.index.get(&item.to_lowercase())
} }
/// Get all items /// Get all items

View file

@ -28,20 +28,37 @@ pub struct Item {
pub name: String, pub name: String,
/// Category of the Item /// Category of the Item
pub category: Option<String>, pub category: Option<String>,
/// Image
pub image_path: Option<String>,
/// The variants of an Item. /// The variants of an Item.
/// Each key of the `HashMap<_>` is the ID of a variant and contains a `Variant` /// Each key of the `HashMap<_>` is the ID of a variant and contains a `Variant`
pub variants: HashMap<String, Variant>, pub variants: HashMap<String, Variant>,
} }
pub fn get_image(path: &std::path::Path) -> Option<String> {
let parent = path.parent()?;
let file_name = path.file_stem()?.to_str()?;
for ext in ["jpg", "jpeg", "webp", "png"] {
let mut img_file = parent.to_path_buf();
img_file.push(&format!("{file_name}.{ext}"));
if img_file.exists() {
return Some(img_file.display().to_string());
}
}
None
}
impl Item { impl Item {
/// Creates a new `Item` from a parsed markdown document /// Creates a new `Item` from a parsed markdown document
pub fn new(doc: &mdq::Document) -> Self { pub fn new(doc: &mdq::Document) -> Self {
let id = std::path::Path::new(&doc.path) let path = std::path::Path::new(&doc.path);
.file_stem()
.unwrap() let id = path.file_stem().unwrap().to_str().unwrap().to_lowercase();
.to_str()
.unwrap() let image_path = get_image(path);
.to_string();
let category = doc let category = doc
.frontmatter .frontmatter
@ -86,6 +103,7 @@ impl Item {
id, id,
name, name,
category, category,
image_path,
variants, variants,
} }
} }
@ -131,6 +149,11 @@ impl Item {
json!({ json!({
"uuid": self.id, "uuid": self.id,
"image": if self.image_path.is_some() {
Some(format!("/item/{}/image", self.id))
} else {
None
},
"name": self.name, "name": self.name,
"category": self.category, "category": self.category,
"variants": variants "variants": variants

View file

@ -74,6 +74,29 @@ macro_rules! get_locations {
}; };
} }
pub static FLOW_INFO: OnceCell<JSONStore<FlowInfo>> = OnceCell::const_new();
#[macro_export]
macro_rules! get_flows {
() => {
if let Some(client) = $crate::FLOW_INFO.get() {
client
} else {
let mut flows: $crate::json_store::JSONStore<$crate::flow::FlowInfo> =
$crate::JSONStore::new("./flows");
let flow_keys: Vec<_> = flows.keys().cloned().collect();
for flow_key in flow_keys {
let flow = flows.get_mut(&flow_key).unwrap();
flow.id = flow_key.clone();
}
$crate::FLOW_INFO.set(flows).unwrap();
$crate::FLOW_INFO.get().unwrap()
}
};
}
// ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░ // ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░
// ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░ // ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░
// ░░░░░░░░░░▄███████▀░░░▀███▄░░░░░ // ░░░░░░░░░░▄███████▀░░░▀███▄░░░░░
@ -113,7 +136,7 @@ async fn rocket() -> _ {
let config = config::get_config(); let config = config::get_config();
let itemdb = get_itemdb!().clone(); let itemdb = get_itemdb!().clone();
let locations = get_locations!().clone(); let locations = get_locations!().clone();
let flows: JSONStore<FlowInfo> = JSONStore::new("./flows"); let flows = get_flows!().clone();
integrity::verify_integrity(&config, &flows, &locations, &itemdb).await; integrity::verify_integrity(&config, &flows, &locations, &itemdb).await;
rocket::build() rocket::build()
@ -150,7 +173,8 @@ async fn rocket() -> _ {
routes::flow::active_flows_route, routes::flow::active_flows_route,
routes::flow::flow_api_route, routes::flow::flow_api_route,
routes::flow::create_flow_note_route, routes::flow::create_flow_note_route,
routes::flow::flow_notes_route routes::flow::flow_notes_route,
routes::item::item_image_route
], ],
) )
.manage(itemdb) .manage(itemdb)

View file

@ -9,6 +9,7 @@ use std::str::FromStr;
pub use demand::*; pub use demand::*;
pub use error::*; pub use error::*;
pub use location::*; pub use location::*;
use rocket::fs::NamedFile;
use rocket::post; use rocket::post;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use serde::Deserialize; use serde::Deserialize;
@ -62,6 +63,17 @@ pub fn item_route(
Ok(item.api_json()) Ok(item.api_json())
} }
#[get("/item/<item_id>/image")]
pub async fn item_image_route(item_id: &str, itemdb: &State<ItemDB>) -> Option<NamedFile> {
let item = itemdb.get_item(item_id)?;
if let Some(img_path) = &item.image_path {
return Some(NamedFile::open(img_path).await.ok()?);
}
None
}
/// Returns all variants of an Item /// Returns all variants of an Item
#[get("/item/<item_id>/variants")] #[get("/item/<item_id>/variants")]
pub fn item_variants_page( pub fn item_variants_page(

View file

@ -43,6 +43,8 @@ pub struct Variant {
pub min: Option<i64>, pub min: Option<i64>,
/// Days until expiry /// Days until expiry
pub expiry: Option<i64>, pub expiry: Option<i64>,
/// Associated barcodes
pub barcodes: Option<Vec<i64>>,
} }
impl Variant { impl Variant {
@ -69,6 +71,13 @@ impl Variant {
.unwrap() .unwrap()
.get("expiry") .get("expiry")
.map(|x| x.as_i64().unwrap()), .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()
}),
} }
} }
@ -294,7 +303,8 @@ impl Variant {
"variant": self.variant, "variant": self.variant,
"name": self.name, "name": self.name,
"min": self.min, "min": self.min,
"expiry": self.expiry "expiry": self.expiry,
"barcodes": self.barcodes
}) })
} }
} }