Season view: filter the editor to a year (#54) (#66)
Build image / build-and-push (push) Successful in 17s
Build image / build-and-push (push) Successful in 17s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #66.
This commit is contained in:
@@ -62,6 +62,68 @@ func queryPlantings(ctx context.Context, q queryer, query string, args ...any) (
|
||||
return plantings, nil
|
||||
}
|
||||
|
||||
// ListPlantingsForGardenYear returns every plop in a garden whose time in the
|
||||
// ground overlaps the given calendar year — the season view (#54).
|
||||
//
|
||||
// The interval is [planted_at, removed_at]: planted before the year ended, and
|
||||
// either still in the ground or removed after the year began. Garlic planted in
|
||||
// October and pulled the following July therefore appears in BOTH years, which
|
||||
// is what actually happened.
|
||||
//
|
||||
// Undated plantings are always included. Every planting that predates this
|
||||
// feature has a null planted_at, and a rule that excluded them would empty every
|
||||
// existing garden the moment a year was selected — an unhelpful way to be
|
||||
// technically correct.
|
||||
func (d *DB) ListPlantingsForGardenYear(ctx context.Context, gardenID int64, year int) ([]domain.Planting, error) {
|
||||
start := fmt.Sprintf("%04d-01-01", year)
|
||||
end := fmt.Sprintf("%04d-12-31", year)
|
||||
return queryPlantings(ctx, d.sql,
|
||||
`SELECT `+qualifyColumns("pl", plantingColumns)+` FROM plantings pl
|
||||
JOIN garden_objects o ON o.id = pl.object_id
|
||||
WHERE o.garden_id = ?
|
||||
AND (pl.planted_at IS NULL OR pl.planted_at <= ?)
|
||||
AND (pl.removed_at IS NULL OR pl.removed_at >= ?)
|
||||
ORDER BY pl.id`,
|
||||
gardenID, end, start)
|
||||
}
|
||||
|
||||
// GardenPlantingYears lists the calendar years a garden has planting data for,
|
||||
// newest first — every year touched by a [planted_at, removed_at] interval.
|
||||
//
|
||||
// Undated plantings contribute no year, deliberately: they belong to every year
|
||||
// the selector offers, so adding one here would be noise.
|
||||
func (d *DB) GardenPlantingYears(ctx context.Context, gardenID int64) ([]int, error) {
|
||||
rows, err := d.sql.QueryContext(ctx,
|
||||
`SELECT DISTINCT CAST(substr(y, 1, 4) AS INTEGER) AS year FROM (
|
||||
SELECT pl.planted_at AS y FROM plantings pl
|
||||
JOIN garden_objects o ON o.id = pl.object_id
|
||||
WHERE o.garden_id = ? AND pl.planted_at IS NOT NULL
|
||||
UNION
|
||||
SELECT pl.removed_at AS y FROM plantings pl
|
||||
JOIN garden_objects o ON o.id = pl.object_id
|
||||
WHERE o.garden_id = ? AND pl.removed_at IS NOT NULL
|
||||
)
|
||||
ORDER BY year DESC`,
|
||||
gardenID, gardenID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: garden planting years: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
years := []int{}
|
||||
for rows.Next() {
|
||||
var y int
|
||||
if err := rows.Scan(&y); err != nil {
|
||||
return nil, fmt.Errorf("store: scan planting year: %w", err)
|
||||
}
|
||||
years = append(years, y)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("store: iterate planting years: %w", err)
|
||||
}
|
||||
return years, nil
|
||||
}
|
||||
|
||||
// ListActivePlantingsForObject returns an object's currently-planted plops
|
||||
// (removed_at IS NULL). Always a non-nil slice. Used by FillRegion to avoid
|
||||
// stacking new plops inside existing ones.
|
||||
|
||||
@@ -24,18 +24,30 @@ func scanPlant(s scanner) (*domain.Plant, error) {
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// ListReferencedPlants returns the distinct plants used by a garden's active
|
||||
// plantings — the catalog subset the editor needs to render them. Always a
|
||||
// non-nil slice (empty when the garden has no active plantings). This is the
|
||||
// /full read side; full plant CRUD/seeding lives in plants.go / plantings.go.
|
||||
func (d *DB) ListReferencedPlants(ctx context.Context, gardenID int64) ([]domain.Plant, error) {
|
||||
// ListReferencedPlants returns the distinct plants used by a garden's plantings
|
||||
// — the catalog subset the editor needs to render them. Always a non-nil slice.
|
||||
//
|
||||
// year nil means the active plops only. A year widens it to the plops that were
|
||||
// in the ground THAT YEAR, which is what a past season needs: a plant pulled
|
||||
// last July is not active, but its plops still have to render with the right
|
||||
// icon and colour. It is scoped to the same year as the plops rather than to the
|
||||
// garden's whole history, so the two selections always agree and a decade-old
|
||||
// garden doesn't load a decade of catalog to draw one season.
|
||||
func (d *DB) ListReferencedPlants(ctx context.Context, gardenID int64, year *int) ([]domain.Plant, error) {
|
||||
where := ` AND pl.removed_at IS NULL`
|
||||
args := []any{gardenID}
|
||||
if year != nil {
|
||||
where = ` AND (pl.planted_at IS NULL OR pl.planted_at <= ?)
|
||||
AND (pl.removed_at IS NULL OR pl.removed_at >= ?)`
|
||||
args = append(args, fmt.Sprintf("%04d-12-31", *year), fmt.Sprintf("%04d-01-01", *year))
|
||||
}
|
||||
rows, err := d.sql.QueryContext(ctx,
|
||||
`SELECT DISTINCT `+qualifyColumns("p", plantColumns)+` FROM plants p
|
||||
JOIN plantings pl ON pl.plant_id = p.id
|
||||
JOIN garden_objects o ON o.id = pl.object_id
|
||||
WHERE o.garden_id = ? AND pl.removed_at IS NULL
|
||||
WHERE o.garden_id = ?`+where+`
|
||||
ORDER BY p.id`,
|
||||
gardenID,
|
||||
args...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: list referenced plants: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user