aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-10-08 11:54:04 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-10-08 11:54:04 +0200
commit08cf86a44a9a7c513cd12cbc4a0bac7c029b9ded (patch)
tree88b202b25ec22b86f3b4df9f2022b7b23ec3cba1
parentchore(crates/rocie-client): Regenerate (diff)
downloadserver-08cf86a44a9a7c513cd12cbc4a0bac7c029b9ded.zip
feat(crates/rocie-server/unit-property): Init
Diffstat (limited to '')
-rw-r--r--crates/rocie-server/src/api/get/mod.rs3
-rw-r--r--crates/rocie-server/src/api/get/unit.rs36
-rw-r--r--crates/rocie-server/src/api/get/unit_property.rs68
-rw-r--r--crates/rocie-server/src/api/set/barcode.rs7
-rw-r--r--crates/rocie-server/src/api/set/mod.rs2
-rw-r--r--crates/rocie-server/src/api/set/product.rs15
-rw-r--r--crates/rocie-server/src/api/set/unit.rs13
-rw-r--r--crates/rocie-server/src/api/set/unit_property.rs47
-rw-r--r--crates/rocie-server/src/error.rs1
-rw-r--r--crates/rocie-server/src/main.rs3
-rw-r--r--crates/rocie-server/src/storage/migrate/sql/0->1.sql56
-rw-r--r--crates/rocie-server/src/storage/sql/barcode.rs9
-rw-r--r--crates/rocie-server/src/storage/sql/get/barcode/mod.rs4
-rw-r--r--crates/rocie-server/src/storage/sql/get/mod.rs1
-rw-r--r--crates/rocie-server/src/storage/sql/get/product/mod.rs11
-rw-r--r--crates/rocie-server/src/storage/sql/get/product_amount/mod.rs5
-rw-r--r--crates/rocie-server/src/storage/sql/get/unit/mod.rs11
-rw-r--r--crates/rocie-server/src/storage/sql/get/unit_property/mod.rs117
-rw-r--r--crates/rocie-server/src/storage/sql/insert/barcode/mod.rs9
-rw-r--r--crates/rocie-server/src/storage/sql/insert/mod.rs1
-rw-r--r--crates/rocie-server/src/storage/sql/insert/product/mod.rs17
-rw-r--r--crates/rocie-server/src/storage/sql/insert/unit/mod.rs22
-rw-r--r--crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs106
-rw-r--r--crates/rocie-server/src/storage/sql/mod.rs95
-rw-r--r--crates/rocie-server/src/storage/sql/product.rs88
-rw-r--r--crates/rocie-server/src/storage/sql/product_amount.rs2
-rw-r--r--crates/rocie-server/src/storage/sql/unit.rs100
-rw-r--r--crates/rocie-server/src/storage/sql/unit_property.rs23
28 files changed, 685 insertions, 187 deletions
diff --git a/crates/rocie-server/src/api/get/mod.rs b/crates/rocie-server/src/api/get/mod.rs
index 21684af..e21a120 100644
--- a/crates/rocie-server/src/api/get/mod.rs
+++ b/crates/rocie-server/src/api/get/mod.rs
@@ -3,11 +3,14 @@ use actix_web::web;
pub(crate) mod inventory;
pub(crate) mod product;
pub(crate) mod unit;
+pub(crate) mod unit_property;
pub(crate) fn register_paths(cfg: &mut web::ServiceConfig) {
cfg.service(product::product_by_id)
.service(product::products)
.service(unit::units)
.service(unit::unit_by_id)
+ .service(unit_property::unit_properties)
+ .service(unit_property::unit_property_by_id)
.service(inventory::amount_by_id);
}
diff --git a/crates/rocie-server/src/api/get/unit.rs b/crates/rocie-server/src/api/get/unit.rs
index 4854ea3..73aa626 100644
--- a/crates/rocie-server/src/api/get/unit.rs
+++ b/crates/rocie-server/src/api/get/unit.rs
@@ -5,8 +5,16 @@ use crate::{app::App, storage::sql::unit::{Unit, UnitId, UnitIdStub}};
/// Return all registered units
#[utoipa::path(
responses(
- (status = OK, description = "All units founds", body = Vec<Unit>),
- (status = INTERNAL_SERVER_ERROR, description = "Server encountered error", body = String)
+ (
+ status = OK,
+ description = "All units founds",
+ body = Vec<Unit>
+ ),
+ (
+ status = INTERNAL_SERVER_ERROR,
+ description = "Server encountered error",
+ body = String
+ )
),
)]
#[get("/units/")]
@@ -19,12 +27,26 @@ pub(crate) async fn units(app: web::Data<App>) -> Result<impl Responder> {
/// Get Unit by id
#[utoipa::path(
responses(
- (status = OK, description = "Unit found from database", body = Unit),
- (status = NOT_FOUND, description = "Unit not found in database"),
- (status = INTERNAL_SERVER_ERROR, description = "Server encountered error", body = String)
+ (
+ status = OK,
+ description = "Unit found from database",
+ body = Unit
+ ),
+ (
+ status = NOT_FOUND,
+ description = "Unit not found in database"
+ ),
+ (
+ status = INTERNAL_SERVER_ERROR,
+ description = "Server encountered error",
+ body = String
+ )
),
params(
- ("id" = UnitId, description = "Unit id" ),
+ (
+ "id" = UnitId,
+ description = "Unit id"
+ ),
)
)]
#[get("/unit/{id}")]
@@ -35,7 +57,7 @@ pub(crate) async fn unit_by_id(
let id = id.into_inner();
match Unit::from_id(&app, id.into()).await? {
- Some(product) => Ok(HttpResponse::Ok().json(product)),
+ Some(unit) => Ok(HttpResponse::Ok().json(unit)),
None => Ok(HttpResponse::NotFound().finish()),
}
}
diff --git a/crates/rocie-server/src/api/get/unit_property.rs b/crates/rocie-server/src/api/get/unit_property.rs
new file mode 100644
index 0000000..3160480
--- /dev/null
+++ b/crates/rocie-server/src/api/get/unit_property.rs
@@ -0,0 +1,68 @@
+use actix_web::{HttpResponse, Responder, Result, get, web};
+
+use crate::{
+ app::App,
+ storage::sql::{
+ unit_property::{UnitProperty, UnitPropertyId, UnitPropertyIdStub},
+ },
+};
+
+/// Return all registered unit properties
+#[utoipa::path(
+ responses(
+ (
+ status = OK,
+ description = "All unit properties founds",
+ body = Vec<UnitProperty>
+ ),
+ (
+ status = INTERNAL_SERVER_ERROR,
+ description = "Server encountered error",
+ body = String
+ )
+ ),
+)]
+#[get("/unit-properties/")]
+pub(crate) async fn unit_properties(app: web::Data<App>) -> Result<impl Responder> {
+ let all = UnitProperty::get_all(&app).await?;
+
+ Ok(HttpResponse::Ok().json(all))
+}
+
+/// Get Unit property by id
+#[utoipa::path(
+ responses(
+ (
+ status = OK,
+ description = "Unit property found from database",
+ body = UnitProperty
+ ),
+ (
+ status = NOT_FOUND,
+ description = "Unit Property not found in database"
+ ),
+ (
+ status = INTERNAL_SERVER_ERROR,
+ description = "Server encountered error",
+ body = String
+ )
+ ),
+ params(
+ (
+ "id" = UnitPropertyId,
+ description = "Unit Property id"
+ ),
+ )
+)]
+#[get("/unit-property/{id}")]
+pub(crate) async fn unit_property_by_id(
+ app: web::Data<App>,
+ id: web::Path<UnitPropertyIdStub>,
+) -> Result<impl Responder> {
+ let id = id.into_inner();
+
+ match UnitProperty::from_id(&app, id.into()).await? {
+ Some(unit_property) => Ok(HttpResponse::Ok().json(unit_property)),
+ None => Ok(HttpResponse::NotFound().finish()),
+ }
+}
diff --git a/crates/rocie-server/src/api/set/barcode.rs b/crates/rocie-server/src/api/set/barcode.rs
index 60b1650..29eac9e 100644
--- a/crates/rocie-server/src/api/set/barcode.rs
+++ b/crates/rocie-server/src/api/set/barcode.rs
@@ -4,8 +4,9 @@ use log::debug;
use crate::{
app::App,
storage::sql::{
- barcode::{Barcode, BarcodeId, BarcodeIdStub, UnitAmount},
+ barcode::{Barcode, BarcodeId, BarcodeIdStub},
insert::Operations,
+ unit::UnitAmount,
},
};
@@ -86,7 +87,9 @@ pub(crate) async fn consume_barcode(
match barcode {
Some(barcode) => {
- barcode.consume(&app, unit_amount.into_inner(), &mut ops).await?;
+ barcode
+ .consume(&app, unit_amount.into_inner(), &mut ops)
+ .await?;
ops.apply(&app).await?;
diff --git a/crates/rocie-server/src/api/set/mod.rs b/crates/rocie-server/src/api/set/mod.rs
index a7ddab5..5dd0219 100644
--- a/crates/rocie-server/src/api/set/mod.rs
+++ b/crates/rocie-server/src/api/set/mod.rs
@@ -3,11 +3,13 @@ use actix_web::web;
pub(crate) mod barcode;
pub(crate) mod product;
pub(crate) mod unit;
+pub(crate) mod unit_property;
pub(crate) fn register_paths(cfg: &mut web::ServiceConfig) {
cfg.service(product::register_product)
.service(product::associate_barcode)
.service(unit::register_unit)
+ .service(unit_property::register_unit_property)
.service(barcode::consume_barcode)
.service(barcode::buy_barcode);
}
diff --git a/crates/rocie-server/src/api/set/product.rs b/crates/rocie-server/src/api/set/product.rs
index d347ee7..7372d23 100644
--- a/crates/rocie-server/src/api/set/product.rs
+++ b/crates/rocie-server/src/api/set/product.rs
@@ -9,13 +9,22 @@ use crate::{
insert::Operations,
product::{Product, ProductId, ProductIdStub},
unit::Unit,
+ unit_property::UnitPropertyId,
},
};
#[derive(Deserialize, ToSchema)]
struct ProductStub {
+ /// The name of the product
name: String,
+
+ /// The Unit Property to use for this product.
+ unit_property: UnitPropertyId,
+
+ /// A description.
description: Option<String>,
+
+ /// A parent of this product, otherwise the parent will be the root of the parent tree.
parent: Option<ProductId>,
}
@@ -46,6 +55,7 @@ pub(crate) async fn register_product(
product_stub.name.clone(),
product_stub.description.clone(),
product_stub.parent,
+ product_stub.unit_property,
&mut ops,
);
@@ -77,7 +87,10 @@ pub(crate) async fn register_product(
)
),
params (
- ("id" = ProductId, description = "The id of the product to associated the barcode with"),
+ (
+ "id" = ProductId,
+ description = "The id of the product to associated the barcode with"
+ ),
),
request_body = Barcode,
)]
diff --git a/crates/rocie-server/src/api/set/unit.rs b/crates/rocie-server/src/api/set/unit.rs
index 4281e05..5f39c8f 100644
--- a/crates/rocie-server/src/api/set/unit.rs
+++ b/crates/rocie-server/src/api/set/unit.rs
@@ -2,13 +2,21 @@ use actix_web::{HttpResponse, Responder, Result, post, web};
use serde::Deserialize;
use utoipa::ToSchema;
-use crate::{app::App, storage::sql::{insert::Operations, unit::{Unit, UnitId}}};
+use crate::{
+ app::App,
+ storage::sql::{
+ insert::Operations,
+ unit::{Unit, UnitId},
+ unit_property::UnitPropertyId,
+ },
+};
#[derive(Deserialize, ToSchema)]
struct UnitStub {
full_name_plural: String,
full_name_singular: String,
short_name: String,
+ unit_property: UnitPropertyId,
description: Option<String>,
}
@@ -17,7 +25,7 @@ struct UnitStub {
responses(
(
status = 200,
- description = "Product successfully registered in database",
+ description = "Unit successfully registered in database",
body = UnitId,
),
(
@@ -40,6 +48,7 @@ pub(crate) async fn register_unit(
unit.full_name_plural.clone(),
unit.short_name.clone(),
unit.description.clone(),
+ unit.unit_property,
&mut ops,
);
diff --git a/crates/rocie-server/src/api/set/unit_property.rs b/crates/rocie-server/src/api/set/unit_property.rs
new file mode 100644
index 0000000..9915e84
--- /dev/null
+++ b/crates/rocie-server/src/api/set/unit_property.rs
@@ -0,0 +1,47 @@
+use actix_web::{HttpResponse, Responder, Result, post, web};
+use serde::Deserialize;
+use utoipa::ToSchema;
+
+use crate::{
+ app::App,
+ storage::sql::{
+ insert::Operations,
+ unit_property::{UnitProperty, UnitPropertyId},
+ },
+};
+
+#[derive(Deserialize, ToSchema)]
+struct UnitPropertyStub {
+ name: String,
+ description: Option<String>,
+}
+
+/// Register an Unit Property
+#[utoipa::path(
+ responses(
+ (
+ status = 200,
+ description = "Unit property successfully registered in database",
+ body = UnitPropertyId,
+ ),
+ (
+ status = INTERNAL_SERVER_ERROR,
+ description = "Server encountered error",
+ body = String,
+ )
+ ),
+ request_body = UnitPropertyStub,
+)]
+#[post("/unit-property/new")]
+pub(crate) async fn register_unit_property(
+ app: web::Data<App>,
+ unit: web::Json<UnitPropertyStub>,
+) -> Result<impl Responder> {
+ let mut ops = Operations::new("register unit property");
+
+ let unit = UnitProperty::register(unit.name.clone(), unit.description.clone(), &mut ops);
+
+ ops.apply(&app).await?;
+
+ Ok(HttpResponse::Ok().json(unit.id))
+}
diff --git a/crates/rocie-server/src/error.rs b/crates/rocie-server/src/error.rs
deleted file mode 100644
index 8b13789..0000000
--- a/crates/rocie-server/src/error.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/crates/rocie-server/src/main.rs b/crates/rocie-server/src/main.rs
index 8e71034..af36c9e 100644
--- a/crates/rocie-server/src/main.rs
+++ b/crates/rocie-server/src/main.rs
@@ -22,10 +22,13 @@ async fn main() -> Result<(), std::io::Error> {
api::get::product::products,
api::get::unit::units,
api::get::unit::unit_by_id,
+ api::get::unit_property::unit_property_by_id,
+ api::get::unit_property::unit_properties,
api::get::inventory::amount_by_id,
api::set::product::register_product,
api::set::product::associate_barcode,
api::set::unit::register_unit,
+ api::set::unit_property::register_unit_property,
api::set::barcode::buy_barcode,
api::set::barcode::consume_barcode,
),
diff --git a/crates/rocie-server/src/storage/migrate/sql/0->1.sql b/crates/rocie-server/src/storage/migrate/sql/0->1.sql
index fd48c68..7f08738 100644
--- a/crates/rocie-server/src/storage/migrate/sql/0->1.sql
+++ b/crates/rocie-server/src/storage/migrate/sql/0->1.sql
@@ -57,10 +57,12 @@ END;
CREATE TABLE products (
id TEXT UNIQUE NOT NULL PRIMARY KEY,
- name TEXT NOT NULL,
+ name TEXT UNIQUE NOT NULL,
description TEXT,
parent TEXT DEFAULT NULL,
- FOREIGN KEY(parent) REFERENCES parents(id)
+ unit_property TEXT NOT NULL,
+ FOREIGN KEY(parent) REFERENCES parents(id),
+ FOREIGN KEY(unit_property) REFERENCES unit_properties(id)
) STRICT;
CREATE TABLE barcodes (
@@ -71,12 +73,41 @@ CREATE TABLE barcodes (
FOREIGN KEY(product_id) REFERENCES products(id),
FOREIGN KEY(unit) REFERENCES units(id)
) STRICT;
+CREATE TRIGGER unit_has_property_of_product_update
+BEFORE UPDATE OF product_id,unit ON barcodes
+FOR EACH ROW
+BEGIN
+ SELECT RAISE(FAIL, "Used different unit_property for product_id and unit")
+ FROM products, units
+ WHERE NEW.product_id = products.id
+ AND NEW.unit = units.id
+ AND units.unit_property != products.unit_property;
+END;
+
+CREATE TRIGGER unit_has_property_of_product_insert
+BEFORE INSERT ON barcodes
+FOR EACH ROW
+BEGIN
+ SELECT RAISE(FAIL, "Used different unit_property for product_id and unit")
+ FROM products, units
+ WHERE NEW.product_id = products.id
+ AND NEW.unit = units.id
+ AND units.unit_property != products.unit_property;
+END;
CREATE TABLE units (
id TEXT UNIQUE NOT NULL PRIMARY KEY,
+ unit_property TEXT UNIQUE NOT NULL,
full_name_singular TEXT UNIQUE NOT NULL,
full_name_plural TEXT UNIQUE NOT NULL,
short_name TEXT UNIQUE NOT NULL,
+ description TEXT,
+ FOREIGN KEY(unit_property) REFERENCES unit_properties(id)
+) STRICT;
+
+CREATE TABLE unit_properties (
+ id TEXT UNIQUE NOT NULL PRIMARY KEY,
+ name TEXT UNIQUE NOT NULL,
description TEXT
) STRICT;
@@ -90,6 +121,27 @@ CREATE TABLE unit_conversions (
FOREIGN KEY(from_unit) REFERENCES units(id),
FOREIGN KEY(to_unit) REFERENCES units(id)
) STRICT;
+CREATE TRIGGER both_units_have_same_unit_property_update
+BEFORE UPDATE OF from_unit,to_unit ON unit_conversions
+FOR EACH ROW
+BEGIN
+ SELECT RAISE(FAIL, "Used not the same unit_property")
+ FROM units, units AS units2
+ WHERE NEW.from_unit = units.id
+ AND NEW.to_unit = units2.id
+ AND units.unit_property != units2.unit_property;
+END;
+
+CREATE TRIGGER both_units_have_same_unit_property_insert
+BEFORE INSERT ON unit_conversions
+FOR EACH ROW
+BEGIN
+ SELECT RAISE(FAIL, "Used not the same unit_property")
+ FROM units, units AS units2
+ WHERE NEW.from_unit = units.id
+ AND NEW.to_unit = units2.id
+ AND units.unit_property != units2.unit_property;
+END;
-- Log of all the applied operations to this db.
CREATE TABLE txn_log (
diff --git a/crates/rocie-server/src/storage/sql/barcode.rs b/crates/rocie-server/src/storage/sql/barcode.rs
index 1c3c55a..f15d399 100644
--- a/crates/rocie-server/src/storage/sql/barcode.rs
+++ b/crates/rocie-server/src/storage/sql/barcode.rs
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
-use crate::storage::sql::unit::UnitId;
+use crate::storage::sql::unit::UnitAmount;
#[derive(ToSchema, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Barcode {
@@ -41,10 +41,3 @@ impl From<BarcodeIdStub> for BarcodeId {
Self { value: value.value }
}
}
-
-#[derive(ToSchema, Debug, Clone, Copy, Serialize, Deserialize)]
-pub(crate) struct UnitAmount {
- #[schema(minimum = 0)]
- pub(crate) value: u32,
- pub(crate) unit: UnitId,
-}
diff --git a/crates/rocie-server/src/storage/sql/get/barcode/mod.rs b/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
index 7b656b1..4eba105 100644
--- a/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
@@ -1,8 +1,8 @@
use crate::{
app::App,
storage::sql::{
- barcode::{Barcode, BarcodeId, UnitAmount},
- unit::UnitId,
+ barcode::{Barcode, BarcodeId},
+ unit::{UnitAmount, UnitId},
},
};
diff --git a/crates/rocie-server/src/storage/sql/get/mod.rs b/crates/rocie-server/src/storage/sql/get/mod.rs
index 048cb3d..62047b8 100644
--- a/crates/rocie-server/src/storage/sql/get/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/mod.rs
@@ -1,4 +1,5 @@
pub(crate) mod product;
pub(crate) mod product_amount;
pub(crate) mod unit;
+pub(crate) mod unit_property;
pub(crate) mod barcode;
diff --git a/crates/rocie-server/src/storage/sql/get/product/mod.rs b/crates/rocie-server/src/storage/sql/get/product/mod.rs
index 541f388..01047b2 100644
--- a/crates/rocie-server/src/storage/sql/get/product/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/product/mod.rs
@@ -1,9 +1,10 @@
use crate::{
app::App,
storage::sql::{
- barcode::{Barcode, BarcodeId, UnitAmount},
+ barcode::{Barcode, BarcodeId},
product::{Product, ProductId},
- unit::UnitId,
+ unit::{UnitAmount, UnitId},
+ unit_property::UnitPropertyId,
},
};
@@ -13,7 +14,7 @@ impl Product {
pub(crate) async fn from_id(app: &App, id: ProductId) -> Result<Option<Self>, from_id::Error> {
let record = query!(
"
- SELECT name, description, parent
+ SELECT name, unit_property, description, parent
FROM products
WHERE id = ?
",
@@ -25,6 +26,7 @@ impl Product {
if let Some(record) = record {
Ok(Some(Self {
id,
+ unit_property: UnitPropertyId::from_db(&record.unit_property),
name: record.name,
description: record.description,
associated_bar_codes: vec![], // todo
@@ -37,7 +39,7 @@ impl Product {
pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
let records = query!(
"
- SELECT id, name, description, parent
+ SELECT id, unit_property, name, description, parent
FROM products
"
)
@@ -59,6 +61,7 @@ impl Product {
all.push(Self {
id: ProductId::from_db(&record.id),
+ unit_property: UnitPropertyId::from_db(&record.unit_property),
name: record.name,
description: record.description,
associated_bar_codes: barcodes
diff --git a/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs b/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
index 7700274..f82c2a0 100644
--- a/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
@@ -1,8 +1,6 @@
use crate::{
app::App,
- storage::sql::{
- barcode::UnitAmount, product::ProductId, product_amount::ProductAmount, unit::UnitId,
- },
+ storage::sql::{product::ProductId, product_amount::ProductAmount, unit::{UnitAmount, UnitId}},
};
use sqlx::query;
@@ -20,6 +18,7 @@ SELECT
FROM barcodes
JOIN products ON products.id = ?
JOIN buys ON buys.barcode_id = barcodes.id
+WHERE barcodes.product_id = products.id
GROUP BY barcodes.unit;
"#,
product_id
diff --git a/crates/rocie-server/src/storage/sql/get/unit/mod.rs b/crates/rocie-server/src/storage/sql/get/unit/mod.rs
index 6c2bbcc..6f5d297 100644
--- a/crates/rocie-server/src/storage/sql/get/unit/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/unit/mod.rs
@@ -1,6 +1,9 @@
use crate::{
app::App,
- storage::sql::unit::{Unit, UnitId},
+ storage::sql::{
+ unit::{Unit, UnitId},
+ unit_property::UnitPropertyId,
+ },
};
use sqlx::query;
@@ -9,7 +12,7 @@ impl Unit {
pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
let records = query!(
"
- SELECT id, full_name_singular, full_name_plural, short_name, description
+ SELECT id, unit_property, full_name_singular, full_name_plural, short_name, description
FROM units
"
)
@@ -20,6 +23,7 @@ impl Unit {
.into_iter()
.map(|record| Self {
id: UnitId::from_db(&record.id),
+ unit_property: UnitPropertyId::from_db(&record.unit_property),
full_name_singular: record.full_name_singular,
full_name_plural: record.full_name_plural,
short_name: record.short_name,
@@ -31,7 +35,7 @@ impl Unit {
pub(crate) async fn from_id(app: &App, id: UnitId) -> Result<Option<Self>, from_id::Error> {
let record = query!(
"
- SELECT full_name_singular, full_name_plural, short_name, description
+ SELECT full_name_singular, unit_property, full_name_plural, short_name, description
FROM units
WHERE id = ?
",
@@ -43,6 +47,7 @@ impl Unit {
if let Some(record) = record {
Ok(Some(Self {
id,
+ unit_property: UnitPropertyId::from_db(&record.unit_property),
full_name_singular: record.full_name_singular,
full_name_plural: record.full_name_plural,
short_name: record.short_name,
diff --git a/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs b/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs
new file mode 100644
index 0000000..be24181
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs
@@ -0,0 +1,117 @@
+use crate::{
+ app::App,
+ storage::sql::{
+ unit::UnitId,
+ unit_property::{UnitProperty, UnitPropertyId},
+ },
+};
+
+use sqlx::query;
+
+impl UnitProperty {
+ pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
+ let records = query!(
+ "
+ SELECT id, name, description
+ FROM unit_properties
+"
+ )
+ .fetch_all(&app.db)
+ .await?;
+
+ let mut output = Vec::with_capacity(records.len());
+ for record in records {
+ let units = query!(
+ "
+ SELECT id
+ FROM units
+ WHERE units.unit_property = ?
+ ",
+ record.id
+ )
+ .fetch_all(&app.db)
+ .await?;
+
+ let units = units
+ .into_iter()
+ .map(|record| UnitId::from_db(&record.id))
+ .collect();
+
+ output.push(Self {
+ id: UnitPropertyId::from_db(&record.id),
+ units,
+ name: record.name,
+ description: record.description,
+ })
+ }
+
+ Ok(output)
+ }
+
+ pub(crate) async fn from_id(
+ app: &App,
+ id: UnitPropertyId,
+ ) -> Result<Option<Self>, from_id::Error> {
+ let record = query!(
+ "
+ SELECT name, description
+ FROM unit_properties
+ WHERE id = ?
+",
+ id
+ )
+ .fetch_optional(&app.db)
+ .await?;
+
+ if let Some(record) = record {
+ let units = query!(
+ "
+ SELECT id
+ FROM units
+ WHERE units.unit_property = ?
+ ",
+ id
+ )
+ .fetch_all(&app.db)
+ .await?;
+
+ let units = units
+ .into_iter()
+ .map(|record| UnitId::from_db(&record.id))
+ .collect();
+
+ Ok(Some(Self {
+ id,
+ units,
+ name: record.name,
+ description: record.description,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+pub(crate) mod get_all {
+ use actix_web::ResponseError;
+
+ #[derive(thiserror::Error, Debug)]
+ pub(crate) enum Error {
+ #[error("Failed to execute the sql query")]
+ SqlError(#[from] sqlx::Error),
+ }
+
+ impl ResponseError for Error {}
+}
+
+pub(crate) mod from_id {
+ use actix_web::ResponseError;
+
+ #[derive(thiserror::Error, Debug)]
+ pub(crate) enum Error {
+ #[error("Failed to execute the sql query")]
+ SqlError(#[from] sqlx::Error),
+ }
+
+ impl ResponseError for Error {}
+}
diff --git a/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
index 62a2e11..11707b9 100644
--- a/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
@@ -9,9 +9,9 @@ use crate::{
storage::{
migrate::get_current_date,
sql::{
- barcode::{Barcode, BarcodeId, UnitAmount},
+ barcode::{Barcode, BarcodeId},
insert::{Operations, Transactionable},
- unit::Unit,
+ unit::{Unit, UnitAmount},
},
},
};
@@ -208,10 +208,7 @@ pub(crate) mod consume {
use crate::storage::{
self,
- sql::{
- barcode::UnitAmount,
- unit::{Unit, UnitId},
- },
+ sql::unit::{Unit, UnitAmount, UnitId},
};
#[derive(thiserror::Error, Debug)]
diff --git a/crates/rocie-server/src/storage/sql/insert/mod.rs b/crates/rocie-server/src/storage/sql/insert/mod.rs
index e6728d9..3b2d702 100644
--- a/crates/rocie-server/src/storage/sql/insert/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/mod.rs
@@ -10,6 +10,7 @@ use sqlx::{SqliteConnection, query};
pub(crate) mod barcode;
pub(crate) mod product;
pub(crate) mod unit;
+pub(crate) mod unit_property;
pub(crate) trait Transactionable:
Sized + std::fmt::Debug + Serialize + DeserializeOwned
diff --git a/crates/rocie-server/src/storage/sql/insert/product/mod.rs b/crates/rocie-server/src/storage/sql/insert/product/mod.rs
index e14d3f4..d762e9b 100644
--- a/crates/rocie-server/src/storage/sql/insert/product/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/product/mod.rs
@@ -6,6 +6,7 @@ use crate::storage::sql::{
barcode::Barcode,
insert::{Operations, Transactionable},
product::{Product, ProductId},
+ unit_property::UnitPropertyId,
};
#[derive(Debug, Deserialize, Serialize)]
@@ -15,6 +16,7 @@ pub(crate) enum Operation {
name: String,
description: Option<String>,
parent: Option<ProductId>,
+ unit_property: UnitPropertyId,
},
AssociateBarcode {
id: ProductId,
@@ -33,13 +35,15 @@ impl Transactionable for Operation {
name,
description,
parent,
+ unit_property,
} => {
query!(
"
- INSERT INTO products (id, name, description, parent)
- VALUES (?,?,?,?)
+ INSERT INTO products (id, unit_property, name, description, parent)
+ VALUES (?,?,?,?,?)
",
id,
+ unit_property,
name,
description,
parent
@@ -75,17 +79,19 @@ impl Transactionable for Operation {
id,
name,
description,
+ unit_property,
parent,
} => {
query!(
"
DELETE FROM products
- WHERE id = ? AND name = ? AND description = ? AND parent = ?;
+ WHERE id = ? AND name = ? AND description = ? AND parent = ? AND unit_property = ?;
",
id,
name,
description,
- parent
+ parent,
+ unit_property
)
.execute(txn)
.await?;
@@ -133,6 +139,7 @@ impl Product {
name: String,
description: Option<String>,
parent: Option<ProductId>,
+ unit_property: UnitPropertyId,
ops: &mut Operations<Operation>,
) -> Self {
let id = ProductId::from(Uuid::new_v4());
@@ -141,6 +148,7 @@ impl Product {
id,
name: name.clone(),
description: description.clone(),
+ unit_property,
parent,
});
@@ -148,6 +156,7 @@ impl Product {
id,
name,
description,
+ unit_property,
associated_bar_codes: vec![],
}
}
diff --git a/crates/rocie-server/src/storage/sql/insert/unit/mod.rs b/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
index ba08487..815cb1e 100644
--- a/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
@@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize};
use sqlx::query;
use uuid::Uuid;
-use crate::storage::sql::{insert::{Operations, Transactionable}, unit::{Unit, UnitId}};
+use crate::storage::sql::{
+ insert::{Operations, Transactionable},
+ unit::{Unit, UnitId},
+ unit_property::UnitPropertyId,
+};
#[derive(Debug, Deserialize, Serialize)]
pub(crate) enum Operation {
@@ -11,6 +15,7 @@ pub(crate) enum Operation {
full_name_singular: String,
full_name_plural: String,
short_name: String,
+ unit_property: UnitPropertyId,
description: Option<String>,
},
}
@@ -27,13 +32,14 @@ impl Transactionable for Operation {
full_name_plural,
short_name,
description,
+ unit_property,
} => {
query!(
"
- INSERT INTO units (id, full_name_singular, full_name_plural, short_name, description)
- VALUES (?,?,?,?,?)
+ INSERT INTO units (id, unit_property, full_name_singular, full_name_plural, short_name, description)
+ VALUES (?,?,?,?,?,?)
",
- id, full_name_singular, full_name_plural, short_name, description,
+ id, unit_property, full_name_singular, full_name_plural, short_name, description,
)
.execute(txn)
.await?;
@@ -50,13 +56,14 @@ impl Transactionable for Operation {
full_name_plural,
short_name,
description,
+ unit_property,
} => {
query!(
"
DELETE FROM units
- WHERE id = ? AND full_name_singular = ? AND full_name_plural = ? AND short_name = ? AND description = ?;
+ WHERE id = ? AND full_name_singular = ? AND full_name_plural = ? AND short_name = ? AND description = ? AND unit_property = ?;
",
- id, full_name_singular, full_name_plural, short_name, description,
+ id, full_name_singular, full_name_plural, short_name, description, unit_property
)
.execute(txn)
.await?;
@@ -87,6 +94,7 @@ impl Unit {
full_name_plural: String,
short_name: String,
description: Option<String>,
+ unit_property: UnitPropertyId,
ops: &mut Operations<Operation>,
) -> Self {
let id = UnitId::from(Uuid::new_v4());
@@ -97,6 +105,7 @@ impl Unit {
full_name_plural: full_name_plural.clone(),
short_name: short_name.clone(),
description: description.clone(),
+ unit_property,
});
Self {
@@ -105,6 +114,7 @@ impl Unit {
full_name_plural,
short_name,
description,
+ unit_property,
}
}
}
diff --git a/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs b/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs
new file mode 100644
index 0000000..d340465
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs
@@ -0,0 +1,106 @@
+use serde::{Deserialize, Serialize};
+use sqlx::query;
+use uuid::Uuid;
+
+use crate::storage::sql::{
+ insert::{Operations, Transactionable},
+ unit_property::{UnitProperty, UnitPropertyId},
+};
+
+#[derive(Debug, Deserialize, Serialize)]
+pub(crate) enum Operation {
+ RegisterUnitProperty {
+ id: UnitPropertyId,
+ name: String,
+ description: Option<String>,
+ },
+}
+
+impl Transactionable for Operation {
+ type ApplyError = apply::Error;
+ type UndoError = undo::Error;
+
+ async fn apply(self, txn: &mut sqlx::SqliteConnection) -> Result<(), apply::Error> {
+ match self {
+ Operation::RegisterUnitProperty {
+ id,
+ name,
+ description,
+ } => {
+ query!(
+ "
+ INSERT INTO unit_properties (id, name, description)
+ VALUES (?,?,?)
+",
+ id,
+ name,
+ description,
+ )
+ .execute(txn)
+ .await?;
+ }
+ }
+ Ok(())
+ }
+
+ async fn undo(self, txn: &mut sqlx::SqliteConnection) -> Result<(), undo::Error> {
+ match self {
+ Operation::RegisterUnitProperty {
+ id,
+ name,
+ description,
+ } => {
+ query!(
+ "
+ DELETE FROM unit_properties
+ WHERE id = ? AND name = ? AND description = ?;
+",
+ id,
+ name,
+ description
+ )
+ .execute(txn)
+ .await?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub(crate) mod undo {
+ #[derive(thiserror::Error, Debug)]
+ pub(crate) enum Error {
+ #[error("Failed to execute undo sql statments: {0}")]
+ SqlError(#[from] sqlx::Error),
+ }
+}
+pub(crate) mod apply {
+ #[derive(thiserror::Error, Debug)]
+ pub(crate) enum Error {
+ #[error("Failed to execute apply sql statments: {0}")]
+ SqlError(#[from] sqlx::Error),
+ }
+}
+
+impl UnitProperty {
+ pub(crate) fn register(
+ name: String,
+ description: Option<String>,
+ ops: &mut Operations<Operation>,
+ ) -> Self {
+ let id = UnitPropertyId::from(Uuid::new_v4());
+
+ ops.push(Operation::RegisterUnitProperty {
+ id,
+ name: name.clone(),
+ description: description.clone(),
+ });
+
+ Self {
+ id,
+ units: vec![],
+ name,
+ description,
+ }
+ }
+}
diff --git a/crates/rocie-server/src/storage/sql/mod.rs b/crates/rocie-server/src/storage/sql/mod.rs
index edce187..a44fbad 100644
--- a/crates/rocie-server/src/storage/sql/mod.rs
+++ b/crates/rocie-server/src/storage/sql/mod.rs
@@ -2,7 +2,100 @@ pub(crate) mod get;
pub(crate) mod insert;
// Types
+pub(crate) mod barcode;
pub(crate) mod product;
pub(crate) mod product_amount;
pub(crate) mod unit;
-pub(crate) mod barcode;
+pub(crate) mod unit_property;
+
+macro_rules! mk_id {
+ ($name:ident and $stub_name:ident) => {
+ mk_id!($name and $stub_name with uuid::Uuid, "uuid::Uuid");
+ };
+ ($name:ident and $stub_name:ident with $inner:path, $inner_string:literal $($args:meta)*) => {
+ #[derive(
+ serde::Deserialize,
+ serde::Serialize,
+ Debug,
+ Default,
+ utoipa::ToSchema,
+ Clone,
+ Copy,
+ PartialEq,
+ Eq,
+ PartialOrd,
+ Ord,
+ )]
+ pub(crate) struct $name {
+ value: $inner,
+ }
+
+ #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
+ #[serde(from = $inner_string)]
+ pub(crate) struct $stub_name {
+ value: $inner,
+ }
+
+ impl $name {
+ pub(crate) fn from_db(id: &str) -> Self {
+ use std::str::FromStr;
+
+ Self {
+ value: <$inner as FromStr>::from_str(id)
+ .expect(
+ concat!(
+ "We put an ",
+ $inner_string,
+ " into the db, it should also go out again"
+ )
+ ),
+ }
+ }
+ }
+
+ impl std::fmt::Display for $name {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.value)
+ }
+ }
+
+ impl From<$inner> for $name {
+ fn from(value: $inner) -> Self {
+ Self { value }
+ }
+ }
+ impl From<$inner> for $stub_name {
+ fn from(value: $inner) -> Self {
+ Self { value }
+ }
+ }
+ impl From<$stub_name> for $name {
+ fn from(value: $stub_name) -> Self {
+ Self { value: value.value }
+ }
+ }
+
+ impl<'q, DB: sqlx::Database> sqlx::Encode<'q, DB> for $name
+ where
+ String: sqlx::Encode<'q, DB>,
+ {
+ fn encode_by_ref(
+ &self,
+ buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'q>,
+ ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
+ let inner = self.value.to_string();
+ sqlx::Encode::<DB>::encode_by_ref(&inner, buf)
+ }
+ }
+ impl<DB: sqlx::Database> sqlx::Type<DB> for $name
+ where
+ String: sqlx::Type<DB>,
+ {
+ fn type_info() -> DB::TypeInfo {
+ <String as sqlx::Type<DB>>::type_info()
+ }
+ }
+ };
+}
+
+pub(crate) use mk_id;
diff --git a/crates/rocie-server/src/storage/sql/product.rs b/crates/rocie-server/src/storage/sql/product.rs
index c94fcce..2575b59 100644
--- a/crates/rocie-server/src/storage/sql/product.rs
+++ b/crates/rocie-server/src/storage/sql/product.rs
@@ -1,82 +1,32 @@
-use std::{fmt::Display, str::FromStr};
-
use serde::{Deserialize, Serialize};
-use sqlx::{Database, Encode, Type};
use utoipa::ToSchema;
-use uuid::Uuid;
-use crate::storage::sql::barcode::Barcode;
+use crate::storage::sql::{barcode::Barcode, mk_id, unit_property::UnitPropertyId};
+/// The base of rocie.
+///
+/// Products can be bought and consumed and represent, what you actually have in storage.
+/// Not every product is bought, as some can also be obtained by cooking a recipe.
#[derive(Clone, ToSchema, Serialize, Deserialize)]
pub(crate) struct Product {
+ /// The id of the product.
pub(crate) id: ProductId,
- pub(super) name: String,
- pub(super) description: Option<String>,
- pub(super) associated_bar_codes: Vec<Barcode>,
-}
-
-#[derive(
- Deserialize, Serialize, Debug, Default, ToSchema, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
-)]
-pub(crate) struct ProductId {
- value: Uuid,
-}
-
-#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
-#[serde(from = "Uuid")]
-pub(crate) struct ProductIdStub {
- value: Uuid,
-}
-impl ProductId {
- pub(crate) fn from_db(id: &str) -> ProductId {
- Self {
- value: Uuid::from_str(id)
- .expect("We put an uuid into the db, it should also go out again"),
- }
- }
-}
+ /// The property this product is measured in.
+ ///
+ /// (This is probably always either Mass, Volume or Quantity).
+ pub(crate) unit_property: UnitPropertyId,
-impl Display for ProductId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.value)
- }
-}
+ /// The name of the product.
+ ///
+ /// This should be globally unique, to make searching easier for the user.
+ pub(super) name: String,
-impl From<Uuid> for ProductId {
- fn from(value: Uuid) -> Self {
- Self { value }
- }
-}
+ /// An optional description of this product.
+ pub(super) description: Option<String>,
-impl From<Uuid> for ProductIdStub {
- fn from(value: Uuid) -> Self {
- Self { value }
- }
-}
-impl From<ProductIdStub> for ProductId {
- fn from(value: ProductIdStub) -> Self {
- Self { value: value.value }
- }
+ /// Which barcodes are associated with this product.
+ pub(super) associated_bar_codes: Vec<Barcode>,
}
-impl<'q, DB: Database> Encode<'q, DB> for ProductId
-where
- String: Encode<'q, DB>,
-{
- fn encode_by_ref(
- &self,
- buf: &mut <DB as Database>::ArgumentBuffer<'q>,
- ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
- let inner = self.value.to_string();
- Encode::<DB>::encode_by_ref(&inner, buf)
- }
-}
-impl<DB: Database> Type<DB> for ProductId
-where
- String: Type<DB>,
-{
- fn type_info() -> DB::TypeInfo {
- <String as Type<DB>>::type_info()
- }
-}
+mk_id!(ProductId and ProductIdStub);
diff --git a/crates/rocie-server/src/storage/sql/product_amount.rs b/crates/rocie-server/src/storage/sql/product_amount.rs
index 232c5db..0f19afc 100644
--- a/crates/rocie-server/src/storage/sql/product_amount.rs
+++ b/crates/rocie-server/src/storage/sql/product_amount.rs
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
-use crate::storage::sql::{barcode::UnitAmount, product::ProductId};
+use crate::storage::sql::{product::ProductId, unit::UnitAmount};
#[derive(Clone, ToSchema, Serialize, Deserialize)]
pub(crate) struct ProductAmount {
diff --git a/crates/rocie-server/src/storage/sql/unit.rs b/crates/rocie-server/src/storage/sql/unit.rs
index 77e7a2e..d16e783 100644
--- a/crates/rocie-server/src/storage/sql/unit.rs
+++ b/crates/rocie-server/src/storage/sql/unit.rs
@@ -1,80 +1,50 @@
-use std::{fmt::Display, str::FromStr};
-
use serde::{Deserialize, Serialize};
-use sqlx::{Database, Encode, Type};
use utoipa::ToSchema;
-use uuid::Uuid;
+
+use crate::storage::sql::{mk_id, unit_property::UnitPropertyId};
#[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub(crate) struct Unit {
+ /// Unique id for this unit.
pub(crate) id: UnitId,
+
+ /// The singular version of this unit's name.
+ /// E.g.:
+ /// Kilogram
+ /// Gram
pub(crate) full_name_singular: String,
- pub(crate) full_name_plural: String,
- pub(crate) short_name: String,
- pub(crate) description: Option<String>,
-}
-#[derive(
- Deserialize, Serialize, Debug, Default, ToSchema, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
-)]
-pub(crate) struct UnitId {
- value: Uuid,
-}
+ /// The plural version of this unit's name. Is used by a value of two and more.
+ /// (We do not support Slovenian dual numerus versions)
+ /// E.g.:
+ /// Kilogram -> Kilograms
+ /// gram -> meters
+ pub(crate) full_name_plural: String,
-#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
-#[serde(from = "Uuid")]
-pub(crate) struct UnitIdStub {
- value: Uuid,
-}
+ /// Short name or abbreviation of this unit.
+ /// E.g.:
+ /// kg for Kilogram
+ /// g for gram
+ /// m for meter
+ pub(crate) short_name: String,
-impl UnitId {
- pub(crate) fn from_db(id: &str) -> UnitId {
- Self {
- value: Uuid::from_str(id)
- .expect("We put an uuid into the db, it should also go out again"),
- }
- }
-}
+ /// Description of this unit.
+ pub(crate) description: Option<String>,
-impl Display for UnitId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.value)
- }
+ /// Which property is described by this unit.
+ /// E.g.:
+ /// kg -> Mass
+ /// L -> Volume
+ /// m/s -> Speed
+ /// and so forth
+ pub(crate) unit_property: UnitPropertyId,
}
-impl From<Uuid> for UnitId {
- fn from(value: Uuid) -> Self {
- Self { value }
- }
-}
-impl From<Uuid> for UnitIdStub {
- fn from(value: Uuid) -> Self {
- Self { value }
- }
-}
-impl From<UnitIdStub> for UnitId {
- fn from(value: UnitIdStub) -> Self {
- Self { value: value.value }
- }
+#[derive(ToSchema, Debug, Clone, Copy, Serialize, Deserialize)]
+pub(crate) struct UnitAmount {
+ #[schema(minimum = 0)]
+ pub(crate) value: u32,
+ pub(crate) unit: UnitId,
}
-impl<'q, DB: Database> Encode<'q, DB> for UnitId
-where
- String: Encode<'q, DB>,
-{
- fn encode_by_ref(
- &self,
- buf: &mut <DB as Database>::ArgumentBuffer<'q>,
- ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
- let inner = self.value.to_string();
- Encode::<DB>::encode_by_ref(&inner, buf)
- }
-}
-impl<DB: Database> Type<DB> for UnitId
-where
- String: Type<DB>,
-{
- fn type_info() -> DB::TypeInfo {
- <String as Type<DB>>::type_info()
- }
-}
+mk_id!(UnitId and UnitIdStub);
diff --git a/crates/rocie-server/src/storage/sql/unit_property.rs b/crates/rocie-server/src/storage/sql/unit_property.rs
new file mode 100644
index 0000000..9da2d2e
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/unit_property.rs
@@ -0,0 +1,23 @@
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+use crate::storage::sql::{mk_id, unit::UnitId};
+
+/// An unit property describes a property that can be measured by units.
+/// For example velocity, mass or volume.
+#[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
+pub(crate) struct UnitProperty {
+ /// The unique ID for this unit property.
+ pub(crate) id: UnitPropertyId,
+
+ /// The user-displayed name of this property.
+ pub(crate) name: String,
+
+ /// The units with are measuring this property.
+ pub(crate) units: Vec<UnitId>,
+
+ /// An description of this property.
+ pub(crate) description: Option<String>,
+}
+
+mk_id!(UnitPropertyId and UnitPropertyIdStub);