Keep a dropped dinner's name immutable and 404 a never-checked dinner
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:
@@ -0,0 +1,43 @@
|
||||
-- 0014_rotation_review_name: a Rotation check decided drop ("Not for us")
|
||||
-- keeps the name its dinner had when the household dropped it. The dinner
|
||||
-- can be renamed, even once archived, so the passed names every later
|
||||
-- batch refuses are read from here, never from the dinner, as 0012 does
|
||||
-- for a Pass. Only a drop has a name. A check dropped before this takes
|
||||
-- its dinner's name now.
|
||||
-- SQLite cannot add a column with a CHECK that existing drops would fail
|
||||
-- before they are filled in, so the table is rebuilt as 0012 does: copy
|
||||
-- the rows aside, drop the table, create it again, copy the rows back and
|
||||
-- drop the copy. Nothing references rotation_reviews.
|
||||
|
||||
CREATE TABLE rotation_reviews_copy AS SELECT * FROM rotation_reviews;
|
||||
|
||||
DROP TABLE rotation_reviews;
|
||||
|
||||
CREATE TABLE rotation_reviews (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
dinner_id TEXT NOT NULL REFERENCES dinners (id),
|
||||
plan_date TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('pending', 'decided')),
|
||||
decision TEXT CHECK (decision IN ('keep', 'drop', 'not_made')),
|
||||
name TEXT CHECK (name <> ''), -- the dinner's name when dropped
|
||||
decided_at TEXT,
|
||||
updated_by TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE (dinner_id, plan_date),
|
||||
CHECK ((decision IS 'drop') = (name IS NOT NULL))
|
||||
);
|
||||
|
||||
INSERT INTO rotation_reviews (
|
||||
id, dinner_id, plan_date, status, decision, name, decided_at, updated_by, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
rotation_reviews_copy.id, rotation_reviews_copy.dinner_id, rotation_reviews_copy.plan_date,
|
||||
rotation_reviews_copy.status, rotation_reviews_copy.decision,
|
||||
CASE WHEN rotation_reviews_copy.decision = 'drop' THEN dinners.name END,
|
||||
rotation_reviews_copy.decided_at, rotation_reviews_copy.updated_by,
|
||||
rotation_reviews_copy.created_at, rotation_reviews_copy.updated_at
|
||||
FROM rotation_reviews_copy
|
||||
JOIN dinners ON dinners.id = rotation_reviews_copy.dinner_id;
|
||||
|
||||
DROP TABLE rotation_reviews_copy;
|
||||
@@ -8,10 +8,9 @@ SELECT name FROM (
|
||||
FROM suggestions
|
||||
WHERE status = 'declined'
|
||||
UNION ALL
|
||||
SELECT dinners.name, rotation_reviews.decided_at, rotation_reviews.id
|
||||
SELECT name, decided_at, id
|
||||
FROM rotation_reviews
|
||||
JOIN dinners ON dinners.id = rotation_reviews.dinner_id
|
||||
WHERE rotation_reviews.decision = 'drop'
|
||||
WHERE decision = 'drop'
|
||||
)
|
||||
ORDER BY decided_at DESC, id DESC
|
||||
LIMIT ?;
|
||||
|
||||
@@ -40,12 +40,18 @@ ORDER BY rotation_reviews.plan_date, rotation_reviews.id;
|
||||
SELECT * FROM rotation_reviews
|
||||
WHERE dinner_id = ? AND status = 'pending';
|
||||
|
||||
-- name: CountRotationReviews :one
|
||||
-- How many Rotation checks dinner_id has had, pending or decided.
|
||||
SELECT COUNT(*) FROM rotation_reviews WHERE dinner_id = ?;
|
||||
|
||||
-- name: DecideRotationReview :execrows
|
||||
-- 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).
|
||||
UPDATE rotation_reviews
|
||||
SET status = 'decided',
|
||||
decision = sqlc.arg(decision),
|
||||
name = sqlc.narg(name),
|
||||
decided_at = sqlc.arg(now),
|
||||
updated_by = sqlc.arg(updated_by),
|
||||
updated_at = sqlc.arg(now)
|
||||
|
||||
@@ -89,17 +89,16 @@ WHERE suggestions.status = 'pending' AND dinners.status = 'suggested';
|
||||
-- name: ListPassedNames :many
|
||||
-- Every name the household turned down, oldest decision first: the name
|
||||
-- each declined suggestion was suggested under, which renaming its dinner
|
||||
-- does not change, and the name of each dinner a Rotation check dropped
|
||||
-- ("Not for us"), which archiving keeps.
|
||||
-- does not change, and the name each dinner a Rotation check dropped
|
||||
-- ("Not for us") had when dropped, which renaming it does not change.
|
||||
SELECT name FROM (
|
||||
SELECT name, decided_at, id
|
||||
FROM suggestions
|
||||
WHERE status = 'declined'
|
||||
UNION ALL
|
||||
SELECT dinners.name, rotation_reviews.decided_at, rotation_reviews.id
|
||||
SELECT name, decided_at, id
|
||||
FROM rotation_reviews
|
||||
JOIN dinners ON dinners.id = rotation_reviews.dinner_id
|
||||
WHERE rotation_reviews.decision = 'drop'
|
||||
WHERE decision = 'drop'
|
||||
)
|
||||
ORDER BY decided_at, id;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user