Supply archive fidelity: frozen posts keep dinner names as frozen (Maestro 22f80eb6) (#201)
CI / ci (push) Successful in 10m7s

Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #201.
This commit is contained in:
2026-10-11 15:57:29 -04:00
committed by approver_agent
parent 9d91686c35
commit f7ebc85862
16 changed files with 234 additions and 38 deletions
+23 -9
View File
@@ -22,7 +22,9 @@ const (
// Freeze moves the Supply run of the week starting weekStart into the
// Supply archive: its status becomes archived and frozen_at is set, after
// which it is read-only (Generate and SetNeeded are ErrFrozen). Freezing a
// which it is read-only (Generate and SetNeeded are ErrFrozen). The list
// keeps the names its dinners have as it is frozen, so its post in the
// Supply archive shows them whatever a dinner is renamed later. Freezing a
// frozen list does nothing and is no error.
//
// The week must have ended in the household's time zone: a week not yet
@@ -79,14 +81,25 @@ func (s *Service) freeze(ctx context.Context, weekStart store.LocalDate, now tim
"week", list.WeekStart, "list", list.ID, "error", err)
}
}
// A list frozen meanwhile is left as it is: the freeze is done.
// A list frozen meanwhile is left as it is, its names as they were
// frozen: the freeze is done.
stamp := store.Now().String()
if _, err := q.FreezeShoppingList(ctx, db.FreezeShoppingListParams{
FrozenAt: sql.NullString{String: stamp, Valid: true}, UpdatedAt: stamp, ID: list.ID,
}); err != nil {
return fmt.Errorf("shopping: freeze %s: %w", weekStart, err)
}
return nil
return s.st.WithTx(ctx, func(tx *sql.Tx) error {
q := db.New(tx)
n, err := q.FreezeShoppingList(ctx, db.FreezeShoppingListParams{
FrozenAt: sql.NullString{String: stamp, Valid: true}, UpdatedAt: stamp, ID: list.ID,
})
if err != nil {
return fmt.Errorf("shopping: freeze %s: %w", weekStart, err)
}
if n == 0 {
return nil
}
if err := q.FreezeShoppingGroupNames(ctx, list.ID); err != nil {
return fmt.Errorf("shopping: freeze %s: keep dinner names: %w", weekStart, err)
}
return nil
})
}
// freezeTitleJob is the title job of list as Generate would enqueue it:
@@ -145,7 +158,8 @@ type Post struct {
Label string
// Title is nil while the list is untitled, when Label stands in.
Title *string
// Dinners are the names of the list's dinners, in day order.
// Dinners are the names of the list's dinners, in day order: as they
// were when the list was frozen, or as they are now while it is not.
Dinners []string
// Items is how many items the list has, and OnHand how many of them
// the household marked "have it".
+1 -1
View File
@@ -422,7 +422,7 @@ func TestArchiveGroups(t *testing.T) {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET title = 'Chili, Noodles & Co' WHERE week_start = '2026-12-28'`); err != nil {
t.Fatal(err)
}
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2027-01-03T11:05:00.000Z' WHERE week_start < '2027-01-04'`); err != nil {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2027-01-03T11:05:00.000Z' WHERE week_start < '2027-01-04'; UPDATE shopping_groups SET frozen_dinner_name = (SELECT name FROM dinners WHERE dinners.id = dinner_id) WHERE list_id IN (SELECT id FROM shopping_lists WHERE frozen_at IS NOT NULL)`); err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -152,7 +152,7 @@ func TestSendToChatFailuresRecordNothing(t *testing.T) {
t.Errorf("after failed sends sent_to_chat_at = %v, chat_send_unconfirmed_at = %v, %v, want both unset", l.SentToChatAt, l.ChatSendUnconfirmedAt, err)
}
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'`); err != nil {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'; UPDATE shopping_groups SET frozen_dinner_name = (SELECT name FROM dinners WHERE dinners.id = dinner_id) WHERE list_id IN (SELECT id FROM shopping_lists WHERE frozen_at IS NOT NULL)`); err != nil {
t.Fatal(err)
}
posts := len(e.chat.Posts())
+11 -2
View File
@@ -492,7 +492,9 @@ func (s *Service) Get(ctx context.Context, weekStart store.LocalDate) (l List, e
return readList(ctx, q, list)
}
// readList reads the groups and items of list through q.
// readList reads the groups and items of list through q. A group of a
// frozen list is named as its dinner was when the list was frozen; one
// with no such name is an error.
func readList(ctx context.Context, q *db.Queries, list db.ShoppingList) (List, error) {
rows, err := q.ListShoppingGroupDinners(ctx, list.ID)
if err != nil {
@@ -512,8 +514,15 @@ func readList(ctx context.Context, q *db.Queries, list db.ShoppingList) (List, e
groups := make([]Group, 0, len(rows))
for _, r := range rows {
g := r.ShoppingGroup
name := r.DinnerName
if frozen(list) {
if !g.FrozenDinnerName.Valid {
return List{}, fmt.Errorf("shopping: group %s of frozen list %s has no frozen dinner name", g.ID, list.WeekStart)
}
name = g.FrozenDinnerName.String
}
groups = append(groups, Group{
ID: g.ID, DinnerID: g.DinnerID, DinnerName: r.DinnerName, Date: g.Date, Position: int(g.Position),
ID: g.ID, DinnerID: g.DinnerID, DinnerName: name, Date: g.Date, Position: int(g.Position),
Items: byGroup[g.ID],
})
}
+2 -2
View File
@@ -692,7 +692,7 @@ func TestGenerateRefusesFrozenList(t *testing.T) {
e.dinner(t, "Pad Thai", ing{"rice noodles", "8", "oz"})
e.plan(t, "Beef Chili")
e.generate(t)
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'`); err != nil {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'; UPDATE shopping_groups SET frozen_dinner_name = (SELECT name FROM dinners WHERE dinners.id = dinner_id) WHERE list_id IN (SELECT id FROM shopping_lists WHERE frozen_at IS NOT NULL)`); err != nil {
t.Fatal(err)
}
e.swap(t, "2026-10-12", "Pad Thai")
@@ -834,7 +834,7 @@ func TestSetNeededRefusesFrozenList(t *testing.T) {
e.dinner(t, "Beef Chili", ing{"ground beef", "1", "lb"})
e.plan(t, "Beef Chili")
l := e.generate(t).List
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'`); err != nil {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'; UPDATE shopping_groups SET frozen_dinner_name = (SELECT name FROM dinners WHERE dinners.id = dinner_id) WHERE list_id IN (SELECT id FROM shopping_lists WHERE frozen_at IS NOT NULL)`); err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -239,7 +239,7 @@ func TestGenerateTitleNotStoredOnFrozenList(t *testing.T) {
e := titleWeek(t)
e.model.QueueGenerate(titled("Noodles, Tacos and Chili"))
e.beforeTitle = func() {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'`); err != nil {
if _, err := e.st.Writer.Exec(`UPDATE shopping_lists SET status = 'archived', frozen_at = '2026-10-19T04:05:00Z'; UPDATE shopping_groups SET frozen_dinner_name = (SELECT name FROM dinners WHERE dinners.id = dinner_id) WHERE list_id IN (SELECT id FROM shopping_lists WHERE frozen_at IS NOT NULL)`); err != nil {
t.Error(err)
}
}