Allow recording zero eggs collected
All checks were successful
Deploy / deploy (push) Successful in 1m37s

Enable recording "checked coop, found 0 eggs" to distinguish from days
when the coop wasn't checked at all. Statistics remain eggs/calendar day.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 06:37:31 +00:00
parent d91ee362fa
commit eee8552345
5 changed files with 34 additions and 12 deletions

View File

@@ -308,7 +308,7 @@ class ProductCollectedPayload(BaseModel):
location_id: str = Field(..., min_length=26, max_length=26) location_id: str = Field(..., min_length=26, max_length=26)
product_code: str product_code: str
quantity: int = Field(..., ge=1) quantity: int = Field(..., ge=0) # 0 allowed: checked but found none
resolved_ids: list[str] = Field(..., min_length=1) resolved_ids: list[str] = Field(..., min_length=1)
notes: str | None = None notes: str | None = None

View File

@@ -376,9 +376,9 @@ async def product_collected(request: Request, session):
request, db, locations, products, location_id, "Quantity must be a number" request, db, locations, products, location_id, "Quantity must be a number"
) )
if quantity < 1: if quantity < 0:
return _render_harvest_error( return _render_harvest_error(
request, db, locations, products, location_id, "Quantity must be at least 1" request, db, locations, products, location_id, "Quantity cannot be negative"
) )
# Get timestamp - use provided or current (supports backdating) # Get timestamp - use provided or current (supports backdating)

View File

@@ -184,13 +184,13 @@ def harvest_form(
id="location_id", id="location_id",
name="location_id", name="location_id",
), ),
# Quantity input (integer only, min=1) # Quantity input (integer only, 0 allowed for "checked but found none")
LabelInput( LabelInput(
"Quantity", "Quantity",
id="quantity", id="quantity",
name="quantity", name="quantity",
type="number", type="number",
min="1", min="0",
step="1", step="1",
placeholder="Number of eggs", placeholder="Number of eggs",
required=True, required=True,

View File

@@ -285,15 +285,27 @@ class TestProductPayloads:
) )
assert payload.quantity == 12 assert payload.quantity == 12
def test_quantity_must_be_positive(self): def test_quantity_zero_is_valid(self):
"""quantity must be >= 1.""" """quantity=0 is valid (checked but found none)."""
from animaltrack.events.payloads import ProductCollectedPayload
payload = ProductCollectedPayload(
location_id="01ARZ3NDEKTSV4RRFFQ69G5FAV",
product_code="egg.duck",
quantity=0,
resolved_ids=["01ARZ3NDEKTSV4RRFFQ69G5FAV"],
)
assert payload.quantity == 0
def test_quantity_cannot_be_negative(self):
"""quantity must be >= 0."""
from animaltrack.events.payloads import ProductCollectedPayload from animaltrack.events.payloads import ProductCollectedPayload
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
ProductCollectedPayload( ProductCollectedPayload(
location_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", location_id="01ARZ3NDEKTSV4RRFFQ69G5FAV",
product_code="egg.duck", product_code="egg.duck",
quantity=0, quantity=-1,
resolved_ids=["01ARZ3NDEKTSV4RRFFQ69G5FAV"], resolved_ids=["01ARZ3NDEKTSV4RRFFQ69G5FAV"],
) )

View File

@@ -137,10 +137,10 @@ class TestEggCollection:
assert event_row is not None assert event_row is not None
assert event_row[0] == "ProductCollected" assert event_row[0] == "ProductCollected"
def test_egg_collection_validation_quantity_zero( def test_egg_collection_quantity_zero_accepted(
self, client, location_strip1_id, ducks_at_strip1 self, client, seeded_db, location_strip1_id, ducks_at_strip1
): ):
"""quantity=0 returns 422.""" """quantity=0 is accepted (checked coop, found no eggs)."""
resp = client.post( resp = client.post(
"/actions/product-collected", "/actions/product-collected",
data={ data={
@@ -150,7 +150,17 @@ class TestEggCollection:
}, },
) )
assert resp.status_code == 422 assert resp.status_code in [200, 302, 303]
# Verify event was created with quantity=0
event_row = seeded_db.execute(
"SELECT payload FROM events WHERE type = 'ProductCollected' ORDER BY id DESC LIMIT 1"
).fetchone()
assert event_row is not None
import json
payload = json.loads(event_row[0])
assert payload["quantity"] == 0
def test_egg_collection_validation_quantity_negative( def test_egg_collection_validation_quantity_negative(
self, client, location_strip1_id, ducks_at_strip1 self, client, location_strip1_id, ducks_at_strip1