|
|
|
|
@ -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<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[debug_handler]
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl ValidateForm for FormWithPathVars<i64, NegativeAdjustmentFormData> {
|
|
|
|
|
type ValidationErrorResponse = NegativeAdjustmentFormTemplate;
|
|
|
|
|
|
|
|
|
|
async fn validate(self, state: &AppState) -> Result<Self, ValidateFormError<Self::ValidationErrorResponse>> {
|
|
|
|
|
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 = <Option<String> as Into<StringField>>::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<SqlitePool>,
|
|
|
|
|
Path(id): Path<i64>,
|
|
|
|
|
user: SessionUser,
|
|
|
|
|
mut form_data: Form<NegativeAdjustmentFormData>,
|
|
|
|
|
mut data: ValidForm<FormWithPathVars<i64,NegativeAdjustmentFormData>>
|
|
|
|
|
) -> Result<Response, AppError> {
|
|
|
|
|
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<i64>) -> Result<Response, AppError> {
|
|
|
|
|
Ok(NegativeAdjustmentFormTemplate {
|
|
|
|
|
item_id: id,
|
|
|
|
|
amount_error: "",
|
|
|
|
|
reason_error: "",
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
.into_response())
|
|
|
|
|
}
|
|
|
|
|
|