Plantings backend: plop CRUD, derived counts, removed_at #14

Closed
opened 2026-07-18 18:18:21 +00:00 by steve · 0 comments
Owner

Part of epic #20 · Design: DESIGN.md

Context

A planting ("plop") is a circular patch of one plant inside a plantable object: center x/y in the object's local frame (so moving/rotating the bed carries its plants), a radius, and either an explicit plant count or one derived from area ÷ spacing². removed_at soft-removes a plop — that's how "clear bed" and future season history work.

Scope

  • internal/store/plantings.go + internal/service/plantings.go (conventions from #7; mutations require editor role on the object's garden).
  • POST /api/v1/objects/:id/plantings: object must be plantable; fields plant_id (must be visible to the actor: built-in or own), x_cm, y_cm (local frame), radius_cm > 0, optional count override, optional label, planted_at (default: today). Loose bounds check: center inside the object's unrotated bounds (radius may overhang).
  • PATCH /api/v1/plantings/:id (partial + version): move, resize radius, change plant/count/label/dates, set/unset removed_at. DELETE /api/v1/plantings/:id: hard delete (for mistakes; "harvested/removed" flows should set removed_at).
  • Derived count: when count is NULL, responses include derived_count = max(1, round(π·r² / spacing_cm²)) computed from the plant's spacing — one helper in internal/service, unit-tested (this exact formula also feeds #15's display and #19's FillRegion).
  • /full (#10) now includes active plantings (removed_at IS NULL) + the referenced plants; verify the wiring.
  • Service tests: place/move/resize, derived vs override count, non-plantable object rejection, foreign plant rejection, removed_at filtering, version conflict.

Out of scope

  • Frontend (#15). Bulk region fills (#19). Overlap prevention between plops (allowed by design — interplanting is real).

Key files & patterns

  • Table + formula: DESIGN.md § Domain model (plantings); local-frame rules § Geometry.

Dependencies

Blocked by #10 and #12.

Acceptance criteria

  • curl: place a garlic plop in a bed corner (local coords), grow its radius — derived_count rises with r²; set count=12 — override wins; set removed_at — it leaves /full.
  • Planting into a tree/path → 400/422 with a clear message; planting user B's custom plant as user A → rejected.
  • go test ./internal/service/... green including the count formula's edge cases (tiny radius → 1).
Part of epic #20 · Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md) ## Context A planting ("plop") is a circular patch of one plant inside a plantable object: center x/y **in the object's local frame** (so moving/rotating the bed carries its plants), a radius, and either an explicit plant count or one derived from area ÷ spacing². `removed_at` soft-removes a plop — that's how "clear bed" and future season history work. ## Scope - [ ] `internal/store/plantings.go` + `internal/service/plantings.go` (conventions from #7; mutations require editor role on the **object's garden**). - [ ] `POST /api/v1/objects/:id/plantings`: object must be `plantable`; fields plant_id (must be visible to the actor: built-in or own), x_cm, y_cm (local frame), radius_cm > 0, optional count override, optional label, `planted_at` (default: today). Loose bounds check: center inside the object's unrotated bounds (radius may overhang). - [ ] `PATCH /api/v1/plantings/:id` (partial + `version`): move, resize radius, change plant/count/label/dates, set/unset `removed_at`. `DELETE /api/v1/plantings/:id`: hard delete (for mistakes; "harvested/removed" flows should set `removed_at`). - [ ] Derived count: when `count` is NULL, responses include `derived_count = max(1, round(π·r² / spacing_cm²))` computed from the plant's spacing — one helper in `internal/service`, unit-tested (this exact formula also feeds #15's display and #19's FillRegion). - [ ] `/full` (#10) now includes active plantings (`removed_at IS NULL`) + the referenced plants; verify the wiring. - [ ] Service tests: place/move/resize, derived vs override count, non-plantable object rejection, foreign plant rejection, removed_at filtering, version conflict. ## Out of scope - Frontend (#15). Bulk region fills (#19). Overlap prevention between plops (allowed by design — interplanting is real). ## Key files & patterns - Table + formula: `DESIGN.md` § Domain model (`plantings`); local-frame rules § Geometry. ## Dependencies Blocked by #10 and #12. ## Acceptance criteria - curl: place a garlic plop in a bed corner (local coords), grow its radius — `derived_count` rises with r²; set `count=12` — override wins; set `removed_at` — it leaves `/full`. - Planting into a tree/path → 400/422 with a clear message; planting user B's custom plant as user A → rejected. - `go test ./internal/service/...` green including the count formula's edge cases (tiny radius → 1).
steve added the backenddrawing labels 2026-07-18 18:18:21 +00:00
steve closed this issue 2026-07-19 02:41:23 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#14