Freeze ended weeks into the Supply archive and serve the archive API (Feature 17)
shopping.Freeze(weekStart) archives a week's Supply run once its week has
ended in the household's time zone, setting status archived and frozen_at;
it is idempotent, and a week not yet ended (ErrWeekNotEnded) or with no
list (ErrListNotFound) is an explicit error. A list still untitled is
titled first with archive_title and the same conditional write the
title_supply_run job uses; a failed title is logged and the list is
frozen untitled, keeping its week label.
GET /api/v1/shopping/archive?by=week|month|year returns the Supply runs
up to the household's current week, newest first, grouped by the
Monday's ISO week, month or year, each post with its week label, title or
null, dinner, item and on-hand counts, dinner names and current flag. Any
other by is 400 invalid_request. Frozen lists stay readable through
GET /shopping/{weekStart} and reject item PATCHes with 409 archived.
Maestro task 7ac36526-cb51-4dc7-bbad-6ebbee481942.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,10 @@
|
||||
// (see Generate). The one model call is the Supply run's title, written by
|
||||
// archive_title in a KindTitleSupplyRun job when the list is generated
|
||||
// untitled (see Generate).
|
||||
//
|
||||
// Freeze moves a week's Supply run into the Supply archive once its week
|
||||
// has ended, and Archive reads the archive back as posts grouped by week,
|
||||
// month or year.
|
||||
package shopping
|
||||
|
||||
import (
|
||||
@@ -19,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/skills"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
||||
@@ -70,6 +75,14 @@ var ErrListNotFound = errors.New("the week has no Supply run")
|
||||
// ErrItemNotFound is a change to an item on no Supply run.
|
||||
var ErrItemNotFound = errors.New("no Supply run has that item")
|
||||
|
||||
// ErrWeekNotEnded is a freeze of a Supply run whose week has not ended in
|
||||
// the household's time zone.
|
||||
var ErrWeekNotEnded = errors.New("the week has not ended")
|
||||
|
||||
// ErrInvalidPeriod is an Archive grouping other than ByWeek, ByMonth and
|
||||
// ByYear.
|
||||
var ErrInvalidPeriod = errors.New("by must be week, month or year")
|
||||
|
||||
// Titler runs archive_title on in; scope names the week, its plan revision
|
||||
// and its dinners.
|
||||
// In production it is skills.TitleArchive with the household's
|
||||
@@ -82,6 +95,8 @@ type Service struct {
|
||||
title Titler
|
||||
titleJob *jobs.Kind[TitleJob]
|
||||
logger *slog.Logger
|
||||
// now is the clock weeks are ended and current on; tests replace it.
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewService returns a service over st that titles Supply runs with title
|
||||
@@ -98,7 +113,7 @@ func NewService(st *store.Store, title Titler, runner *jobs.Runner, logger *slog
|
||||
case logger == nil:
|
||||
return nil, errors.New("shopping: nil logger")
|
||||
}
|
||||
s := &Service{st: st, title: title, logger: logger}
|
||||
s := &Service{st: st, title: title, logger: logger, now: time.Now}
|
||||
kind, err := jobs.Register(runner, KindTitleSupplyRun, s.runTitleJob, ReasonTitleFailed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -423,13 +438,18 @@ func (s *Service) Get(ctx context.Context, weekStart store.LocalDate) (l List, e
|
||||
if err != nil {
|
||||
return List{}, fmt.Errorf("shopping: read list: %w", err)
|
||||
}
|
||||
return readList(ctx, q, list)
|
||||
}
|
||||
|
||||
// readList reads the groups and items of list through q.
|
||||
func readList(ctx context.Context, q *db.Queries, list db.ShoppingList) (List, error) {
|
||||
rows, err := q.ListShoppingGroupDinners(ctx, list.ID)
|
||||
if err != nil {
|
||||
return List{}, fmt.Errorf("shopping: read groups: %w", err)
|
||||
return List{}, fmt.Errorf("shopping: read groups of %s: %w", list.WeekStart, err)
|
||||
}
|
||||
items, err := q.ListShoppingItems(ctx, list.ID)
|
||||
if err != nil {
|
||||
return List{}, fmt.Errorf("shopping: read items: %w", err)
|
||||
return List{}, fmt.Errorf("shopping: read items of %s: %w", list.WeekStart, err)
|
||||
}
|
||||
byGroup := make(map[string][]Item, len(rows))
|
||||
for _, r := range items {
|
||||
|
||||
Reference in New Issue
Block a user