3c: partial consumption + undo for cooking sessions
- Append-only undo via correction transactions tied to cookingSessionId. - POST /v1/cooking-sessions/:id/undo with 24h UTC window (409 + i18n after expiry). - Complete undo scope: meals removed, meal boxes discarded, recipe_cooks deleted, cookCount decremented; activation milestones left intact. - Deterministic assumption-profile rollback via lastSessionAnswers.sessionId; profile deleted when observationCount reaches 0. - New status 'undone' as TEXT validated against COOKING_SESSION_STATUSES. - Analytics event cooking_session_undone + domain events MEAL_BOX_DISCARDED, MEAL_REMOVED, COOKING_SESSION_UNDONE. - Partial consumption uses actualPortionsEaten/plannedPortions factor. - i18n coverage for cooked.undoWindowExpired across all 12 locales + server. - Invariant tests: computeBalance(transactions) === item.quantity after complete, undo, and undo + new complete; transaction count increases on undo. - Migration 0014_expand_event_types.sql adds new enum values. - Updated FAS3-COOKING-SESSIONS-AUDIT.md with chosen semantics.
This commit is contained in:
@@ -10,7 +10,7 @@ import { errors, parse } from "../lib/errors.js";
|
||||
import { requireActiveHousehold, requireMembership } from "../lib/helpers.js";
|
||||
import { cookingSessionStarted, cookingSessionCancelled } from "@app/analytics";
|
||||
import { trackProductAnalytics } from "../lib/helpers.js";
|
||||
import { completeCookingSession } from "../lib/cooking.js";
|
||||
import { completeCookingSession, undoCookingSession } from "../lib/cooking.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
@@ -103,7 +103,7 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
const params = z.object({ id: z.uuid() }).parse(req.params);
|
||||
const body = z.object({ reason: z.string().max(200).optional() }).parse(req.body ?? {});
|
||||
const session = await getOwnedSession(app, params.id, req.userId);
|
||||
if (session.status === "completed" || session.status === "cancelled") {
|
||||
if (session.status === "completed" || session.status === "cancelled" || session.status === "undone") {
|
||||
throw errors.conflict("Sessionen är redan avslutad.");
|
||||
}
|
||||
|
||||
@@ -196,6 +196,18 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Ångra en completed session inom 24 h.
|
||||
* Ledger är append-only: reverseringstransaktioner skrivs, befintliga
|
||||
* transaktioner rörs inte.
|
||||
*/
|
||||
app.post("/v1/cooking-sessions/:id/undo", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const session = await getOwnedSession(app, id, req.userId);
|
||||
const result = await undoCookingSession(app, session, req.userId, req.correlationId);
|
||||
return result;
|
||||
});
|
||||
|
||||
/** Lista hushållets aktiva sessioner. */
|
||||
app.get("/v1/cooking-sessions", auth, async (req) => {
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
|
||||
Reference in New Issue
Block a user