diff --git a/src/animaltrack/web/routes/eggs.py b/src/animaltrack/web/routes/eggs.py index 5e42a90..3e060e0 100644 --- a/src/animaltrack/web/routes/eggs.py +++ b/src/animaltrack/web/routes/eggs.py @@ -365,7 +365,14 @@ async def product_collected(request: Request, session): # Validate location_id if not location_id: return _render_harvest_error( - request, db, locations, products, None, "Please select a location" + request, + db, + locations, + products, + None, + "Please select a location", + quantity=quantity_str, + notes=notes, ) # Validate quantity @@ -373,12 +380,26 @@ async def product_collected(request: Request, session): quantity = int(quantity_str) except ValueError: return _render_harvest_error( - request, db, locations, products, location_id, "Quantity must be a number" + request, + db, + locations, + products, + location_id, + "Quantity must be a number", + quantity=quantity_str, + notes=notes, ) if quantity < 0: return _render_harvest_error( - request, db, locations, products, location_id, "Quantity cannot be negative" + request, + db, + locations, + products, + location_id, + "Quantity cannot be negative", + quantity=quantity_str, + notes=notes, ) # Get timestamp - use provided or current (supports backdating) @@ -389,7 +410,14 @@ async def product_collected(request: Request, session): if not resolved_ids: return _render_harvest_error( - request, db, locations, products, location_id, "No ducks at this location" + request, + db, + locations, + products, + location_id, + "No ducks at this location", + quantity=quantity_str, + notes=notes, ) # Create product service @@ -422,7 +450,16 @@ async def product_collected(request: Request, session): route="/actions/product-collected", ) except ValidationError as e: - return _render_harvest_error(request, db, locations, products, location_id, str(e)) + return _render_harvest_error( + request, + db, + locations, + products, + location_id, + str(e), + quantity=quantity_str, + notes=notes, + ) # Save user defaults (only if user exists in database) if UserRepository(db).get(actor): @@ -486,19 +523,48 @@ async def product_sold(request: Request, session): # Validate product_code if not product_code: - return _render_sell_error(request, db, locations, products, None, "Please select a product") + return _render_sell_error( + request, + db, + locations, + products, + None, + "Please select a product", + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, + ) # Validate quantity try: quantity = int(quantity_str) except ValueError: return _render_sell_error( - request, db, locations, products, product_code, "Quantity must be a number" + request, + db, + locations, + products, + product_code, + "Quantity must be a number", + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, ) if quantity < 1: return _render_sell_error( - request, db, locations, products, product_code, "Quantity must be at least 1" + request, + db, + locations, + products, + product_code, + "Quantity must be at least 1", + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, ) # Validate total_price_cents @@ -506,12 +572,30 @@ async def product_sold(request: Request, session): total_price_cents = int(total_price_str) except ValueError: return _render_sell_error( - request, db, locations, products, product_code, "Total price must be a number" + request, + db, + locations, + products, + product_code, + "Total price must be a number", + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, ) if total_price_cents < 0: return _render_sell_error( - request, db, locations, products, product_code, "Total price cannot be negative" + request, + db, + locations, + products, + product_code, + "Total price cannot be negative", + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, ) # Get timestamp - use provided or current (supports backdating) @@ -544,7 +628,18 @@ async def product_sold(request: Request, session): route="/actions/product-sold", ) except ValidationError as e: - return _render_sell_error(request, db, locations, products, product_code, str(e)) + return _render_sell_error( + request, + db, + locations, + products, + product_code, + str(e), + quantity=quantity_str, + total_price_cents=total_price_str, + buyer=buyer, + notes=notes, + ) # Add success toast with link to event add_toast( @@ -573,8 +668,17 @@ async def product_sold(request: Request, session): ) -def _render_harvest_error(request, db, locations, products, selected_location_id, error_message): - """Render harvest form with error message. +def _render_harvest_error( + request, + db, + locations, + products, + selected_location_id, + error_message, + quantity: str | None = None, + notes: str | None = None, +): + """Render harvest form with error message and preserved field values. Args: request: The HTTP request. @@ -583,6 +687,8 @@ def _render_harvest_error(request, db, locations, products, selected_location_id products: List of sellable products. selected_location_id: Currently selected location. error_message: Error message to display. + quantity: Quantity value to preserve. + notes: Notes value to preserve. Returns: HTMLResponse with 422 status. @@ -600,6 +706,8 @@ def _render_harvest_error(request, db, locations, products, selected_location_id harvest_error=error_message, harvest_action=product_collected, sell_action=product_sold, + harvest_quantity=quantity, + harvest_notes=notes, **display_data, ), title="Eggs - AnimalTrack", @@ -610,8 +718,19 @@ def _render_harvest_error(request, db, locations, products, selected_location_id ) -def _render_sell_error(request, db, locations, products, selected_product_code, error_message): - """Render sell form with error message. +def _render_sell_error( + request, + db, + locations, + products, + selected_product_code, + error_message, + quantity: str | None = None, + total_price_cents: str | None = None, + buyer: str | None = None, + notes: str | None = None, +): + """Render sell form with error message and preserved field values. Args: request: The HTTP request. @@ -620,6 +739,10 @@ def _render_sell_error(request, db, locations, products, selected_product_code, products: List of sellable products. selected_product_code: Currently selected product code. error_message: Error message to display. + quantity: Quantity value to preserve. + total_price_cents: Total price value to preserve. + buyer: Buyer value to preserve. + notes: Notes value to preserve. Returns: HTMLResponse with 422 status. @@ -637,6 +760,10 @@ def _render_sell_error(request, db, locations, products, selected_product_code, sell_error=error_message, harvest_action=product_collected, sell_action=product_sold, + sell_quantity=quantity, + sell_total_price_cents=total_price_cents, + sell_buyer=buyer, + sell_notes=notes, **display_data, ), title="Eggs - AnimalTrack", diff --git a/src/animaltrack/web/templates/eggs.py b/src/animaltrack/web/templates/eggs.py index 45e12d8..8fde68c 100644 --- a/src/animaltrack/web/templates/eggs.py +++ b/src/animaltrack/web/templates/eggs.py @@ -37,6 +37,13 @@ def eggs_page( cost_per_egg: float | None = None, sales_stats: dict | None = None, location_names: dict[str, str] | None = None, + # Field value preservation on errors + harvest_quantity: str | None = None, + harvest_notes: str | None = None, + sell_quantity: str | None = None, + sell_total_price_cents: str | None = None, + sell_buyer: str | None = None, + sell_notes: str | None = None, ): """Create the Eggs page with tabbed forms. @@ -56,6 +63,12 @@ def eggs_page( cost_per_egg: 30-day average cost per egg in EUR. sales_stats: Dict with 'total_qty', 'total_cents', and 'avg_price_per_egg_cents'. location_names: Dict mapping location_id to location name for display. + harvest_quantity: Preserved quantity value on error. + harvest_notes: Preserved notes value on error. + sell_quantity: Preserved quantity value on error. + sell_total_price_cents: Preserved total price value on error. + sell_buyer: Preserved buyer value on error. + sell_notes: Preserved notes value on error. Returns: Page content with tabbed forms. @@ -85,6 +98,8 @@ def eggs_page( eggs_per_day=eggs_per_day, cost_per_egg=cost_per_egg, location_names=location_names, + default_quantity=harvest_quantity, + default_notes=harvest_notes, ), cls="uk-active" if harvest_active else None, ), @@ -96,6 +111,10 @@ def eggs_page( action=sell_action, recent_events=sell_events, sales_stats=sales_stats, + default_quantity=sell_quantity, + default_total_price_cents=sell_total_price_cents, + default_buyer=sell_buyer, + default_notes=sell_notes, ), cls=None if harvest_active else "uk-active", ), @@ -113,6 +132,8 @@ def harvest_form( eggs_per_day: float | None = None, cost_per_egg: float | None = None, location_names: dict[str, str] | None = None, + default_quantity: str | None = None, + default_notes: str | None = None, ) -> Div: """Create the Harvest form for egg collection. @@ -125,6 +146,8 @@ def harvest_form( eggs_per_day: 30-day average eggs per day. cost_per_egg: 30-day average cost per egg in EUR. location_names: Dict mapping location_id to location name for display. + default_quantity: Preserved quantity value on error. + default_notes: Preserved notes value on error. Returns: Div containing form and recent events section. @@ -193,6 +216,7 @@ def harvest_form( step="1", placeholder="Number of eggs", required=True, + value=default_quantity or "", ), # Optional notes LabelTextArea( @@ -200,6 +224,7 @@ def harvest_form( id="notes", name="notes", placeholder="Optional notes", + value=default_notes or "", ), # Event datetime picker (for backdating) event_datetime_field("harvest_datetime"), @@ -231,6 +256,10 @@ def sell_form( action: Callable[..., Any] | str = "/actions/product-sold", recent_events: list[tuple[Event, bool]] | None = None, sales_stats: dict | None = None, + default_quantity: str | None = None, + default_total_price_cents: str | None = None, + default_buyer: str | None = None, + default_notes: str | None = None, ) -> Div: """Create the Sell form for recording sales. @@ -241,6 +270,10 @@ def sell_form( action: Route function or URL string for form submission. recent_events: Recent (Event, is_deleted) tuples, most recent first. sales_stats: Dict with 'total_qty' and 'total_cents' for 30-day sales. + default_quantity: Preserved quantity value on error. + default_total_price_cents: Preserved total price value on error. + default_buyer: Preserved buyer value on error. + default_notes: Preserved notes value on error. Returns: Div containing form and recent events section. @@ -315,6 +348,7 @@ def sell_form( step="1", placeholder="Number of items sold", required=True, + value=default_quantity or "", ), # Total price in cents LabelInput( @@ -326,6 +360,7 @@ def sell_form( step="1", placeholder="Total price in cents", required=True, + value=default_total_price_cents or "", ), # Optional buyer LabelInput( @@ -334,6 +369,7 @@ def sell_form( name="buyer", type="text", placeholder="Optional buyer name", + value=default_buyer or "", ), # Optional notes LabelTextArea( @@ -341,6 +377,7 @@ def sell_form( id="sell_notes", name="notes", placeholder="Optional notes", + value=default_notes or "", ), # Event datetime picker (for backdating) event_datetime_field("sell_datetime"),