From 29fbe68c73160777a84a295f30418b1eaa60fb45 Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Thu, 8 Jan 2026 09:24:26 +0000 Subject: [PATCH] Add backdating support to egg harvest and sell forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both forms now have datetime pickers like the feed forms, allowing users to record events at past timestamps. Each form has a unique field_id (harvest_datetime, sell_datetime) to avoid conflicts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/animaltrack/web/routes/eggs.py | 28 +++++++++++++++++++++++---- src/animaltrack/web/templates/eggs.py | 5 +++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/animaltrack/web/routes/eggs.py b/src/animaltrack/web/routes/eggs.py index 5c296fd..0c2b538 100644 --- a/src/animaltrack/web/routes/eggs.py +++ b/src/animaltrack/web/routes/eggs.py @@ -26,6 +26,26 @@ from animaltrack.services.products import ProductService, ValidationError from animaltrack.web.templates import render_page from animaltrack.web.templates.eggs import eggs_page + +def _parse_ts_utc(form_value: str | None) -> int: + """Parse ts_utc from form, defaulting to current time if empty or zero. + + Args: + form_value: The ts_utc value from form data. + + Returns: + Timestamp in milliseconds. Returns current time if form_value is + None, empty, or "0". + """ + if not form_value or form_value == "0": + return int(time.time() * 1000) + try: + ts = int(form_value) + return ts if ts > 0 else int(time.time() * 1000) + except (ValueError, TypeError): + return int(time.time() * 1000) + + # APIRouter for multi-file route organization ar = APIRouter() @@ -147,8 +167,8 @@ async def product_collected(request: Request, session): request, locations, products, location_id, "Quantity must be at least 1" ) - # Get current timestamp - ts_utc = int(time.time() * 1000) + # Get timestamp - use provided or current (supports backdating) + ts_utc = _parse_ts_utc(form.get("ts_utc")) # Resolve ducks at location resolved_ids = resolve_ducks_at_location(db, location_id, ts_utc) @@ -276,8 +296,8 @@ async def product_sold(request: Request, session): request, locations, products, product_code, "Total price cannot be negative" ) - # Get current timestamp - ts_utc = int(time.time() * 1000) + # Get timestamp - use provided or current (supports backdating) + ts_utc = _parse_ts_utc(form.get("ts_utc")) # Create product service event_store = EventStore(db) diff --git a/src/animaltrack/web/templates/eggs.py b/src/animaltrack/web/templates/eggs.py index 008b2c5..4d0177c 100644 --- a/src/animaltrack/web/templates/eggs.py +++ b/src/animaltrack/web/templates/eggs.py @@ -16,6 +16,7 @@ from monsterui.all import ( from ulid import ULID from animaltrack.models.reference import Location, Product +from animaltrack.web.templates.actions import event_datetime_field def eggs_page( @@ -163,6 +164,8 @@ def harvest_form( name="notes", placeholder="Optional notes", ), + # Event datetime picker (for backdating) + event_datetime_field("harvest_datetime"), # Hidden nonce for idempotency Hidden(name="nonce", value=str(ULID())), # Submit button @@ -263,6 +266,8 @@ def sell_form( name="notes", placeholder="Optional notes", ), + # Event datetime picker (for backdating) + event_datetime_field("sell_datetime"), # Hidden nonce for idempotency Hidden(name="nonce", value=str(ULID())), # Submit button