From c3d3eecf4df93a3e59da20416618299518292736 Mon Sep 17 00:00:00 2001 From: Wes Holland Date: Fri, 27 Dec 2024 15:25:58 -0600 Subject: [PATCH] Better item stats (more reactive) --- .idea/dataSources.xml | 3 + migrations/20241107225934_initial.sql | 131 ++++++++++++++++++++++++++ src/app/item.rs | 32 ++++++- src/app/mod.rs | 1 + src/db/inventory_item.rs | 53 +++++++++-- src/db/mod.rs | 1 + templates/item-stats-fragment.html | 6 ++ templates/item.html | 36 +++++-- 8 files changed, 247 insertions(+), 16 deletions(-) create mode 100644 templates/item-stats-fragment.html diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 2288b14..76909c1 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -7,6 +7,9 @@ org.sqlite.JDBC jdbc:sqlite:inventory-app.db $ProjectFileDir$ + + + \ No newline at end of file diff --git a/migrations/20241107225934_initial.sql b/migrations/20241107225934_initial.sql index 2c466cb..0aebbfb 100644 --- a/migrations/20241107225934_initial.sql +++ b/migrations/20241107225934_initial.sql @@ -25,6 +25,11 @@ CREATE TABLE IF NOT EXISTS InventoryItem ( display_unit INTEGER NOT NULL DEFAULT 1, reorder_point REAL NOT NULL, allow_fractional_units BOOLEAN NOT NULL, + active BOOLEAN NOT NULL, + pims_id TEXT, + vetcove_id TEXT, + manufacturer_name TEXT, + manufacturer_id TEXT, FOREIGN KEY(display_unit) REFERENCES DisplayUnit(id) ); @@ -65,3 +70,129 @@ INSERT INTO DisplayUnit (id, unit, abbreviation) VALUES (1,'count', 'ct'), (2,'milliliter', 'ml'), (3,'milligram', 'mg'); + +CREATE TABLE IF NOT EXISTS SkuCovetrus +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuMWI +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuPatterson +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuMidwest +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuFirstVet +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuPennVet +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuAmatheon +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuVictor +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuVetcove +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuMillerVet +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuBoehringerIngelheim +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuMillerVet +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuZoetis +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuPharmasourceAH +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuNEAnimalHealth +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuDechra +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuMedline +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); + +CREATE TABLE IF NOT EXISTS SkuElanco +( + sku TEXT NOT NULL UNIQUE, + item INTEGER, + FOREIGN KEY(item) REFERENCES InventoryItem(id) +); diff --git a/src/app/item.rs b/src/app/item.rs index f5bdcfe..e91ad8b 100644 --- a/src/app/item.rs +++ b/src/app/item.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use askama::Template; use askama_axum::IntoResponse; use axum::extract::{Path, State}; @@ -5,7 +6,7 @@ use sqlx::SqlitePool; use axum::response::Response; use crate::app::adjustment::AdjustmentDisplayItem; use crate::db::adjustment::{get_item_adjustment_valuation_weighted_mean, sum_all_adjustments_for_item, DbAdjustmentWithValuation}; -use crate::db::inventory_item::{inventory_item_get_by_id_with_unit, DbInventoryItemWithCount}; +use crate::db::inventory_item::{inventory_item_get_by_id_with_unit, inventory_item_get_unit_abbreviation, DbInventoryItemWithCount}; use crate::error::AppError; use crate::session::SessionUser; use crate::util::currency::int_cents_to_dollars_string; @@ -23,6 +24,7 @@ struct ItemDisplayItem { pub name: String, pub reorder_point: f64, pub allow_fractional_units: bool, + pub active: bool, pub display_unit: String, pub display_unit_short: String, pub amount: String, @@ -38,6 +40,7 @@ impl From for ItemDisplayItem { allow_fractional_units: item.allow_fractional_units, display_unit: item.display_unit_str, display_unit_short: item.display_unit_abbreviation, + active: item.active, amount, } } @@ -58,3 +61,30 @@ pub async fn item_count(State(db): State, Path(id): Path) -> Re Ok(count.to_string().into_response()) } + + +#[derive(Template)] +#[template(path = "item-stats-fragment.html")] +struct ItemStatsTemplate { + pub item_id: i64, + pub amount: String, + pub unit_abbreviation: String, + pub value: String, +} +pub async fn item_stats(State(db): State, Path(id): Path) -> Result { + //TODO This is pretty chatty with the database. Might could cut down the + // number of queries + let amount = sum_all_adjustments_for_item(&db, id).await?.to_string(); + + let unit_abbreviation = inventory_item_get_unit_abbreviation(&db, id).await?; + + let value = get_item_adjustment_valuation_weighted_mean(&db, id) + .await? + .into_iter() + .last().ok_or_else(|| anyhow!("No value"))? + .value; + + let value = int_cents_to_dollars_string(value); + + Ok(ItemStatsTemplate { item_id: id, amount, unit_abbreviation, value }.into_response()) +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 78881d5..a280e31 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -29,6 +29,7 @@ pub fn routes() -> Router { .route("/item/:item_id", get(item::item)) .route("/item/:item_id/", get(item::item)) .route("/item/:item_id/count", get(item::item_count)) + .route("/item/:item_id/stats", get(item::item_stats)) .route("/item/:item_id/adjustments", get(adjustment::get_adjustments_for_item)) .route("/item/:item_id/adjustment/sale", post(adjustment::adjustment_sale_form_post)) .route("/item/:item_id/adjustment/sale", get(adjustment::adjustment_sale_form_get)) diff --git a/src/db/inventory_item.rs b/src/db/inventory_item.rs index 4c5a081..5711d7c 100644 --- a/src/db/inventory_item.rs +++ b/src/db/inventory_item.rs @@ -10,6 +10,11 @@ pub struct DbInventoryItem { pub reorder_point: f64, pub allow_fractional_units: bool, pub display_unit: i64, + pub active: bool, + pub pims_id: Option, + pub vetcove_id: Option, + pub manufacturer_name: Option, + pub manufacturer_id: Option, } @@ -23,7 +28,12 @@ pub async fn inventory_item_get_all(db: &SqlitePool, page_size: i64, page_num: i name, reorder_point, allow_fractional_units, - display_unit + display_unit, + active, + pims_id, + vetcove_id, + manufacturer_name, + manufacturer_id FROM InventoryItem LIMIT ? OFFSET ? @@ -49,7 +59,12 @@ pub async fn inventory_item_get_search(db: &SqlitePool, name, reorder_point, allow_fractional_units, - display_unit + display_unit, + active, + pims_id, + vetcove_id, + manufacturer_name, + manufacturer_id FROM InventoryItem WHERE InventoryItem.name LIKE ? @@ -71,7 +86,12 @@ pub async fn inventory_item_get_by_id(db: &SqlitePool, id: i64) -> Result Result { let res = sqlx::query!( r#" - INSERT INTO InventoryItem (name, reorder_point, allow_fractional_units, display_unit) - VALUES (?, ?, ?, (SELECT id from DisplayUnit WHERE abbreviation = ? )) + INSERT INTO InventoryItem (name, reorder_point, allow_fractional_units, display_unit, active) + VALUES (?, ?, ?, (SELECT id from DisplayUnit WHERE abbreviation = ? ), ?) "#, - name, reorder_point, allow_fractional_units, display_unit_abbreviation + name, reorder_point, allow_fractional_units, display_unit_abbreviation, true ).execute(db).await?; let new_id = res.last_insert_rowid(); @@ -109,6 +129,7 @@ pub struct DbInventoryItemWithCount { pub display_unit_str: String, pub display_unit_abbreviation: String, pub amount: Option, + pub active: bool, } pub async fn inventory_item_get_by_id_with_unit(db: &SqlitePool, id: i64) -> Result { @@ -123,7 +144,8 @@ pub async fn inventory_item_get_by_id_with_unit(db: &SqlitePool, id: i64) -> Res item.display_unit as display_unit, display_unit.unit as display_unit_str, display_unit.abbreviation as display_unit_abbreviation, - (SELECT TOTAL(amount) as amt FROM Adjustment WHERE item = ?) as amount + (SELECT TOTAL(amount) as amt FROM Adjustment WHERE item = ?) as amount, + item.active as active FROM InventoryItem as item JOIN DisplayUnit as display_unit ON item.display_unit = display_unit.id @@ -150,3 +172,20 @@ pub async fn does_inventory_item_allow_fractional_units(db: &SqlitePool, id: i64 Ok(res.allow_fractional_units) } + +pub async fn inventory_item_get_unit_abbreviation(db: &SqlitePool, id: i64) -> Result { + let res = sqlx::query!( + r#" + SELECT + unit.abbreviation + FROM InventoryItem as item + JOIN DisplayUnit as unit + ON item.display_unit = unit.id + WHERE item.id = ? + "#, + id + ) + .fetch_one(db).await?; + + Ok(res.abbreviation) +} diff --git a/src/db/mod.rs b/src/db/mod.rs index dd63085..3a54222 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -25,6 +25,7 @@ pub async fn init() -> anyhow::Result { pub async fn connect_db(url: &str) -> anyhow::Result { let options = SqliteConnectOptions::from_str(url)? + .foreign_keys(true) .create_if_missing(true); let exists = options.get_filename().exists(); diff --git a/templates/item-stats-fragment.html b/templates/item-stats-fragment.html new file mode 100644 index 0000000..f11f778 --- /dev/null +++ b/templates/item-stats-fragment.html @@ -0,0 +1,6 @@ + +
+
Amount in stock: {{amount}} {{unit_abbreviation}}
+
Total Value: {{value}}
+
+ diff --git a/templates/item.html b/templates/item.html index f6b46f2..18ef5fa 100644 --- a/templates/item.html +++ b/templates/item.html @@ -4,17 +4,37 @@ {% block content %} -

{{item.name}}

-

Reorder at: {{item.reorder_point}}

-

Amount in stock: {{item.amount}} {{item.display_unit_short}}

+

{{item.name}} {% if !item.active %} Inactive {% endif %}

+
+
+
-
-
+
+
Reorder at: {{item.reorder_point}}
+
+
-
-
+{% if item.active %} +
+
+
+

Sale

+
+
+
-
+
+

New Stock

+
+
+
+
+{% endif %} + +
+
+
+
{% endblock %} \ No newline at end of file