Per D-021, plan.MarkTried(date) moves every Quest log dinner planned on
a household-local date (any plan status) to tried and opens a pending
rotation_reviews row; a dinner already checked for the date is left as
it is, so it is idempotent and never re-marks a dinner returned to the
Quest log.
GET /api/v1/rotation-reviews lists the pending checks as
{dinner {id, name, photo}, plan_date}. POST
/api/v1/dinners/{id}/rotation-review {decision} applies keep (Regular,
origin kept), drop ("Not for us", archived) or not_made ("We didn't
make it", back to the Quest log), closing the check with decided_at and
the session's updated_by; a second decision is 409 no_pending_review.
A dropped dinner's name joins the passed names: ListDeclinedSuggestionNames
and ListRecentDeclinedNames become ListPassedNames and
ListRecentPassedNames, which also read Rotation checks decided drop, so
the Side quest dedupe and household context never suggest it again.
A static test guards that keep is the only path to Regular: every query
writing dinners.status and every use of the regular status is reviewed.
Maestro: a1887d1c-c405-478c-ac7d-8cb8b40d280d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.4 KiB
SQL
61 lines
2.4 KiB
SQL
-- name: ListTriedCandidates :many
|
|
-- The ids of the dinners with status quest_log planned on date, on a plan
|
|
-- of any status, that have no Rotation check for date yet.
|
|
SELECT DISTINCT dinners.id
|
|
FROM plan_days
|
|
JOIN dinners ON dinners.id = plan_days.dinner_id
|
|
WHERE plan_days.date = sqlc.arg(date)
|
|
AND dinners.status = 'quest_log'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM rotation_reviews
|
|
WHERE rotation_reviews.dinner_id = dinners.id
|
|
AND rotation_reviews.plan_date = plan_days.date
|
|
)
|
|
ORDER BY dinners.id;
|
|
|
|
-- name: MarkDinnerTried :execrows
|
|
-- Moves a dinner still in the Quest log to tried; a dinner with any other
|
|
-- status is left as it is (0 rows).
|
|
UPDATE dinners
|
|
SET status = 'tried', updated_at = ?
|
|
WHERE id = ? AND status = 'quest_log';
|
|
|
|
-- name: CreateRotationReview :exec
|
|
INSERT INTO rotation_reviews (id, dinner_id, plan_date, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, 'pending', ?, ?);
|
|
|
|
-- name: ListPendingRotationReviews :many
|
|
-- The pending Rotation checks whose dinner is still tried, oldest plan
|
|
-- date first, with their dinners.
|
|
SELECT rotation_reviews.plan_date, sqlc.embed(dinners)
|
|
FROM rotation_reviews
|
|
JOIN dinners ON dinners.id = rotation_reviews.dinner_id
|
|
WHERE rotation_reviews.status = 'pending' AND dinners.status = 'tried'
|
|
ORDER BY rotation_reviews.plan_date, rotation_reviews.id;
|
|
|
|
-- name: GetPendingRotationReview :one
|
|
-- The pending Rotation check of dinner_id. MarkTried makes one only for a
|
|
-- dinner in the Quest log, which it moves to tried, so a dinner has at
|
|
-- most one. Returns sql.ErrNoRows when there is none.
|
|
SELECT * FROM rotation_reviews
|
|
WHERE dinner_id = ? AND status = 'pending';
|
|
|
|
-- name: DecideRotationReview :execrows
|
|
-- Records the household's decision on a pending Rotation check; one
|
|
-- already decided is left as it is (0 rows).
|
|
UPDATE rotation_reviews
|
|
SET status = 'decided',
|
|
decision = sqlc.arg(decision),
|
|
decided_at = sqlc.arg(now),
|
|
updated_by = sqlc.arg(updated_by),
|
|
updated_at = sqlc.arg(now)
|
|
WHERE id = sqlc.arg(id) AND status = 'pending';
|
|
|
|
-- name: MoveTriedDinner :execrows
|
|
-- Moves a dinner still tried to status (regular, archived or quest_log)
|
|
-- by a Rotation check decision, recording updated_by; its origin is kept.
|
|
-- A dinner no longer tried is left as it is (0 rows).
|
|
UPDATE dinners
|
|
SET status = sqlc.arg(status), updated_by = sqlc.arg(updated_by), updated_at = sqlc.arg(updated_at)
|
|
WHERE id = sqlc.arg(id) AND status = 'tried';
|