Revalidate the draft pool on save, refuse duplicate swaps, refresh the Quest log after a draft
Feature 14 integration fix (Maestro 857e4c33-e250-46ec-b5ea-7dd4f3b719d9). - F14-01: storeDraft re-reads the draft pool (ListDraftPool, the same eligibility rule draft_week was given) inside the save transaction. A day whose dinner was archived, moved to tried, or planned on another current or later week while draft_week ran fails the job with the new retryable reason pool_changed and stores nothing. - F14-02: Swap refuses a dinner already on another day of the week inside the write transaction; the API answers 409 dinner_already_planned. - F14-03: a finished draft invalidates the Quest log query, as the swap path does, so the tablet strip and desktop panel drop dinners it planned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,8 +107,8 @@ func weekStartOrFail(w http.ResponseWriter, r *http.Request, logger *slog.Logger
|
||||
// writePlanError maps a plan.Service error to its API error: a week with
|
||||
// no plan or an unknown dinner is 404, a date outside the week is 400, and
|
||||
// drafting over a synced plan without confirm, a confirmed draft while an
|
||||
// unconfirmed one runs, or planning a dinner that is neither a Regular nor
|
||||
// in the Quest log, is 409.
|
||||
// unconfirmed one runs, planning a dinner that is neither a Regular nor in
|
||||
// the Quest log, or one already on another day of the week, is 409.
|
||||
func writePlanError(w http.ResponseWriter, r *http.Request, logger *slog.Logger, action string, err error) {
|
||||
var day *plan.InvalidDayError
|
||||
var week *plan.InvalidWeekError
|
||||
@@ -120,6 +120,8 @@ func writePlanError(w http.ResponseWriter, r *http.Request, logger *slog.Logger,
|
||||
writeError(w, r, logger, http.StatusBadRequest, "invalid_week", week.Error())
|
||||
case errors.As(err, ¬Plannable):
|
||||
writeError(w, r, logger, http.StatusConflict, "dinner_not_plannable", notPlannable.Error())
|
||||
case errors.Is(err, plan.ErrDinnerAlreadyPlanned):
|
||||
writeError(w, r, logger, http.StatusConflict, "dinner_already_planned", err.Error())
|
||||
case errors.Is(err, plan.ErrConfirmRequired):
|
||||
writeError(w, r, logger, http.StatusConflict, "confirm_required", err.Error())
|
||||
case errors.Is(err, plan.ErrDraftRunning):
|
||||
|
||||
@@ -309,6 +309,7 @@ func TestPutPlanDay(t *testing.T) {
|
||||
{"2026-10-12/days/2026-10-32", `{"dinner_id":"` + e.questLog + `"}`, http.StatusBadRequest, "invalid_date"},
|
||||
{"2026-10-12/days/2026-10-14", `{"dinner_id":"no-such-dinner"}`, http.StatusNotFound, "not_found"},
|
||||
{"2026-10-19/days/2026-10-20", `{"dinner_id":"` + e.questLog + `"}`, http.StatusNotFound, "not_found"},
|
||||
{"2026-10-12/days/2026-10-15", `{"dinner_id":"` + e.questLog + `"}`, http.StatusConflict, "dinner_already_planned"},
|
||||
} {
|
||||
rec, body := callDinners(t, e.h, http.MethodPut, plansPath+tc.target, tc.body)
|
||||
if rec.Code != tc.status || errorCode(body) != tc.code {
|
||||
|
||||
Reference in New Issue
Block a user