Keep a dropped dinner's name immutable and 404 a never-checked dinner
CI / ci (push) Successful in 3m37s
CI / ci (pull_request) Successful in 3m14s

R-01: a Rotation check decided drop now records the dinner's name at the
decision in rotation_reviews.name (migration 0014, a table rebuild as 0012
did for suggestions; checks dropped before it take their dinner's name).
ListPassedNames and ListRecentPassedNames read that name instead of the
mutable dinners.name, so renaming the archived dinner no longer lets the
dropped dish back into a Side quest batch. A CHECK keeps a name on drops
only.

R-02: deciding on a dinner that has never had a Rotation check is now
ErrReviewNotFound, 404 not_found; a dinner whose checks are all decided,
such as a second decision, stays 409 no_pending_review.

Tests: TestLaterBatchNeverResuggestsRenamedDrop (drop, rename, next batch
still refuses the original name), TestMigrateRotationReviewName (0013 to
0014 upgrade and constraints), TestPostRotationReviewNeverChecked (404),
and TestDecideReview/TestDecideReviewRefused updated.

Maestro: a1887d1c-c405-478c-ac7d-8cb8b40d280d

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 07:32:05 -04:00
co-authored by Claude Opus 5.5
parent f97b4c7b8c
commit cca6a82810
16 changed files with 320 additions and 58 deletions
+24 -7
View File
@@ -10,6 +10,18 @@ import (
"database/sql"
)
const countRotationReviews = `-- name: CountRotationReviews :one
SELECT COUNT(*) FROM rotation_reviews WHERE dinner_id = ?
`
// How many Rotation checks dinner_id has had, pending or decided.
func (q *Queries) CountRotationReviews(ctx context.Context, dinnerID string) (int64, error) {
row := q.db.QueryRowContext(ctx, countRotationReviews, dinnerID)
var count int64
err := row.Scan(&count)
return count, err
}
const createRotationReview = `-- name: CreateRotationReview :exec
INSERT INTO rotation_reviews (id, dinner_id, plan_date, status, created_at, updated_at)
VALUES (?, ?, ?, 'pending', ?, ?)
@@ -38,24 +50,28 @@ const decideRotationReview = `-- name: DecideRotationReview :execrows
UPDATE rotation_reviews
SET status = 'decided',
decision = ?1,
decided_at = ?2,
updated_by = ?3,
updated_at = ?2
WHERE id = ?4 AND status = 'pending'
name = ?2,
decided_at = ?3,
updated_by = ?4,
updated_at = ?3
WHERE id = ?5 AND status = 'pending'
`
type DecideRotationReviewParams struct {
Decision sql.NullString
Name sql.NullString
Now sql.NullString
UpdatedBy sql.NullString
ID string
}
// Records the household's decision on a pending Rotation check; one
// already decided is left as it is (0 rows).
// Records the household's decision on a pending Rotation check, with the
// dinner's name for a drop and NULL otherwise; one already decided is left
// as it is (0 rows).
func (q *Queries) DecideRotationReview(ctx context.Context, arg DecideRotationReviewParams) (int64, error) {
result, err := q.db.ExecContext(ctx, decideRotationReview,
arg.Decision,
arg.Name,
arg.Now,
arg.UpdatedBy,
arg.ID,
@@ -67,7 +83,7 @@ func (q *Queries) DecideRotationReview(ctx context.Context, arg DecideRotationRe
}
const getPendingRotationReview = `-- name: GetPendingRotationReview :one
SELECT id, dinner_id, plan_date, status, decision, decided_at, updated_by, created_at, updated_at FROM rotation_reviews
SELECT id, dinner_id, plan_date, status, decision, name, decided_at, updated_by, created_at, updated_at FROM rotation_reviews
WHERE dinner_id = ? AND status = 'pending'
`
@@ -83,6 +99,7 @@ func (q *Queries) GetPendingRotationReview(ctx context.Context, dinnerID string)
&i.PlanDate,
&i.Status,
&i.Decision,
&i.Name,
&i.DecidedAt,
&i.UpdatedBy,
&i.CreatedAt,