From 965073505709e7fbf1c0ea811a56e2338e189a60 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Tue, 10 Feb 2026 18:58:05 +0000 Subject: [PATCH] fix: wire edit forms for Datastar submission and recompute bag remaining Add {contentType: 'form'} to all edit template @put() calls so form inputs are submitted by name rather than as Datastar signals. Replace the brew edit quick notes text input with toggle pill buttons. Recompute bag remaining when amount is updated to account for brew deductions. Add CLI tests for brew and cup update commands. --- src/application/routes/api/coffee/bags.rs | 11 ++++- tests/server/bags_api.rs | 58 ++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/application/routes/api/coffee/bags.rs b/src/application/routes/api/coffee/bags.rs index 22c704c..e58aed4 100644 --- a/src/application/routes/api/coffee/bags.rs +++ b/src/application/routes/api/coffee/bags.rs @@ -170,7 +170,7 @@ pub(crate) async fn update_bag( let (submission, source) = payload.into_parts(); let (body_update, image_data_url) = submission.into_parts(); - let update = UpdateBag { + let mut update = UpdateBag { roast_id: body_update.roast_id.or(update_params.roast_id), roast_date: body_update.roast_date.or(update_params.roast_date), amount: body_update.amount.or(update_params.amount), @@ -180,6 +180,15 @@ pub(crate) async fn update_bag( created_at: body_update.created_at, }; + // When the bag amount changes, recompute remaining based on how much has been consumed. + if let Some(new_amount) = update.amount + && update.remaining.is_none() + { + let current = state.bag_repo.get(id).await.map_err(AppError::from)?; + let consumed = current.amount - current.remaining; + update.remaining = Some((new_amount - consumed).max(0.0)); + } + let bag = if let Some(true) = update.closed { state .bag_service diff --git a/tests/server/bags_api.rs b/tests/server/bags_api.rs index a661d6f..dd11705 100644 --- a/tests/server/bags_api.rs +++ b/tests/server/bags_api.rs @@ -1,5 +1,6 @@ use crate::helpers::{ - create_default_roast, create_default_roaster, spawn_app, spawn_app_with_auth, + create_default_bag, create_default_gear, create_default_roast, create_default_roaster, + spawn_app, spawn_app_with_auth, }; use brewlog::domain::bags::{Bag, BagWithRoast, NewBag, UpdateBag}; use chrono::NaiveDate; @@ -302,3 +303,58 @@ async fn closing_a_bag_automatically_sets_finished_at() { chrono::Utc::now().date_naive() ); } + +#[tokio::test] +async fn updating_bag_amount_recomputes_remaining() { + // Arrange: create a bag and brew from it to consume some coffee + let app = spawn_app_with_auth().await; + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let bag = create_default_bag(&app, roast.id).await; + let grinder = create_default_gear(&app, "grinder", "Comandante", "C40 MK4").await; + let brewer = create_default_gear(&app, "brewer", "Hario", "V60 02").await; + let client = reqwest::Client::new(); + + // Brew 15g from the 250g bag → remaining should be 235g + let new_brew = brewlog::domain::brews::NewBrew { + bag_id: bag.id, + coffee_weight: 15.0, + grinder_id: grinder.id, + grind_setting: 24.0, + brewer_id: brewer.id, + filter_paper_id: None, + water_volume: 250, + water_temp: 92.0, + quick_notes: Vec::new(), + brew_time: None, + created_at: None, + }; + + client + .post(app.api_url("/brews")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_brew) + .send() + .await + .expect("Failed to create brew"); + + // Act: update the bag amount from 250g to 500g + let update_payload = UpdateBag { + amount: Some(500.0), + ..Default::default() + }; + + let response = client + .put(app.api_url(&format!("/bags/{}", bag.id))) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&update_payload) + .send() + .await + .expect("Failed to execute request"); + + // Assert: remaining should be 500 - 15 = 485 (recomputed from consumed) + assert_eq!(response.status(), 200); + let updated_bag: Bag = response.json().await.expect("Failed to parse response"); + assert_eq!(updated_bag.amount, 500.0); + assert_eq!(updated_bag.remaining, 485.0); +}