From 9e6b17b154998ad940ee20dca4851ad36d6f54d0 Mon Sep 17 00:00:00 2001 From: Wes Holland Date: Fri, 28 Mar 2025 16:06:46 -0500 Subject: [PATCH] Update negative adjustment form --- src/app/item/adjustment/negative.rs | 97 +++++++++++-------- src/db/adjustment/adjustment_reason.rs | 12 +-- src/util/form/field_value.rs | 37 ++++++- .../adjustment/negative-adjustment-form.html | 10 +- .../adjustment/positive-adjustment-form.html | 4 +- templates/item/item-create-form.html | 10 +- 6 files changed, 106 insertions(+), 64 deletions(-) diff --git a/src/app/item/adjustment/negative.rs b/src/app/item/adjustment/negative.rs index fa84064..e8d1def 100644 --- a/src/app/item/adjustment/negative.rs +++ b/src/app/item/adjustment/negative.rs @@ -6,18 +6,33 @@ use crate::session::SessionUser; use askama::Template; use askama_axum::{IntoResponse, Response}; use axum::extract::{Path, State}; -use axum::{debug_handler, Form}; +use axum::{async_trait, debug_handler, Form}; use axum_htmx::{HxEvent, HxResponseTrigger}; use serde::Deserialize; use sqlx::SqlitePool; use tracing::info; +use crate::app::routes::AppState; +use crate::util::form::extractors::{FormWithPathVars, ValidForm}; +use crate::util::form::field_error::FieldError; +use crate::util::form::field_value::{FloatField, StringField}; +use crate::util::form::validate_form::{ValidateForm, ValidateFormError}; -#[derive(Template)] +#[derive(Template, Debug)] #[template(path = "item/adjustment/negative-adjustment-form.html")] pub struct NegativeAdjustmentFormTemplate { pub item_id: i64, - pub amount_error: &'static str, - pub reason_error: &'static str, + pub amount: StringField, + pub reason: StringField, +} + +impl Default for NegativeAdjustmentFormTemplate { + fn default() -> Self { + Self { + item_id: Default::default(), + amount: Default::default(), + reason: StringField::new(DbAdjustmentReason::Sale.into()), + } + } } #[derive(Deserialize, Debug)] @@ -26,50 +41,49 @@ pub struct NegativeAdjustmentFormData { pub reason: Option, } -#[debug_handler] +#[async_trait] +impl ValidateForm for FormWithPathVars { + type ValidationErrorResponse = NegativeAdjustmentFormTemplate; + + async fn validate(self, state: &AppState) -> Result> { + let item_id = self.path_data; + let fractional_units_allowed = does_inventory_item_allow_fractional_units(&state.db, item_id).await?; + + let amount = FloatField::new(self.form_data.amount) + .invalid_if(|v| *v == 0.0 || v.is_nan() || v.is_sign_negative(), FieldError::PositiveNumber) + .invalid_if(|v| !(fractional_units_allowed || v.fract() == 0.0), FieldError::WholeNumber) + .map(|v| if fractional_units_allowed { format!("{:.2}", v) } else { format!("{:.0}", v) }); + + let reason = as Into>::into(self.form_data.reason.clone()) + .invalid_if(|v| v.is_empty(), FieldError::Required) + .invalid_if(|v| DbAdjustmentReason::try_from(v.as_str()).is_err(), FieldError::SelectOption); + + if amount.is_error() || reason.is_error() { + return Err(ValidateFormError::ValidationError(Self::ValidationErrorResponse { + item_id, + amount, + reason, + })); + } + + Ok(self) + } +} + pub async fn negative_adjustment_form_post( State(db): State, Path(id): Path, user: SessionUser, - mut form_data: Form, + mut data: ValidForm> ) -> Result { - let adjustment_amount = if form_data.amount > 0.0 { - -1.0 * form_data.amount - } else { - form_data.amount - }; - - let fractional_units_allowed = does_inventory_item_allow_fractional_units(&db, id).await?; + let adjustment_amount = -1.0 * data.form_data.amount; - let reason = form_data + let reason = data.form_data .reason .take() .and_then(|s| DbAdjustmentReason::try_from(s.as_str()).ok()) .unwrap_or_else(|| DbAdjustmentReason::Unknown); - let amount_error = if adjustment_amount == 0.0 { - "Please input a non-zero amount" - } else if !(fractional_units_allowed || adjustment_amount.fract() == 0.0) { - "Please input a whole number" - } else { - "" - }; - - let reason_error = if reason == DbAdjustmentReason::Unknown { - "Unknown adjustment reason" - } else { - "" - }; - - if !(amount_error.is_empty() && reason_error.is_empty()) { - return Ok(NegativeAdjustmentFormTemplate { - item_id: id, - amount_error, - reason_error, - } - .into_response()); - } - let trigger_events = vec![ HxEvent::from("new-adjustment"), HxEvent::from("form-submit-success"), @@ -79,7 +93,7 @@ pub async fn negative_adjustment_form_post( info!( "Add adjustment form: (Amount {}, Reason {:?}, for user {}", - form_data.amount, reason, user.name + data.form_data.amount, reason, user.name ); let _new_id = add_adjustment( @@ -98,19 +112,18 @@ pub async fn negative_adjustment_form_post( HxResponseTrigger::normal(trigger_events), NegativeAdjustmentFormTemplate { item_id: id, - amount_error: "", - reason_error: "", + ..Default::default() } .into_response(), ) .into_response()) } +#[debug_handler] pub async fn negative_adjustment_form_get(Path(id): Path) -> Result { Ok(NegativeAdjustmentFormTemplate { item_id: id, - amount_error: "", - reason_error: "", + ..Default::default() } .into_response()) } diff --git a/src/db/adjustment/adjustment_reason.rs b/src/db/adjustment/adjustment_reason.rs index e7f4ae6..6aafa53 100644 --- a/src/db/adjustment/adjustment_reason.rs +++ b/src/db/adjustment/adjustment_reason.rs @@ -60,12 +60,12 @@ impl TryFrom<&str> for DbAdjustmentReason { impl Into for DbAdjustmentReason { fn into(self) -> String { match self { - Self::Unknown => { String::from("unknown") } - Self::Sale => { String::from("sale") } - Self::Destruction => { String::from("destruction") } - Self::Expiration => { String::from("expiration") } - Self::Theft => { String::from("theft") } - Self::NewStock => { String::from("new-stock") } + Self::Unknown => { String::from("Unknown") } + Self::Sale => { String::from("Sale") } + Self::Destruction => { String::from("Destruction") } + Self::Expiration => { String::from("Expiration") } + Self::Theft => { String::from("Theft") } + Self::NewStock => { String::from("New-stock") } } } } \ No newline at end of file diff --git a/src/util/form/field_value.rs b/src/util/form/field_value.rs index 1aa0e17..32df469 100644 --- a/src/util/form/field_value.rs +++ b/src/util/form/field_value.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use crate::util::form::field_error::FieldError; /** @@ -20,7 +20,7 @@ pub type BoolField = FieldValue; pub type FloatField = FieldValue; impl FieldValue -where Value: Default +where Value: Default, Error: ToString { pub fn new(value: Value) -> Self { Self { value, error: None } @@ -66,10 +66,22 @@ where Value: Default error: self.error, } } + + pub fn with_error(mut self, error: Error) -> Self { + self.set_error(error); + self + } + + pub fn error_string(&self) -> String { + match self.error { + Some(ref error) => error.to_string(), + None => "Unknown".to_string(), + } + } } impl Default for FieldValue -where Value: Default, +where Value: Default, Error: ToString { fn default() -> Self { Self::new(Value::default()) @@ -85,4 +97,21 @@ where Value: Debug, .field("error", &self.error) .finish() } -} \ No newline at end of file +} + +impl From> for FieldValue +where Value: Default, Error: ToString { + fn from(value: Option) -> Self { + Self::new(value.unwrap_or_default()) + } +} + +impl From> for FieldValue +where Value: Default, Error: ToString { + fn from(value: Result) -> Self { + match value { + Ok(value) => Self::new(value), + Err(error) => Self::default().with_error(error), + } + } +} diff --git a/templates/item/adjustment/negative-adjustment-form.html b/templates/item/adjustment/negative-adjustment-form.html index 4e82574..3235d86 100644 --- a/templates/item/adjustment/negative-adjustment-form.html +++ b/templates/item/adjustment/negative-adjustment-form.html @@ -3,7 +3,7 @@ hx-target="this" hx-swap="outerHTML" x-ref="formNegativeAdjustment" - x-data="{ reason: 'Sale', reason_dropdown_show: false }" + x-data="{ reason: '{{ reason.value }}', reason_dropdown_show: false }" >
@@ -15,15 +15,16 @@ class="block max-w-56 rounded-lg border border-neutral-900 p-2.5 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100" placeholder="Amount" aria-label="amount" + value="{{ amount.value }}" required - {% if !amount_error.is_empty() -%} + {% if !amount.is_error() -%} aria-invalid="true" aria-describedby="invalid-amount" {% endif -%} /> - {% if !amount_error.is_empty() -%} + {% if amount.is_error() -%} - {{ amount_error }} + {{ amount.error_string() }} {% endif -%}
@@ -67,7 +68,6 @@ /> -
- {{ amount.error.unwrap() }} + {{ amount.error_string() }} {% endif -%}
@@ -48,7 +48,7 @@ - {{ price.error.unwrap() }} + {{ price.error_string() }} {% endif -%} diff --git a/templates/item/item-create-form.html b/templates/item/item-create-form.html index 6f574d8..3a39261 100644 --- a/templates/item/item-create-form.html +++ b/templates/item/item-create-form.html @@ -24,7 +24,7 @@ /> {% if name.is_error() -%} - {{ name.error.unwrap() }} + {{ name.error_string() }} {% endif -%} @@ -46,7 +46,7 @@ /> {% if reorder_point.is_error() -%} - {{ reorder_point.error.unwrap() }} + {{ reorder_point.error_string() }} {% endif -%} @@ -70,7 +70,7 @@ {% if display_unit_value.is_error() -%} - {{ display_unit_value.error.unwrap() }} + {{ display_unit_value.error_string() }} {% endif -%} @@ -103,7 +103,7 @@ /> {% if pims_id.is_error() -%} - {{ pims_id.error.unwrap() }} + {{ pims_id.error_string() }} {% endif -%} @@ -124,7 +124,7 @@ /> {% if vetcove_id.is_error() -%} - {{ vetcove_id.error.unwrap() }} + {{ vetcove_id.error_string() }} {% endif -%}