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.
This commit is contained in:
Jon Seager 2026-02-10 18:58:05 +00:00
parent cb28a6f8bb
commit 9650735057
No known key found for this signature in database
2 changed files with 67 additions and 2 deletions

View file

@ -170,7 +170,7 @@ pub(crate) async fn update_bag(
let (submission, source) = payload.into_parts(); let (submission, source) = payload.into_parts();
let (body_update, image_data_url) = submission.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_id: body_update.roast_id.or(update_params.roast_id),
roast_date: body_update.roast_date.or(update_params.roast_date), roast_date: body_update.roast_date.or(update_params.roast_date),
amount: body_update.amount.or(update_params.amount), amount: body_update.amount.or(update_params.amount),
@ -180,6 +180,15 @@ pub(crate) async fn update_bag(
created_at: body_update.created_at, 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 { let bag = if let Some(true) = update.closed {
state state
.bag_service .bag_service

View file

@ -1,5 +1,6 @@
use crate::helpers::{ 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 brewlog::domain::bags::{Bag, BagWithRoast, NewBag, UpdateBag};
use chrono::NaiveDate; use chrono::NaiveDate;
@ -302,3 +303,58 @@ async fn closing_a_bag_automatically_sets_finished_at() {
chrono::Utc::now().date_naive() 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);
}