Files
MealQuest/migrations/queries/plans.sql
T
dev_agentandClaude Opus 5.5 7e6e49f6b1
CI / ci (push) Successful in 3m20s
CI / ci (pull_request) Successful in 3m6s
Guard plan drafts against swaps and keep confirm on single-flight
Return the draft job id as jobId, as the plan API contract specifies.

Add a week_plans revision (migration 0013) that every swap and stored
draft raises. A draft_week job reads the revision before draft_week runs
and stores its week only while the plan still has it; a plan edited
while the AI ran is left as it is and the job fails plan_changed, so a
draft never erases the household's swap.

A confirmed draft now joins only a confirmed queued or running job, so a
job asked for without confirm can no longer fail it with
confirm_required after the plan was synced.

Test simultaneous drafts, a swap during a draft, a confirmed draft while
an unconfirmed one runs, impossible calendar dates and start_by across
the end of daylight saving time.

Maestro task ea48e650-d510-49cc-8485-0f07174ee731.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 06:32:51 -04:00

86 lines
3.0 KiB
SQL

-- name: GetWeekPlan :one
-- The plan of the week starting week_start. Returns sql.ErrNoRows when
-- there is none.
SELECT * FROM week_plans
WHERE week_start = ?;
-- name: ListPlanDays :many
-- The days of plan plan_id with their dinners, Monday first. A day with
-- no dinner is left out.
SELECT plan_days.date, plan_days.is_new, sqlc.embed(dinners)
FROM plan_days
JOIN dinners ON dinners.id = plan_days.dinner_id
WHERE plan_days.plan_id = ?
ORDER BY plan_days.date;
-- name: CreateWeekPlan :exec
INSERT INTO week_plans (id, week_start, status, ai_note, created_at, updated_at)
VALUES (?, ?, 'draft', ?, ?, ?);
-- name: RedraftWeekPlan :exec
-- Records a new draft over plan id: its status and note, raising its
-- revision.
UPDATE week_plans
SET status = ?, ai_note = ?, revision = revision + 1, updated_at = ?
WHERE id = ?;
-- name: PutPlanDay :exec
-- Plans dinner_id on date of plan_id, keeping the day's calendar event id
-- when the day exists.
INSERT INTO plan_days (id, plan_id, date, dinner_id, is_new, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (plan_id, date) DO UPDATE
SET dinner_id = excluded.dinner_id, is_new = excluded.is_new, updated_at = excluded.updated_at;
-- name: SwapPlanDay :execrows
-- Plans dinner_id on an existing day of plan_id; a date the plan does not
-- have is left as it is (0 rows).
UPDATE plan_days
SET dinner_id = ?, is_new = ?, updated_at = ?
WHERE plan_id = ? AND date = ?;
-- name: MarkWeekPlanEdited :exec
-- Records an edit of plan id, raising its revision: a synced plan becomes
-- changed, any other keeps its status.
UPDATE week_plans
SET status = CASE WHEN status = 'synced' THEN 'changed' ELSE status END,
revision = revision + 1,
updated_at = ?
WHERE id = ?;
-- name: ListDraftPool :many
-- The dinners a draft of the week starting week_start may plan: every
-- Regular, and every Quest log dinner no other plan for the week starting
-- from_week_start or later has on any day.
SELECT * FROM dinners
WHERE status = 'regular'
OR (status = 'quest_log' AND NOT EXISTS (
SELECT 1 FROM plan_days
JOIN week_plans ON week_plans.id = plan_days.plan_id
WHERE plan_days.dinner_id = dinners.id
AND week_plans.week_start >= sqlc.arg(from_week_start)
AND week_plans.week_start <> sqlc.arg(week_start)
))
ORDER BY normalized_name, id;
-- name: ListWeekPlanDinnerIDs :many
-- The ids of the dinners planned in the week starting week_start, Monday
-- first.
SELECT plan_days.dinner_id
FROM plan_days
JOIN week_plans ON week_plans.id = plan_days.plan_id
WHERE week_plans.week_start = ? AND plan_days.dinner_id IS NOT NULL
ORDER BY plan_days.date;
-- name: GetActiveDraftJobID :one
-- The id of the job of kind queued or running for the week starting
-- week_start, one whose payload has confirm when confirmed is 1. Returns
-- sql.ErrNoRows when there is none.
SELECT id FROM jobs
WHERE kind = sqlc.arg(kind)
AND status IN ('queued', 'running')
AND json_extract(payload, '$.week_start') = sqlc.arg(week_start)
AND (CAST(sqlc.arg(confirmed) AS INTEGER) = 0 OR json_extract(payload, '$.confirm') = 1)
ORDER BY id
LIMIT 1;