Seed provenance + inventory: source links and seed lots (#50) (#64)
Build image / build-and-push (push) Successful in 8s

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #64.
This commit is contained in:
2026-07-21 05:39:24 +00:00
committed by steve
parent 2a8fe24766
commit 78a04672b5
18 changed files with 1511 additions and 31 deletions
+52 -3
View File
@@ -109,6 +109,14 @@ const (
OpCreate = "create"
OpUpdate = "update"
OpDelete = "delete"
// Units a seed lot's quantity can be counted in (mirrors the schema CHECK).
UnitSeeds = "seeds"
UnitGrams = "grams"
UnitOunces = "ounces"
UnitPackets = "packets"
UnitBulbs = "bulbs"
UnitPlants = "plants"
)
// ChangeSet groups the revisions produced by one logical operation — the unit of
@@ -280,6 +288,11 @@ type GardenObject struct {
}
// Plant is a catalog entry. OwnerID nil means a read-only seeded built-in.
//
// SourceURL/Vendor are provenance for the variety itself — the page you'd go
// back to in order to buy it again. What you actually bought, and how much is
// left, lives in SeedLot instead: the same variety may come from two vendors in
// two years, and a single pair of columns here would collapse that.
type Plant struct {
ID int64 `json:"id"`
OwnerID *int64 `json:"ownerId,omitempty"` // nil = built-in
@@ -289,12 +302,44 @@ type Plant struct {
Color string `json:"color"`
Icon string `json:"icon"`
DaysToMaturity *int `json:"daysToMaturity,omitempty"`
SourceURL string `json:"sourceUrl"`
Vendor string `json:"vendor"`
Notes string `json:"notes"`
Version int64 `json:"version"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
// SeedLot is one purchase: a specific packet of a specific variety, from a
// specific vendor, in a specific year. Owned by the buyer even when it points at
// a built-in plant, and never shared along with a garden.
type SeedLot struct {
ID int64 `json:"id"`
OwnerID int64 `json:"ownerId"`
PlantID int64 `json:"plantId"`
Vendor string `json:"vendor"`
SourceURL string `json:"sourceUrl"`
SKU string `json:"sku"`
LotCode string `json:"lotCode"`
PurchasedAt *string `json:"purchasedAt,omitempty"`
PackedForYear *int `json:"packedForYear,omitempty"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
CostCents *int `json:"costCents,omitempty"`
GerminationPct *float64 `json:"germinationPct,omitempty"`
Notes string `json:"notes"`
Version int64 `json:"version"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
// Used is the summed effective count of active plantings attributed to this
// lot, and Remaining is Quantity - Used. Both are computed by the service and
// never stored: a stored running total drifts the moment anything edits a
// planting behind its back, and can't be audited afterwards.
Used float64 `json:"used"`
Remaining float64 `json:"remaining"`
}
// Planting ("plop") is a circular patch of one plant, positioned in its parent
// object's local frame. Count nil means it is derived from area / spacing².
type Planting struct {
@@ -308,9 +353,13 @@ type Planting struct {
Label *string `json:"label,omitempty"`
PlantedAt *string `json:"plantedAt,omitempty"`
RemovedAt *string `json:"removedAt,omitempty"`
Version int64 `json:"version"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
// SeedLotID attributes this planting to a purchase, so a lot can report what
// is left. Nullable: most plantings never name a lot, and retiring a lot sets
// this back to NULL rather than destroying planting history.
SeedLotID *int64 `json:"seedLotId,omitempty"`
Version int64 `json:"version"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
// DerivedCount is the plant count implied by the plop's area and the plant's
// mature spacing: max(1, round(π·r² / spacing²)). It is computed by the