feat(3d): meal-box merge, stored mutations, undo, mobile undo UI + i18n ×12

- Merge leftovers into existing meal_box when same recipeId + cookedAt date + frozen + available.
- Store mealBoxMutations JSONB on cooking_sessions for deterministic undo.
- Undo decrements portions/remaining, discards box at zero, appends correction ledger rows.
- Mobile: undo button in cooking/[id].tsx after-flow and meal-boxes.tsx within 24h window.
- i18n undo strings across all 12 locales; parity test green.
- 6 new integration tests: merge, no cross-date merge, frozen split, undo restore, undo discard, ledger invariant.
- Update FAS3 audit doc with 3d semantics.

Closes Fas 3d
This commit is contained in:
Sven (AAMOS AI)
2026-08-07 17:04:42 +07:00
parent c1ab2c8f37
commit 74f95daab2
26 changed files with 610 additions and 305 deletions
+1
View File
@@ -74,6 +74,7 @@ export const mealBoxes = pgTable(
.references(() => storageLocations.id),
frozen: boolean("frozen").notNull().default(false),
recommendedUseBy: date("recommended_use_by").notNull(),
leftoverSource: text("leftover_source"),
reservedForUserId: uuid("reserved_for_user_id").references(() => users.id, {
onDelete: "set null",
}),
+7
View File
@@ -1,5 +1,6 @@
import {
boolean,
date,
doublePrecision,
index,
integer,
@@ -213,6 +214,12 @@ export const cookingSessions = pgTable(
actualPortionsEaten: integer("actual_portions_eaten"),
/** Förberedda rester efter sessionen (steg 3d). */
leftoverEstimatePortions: integer("leftover_estimate_portions"),
/** Måltidsdatum (UTC) som rester/matlådor knyts till. */
mealDate: date("meal_date"),
/** Vilka meal_boxes som påverkades av sessionen och med hur mycket. */
mealBoxMutations: jsonb("meal_box_mutations").$type<
Array<{ mealBoxId: string; deltaPortions: number; frozen: boolean }>
>(),
leftoverNote: text("leftover_note"),
/** Ursprungligt FEFO-förslag, sparas för ev. undo/omräkning. */
plannedDeductions: jsonb("planned_deductions").$type<
+1
View File
@@ -41,6 +41,7 @@ export interface EventPayloadMap {
SHOPPING_COMPLETED: { shoppingListId: string; itemsAdded: number };
WEEK_PLAN_UPDATED: { weekPlanId: string; reason?: string | null };
MEAL_BOX_CREATED: { mealBoxId: string; portions: number };
MEAL_BOX_UPDATED: { mealBoxId: string; addedPortions: number; totalPortions: number };
MEAL_BOX_CONSUMED: { mealBoxId: string; portions: number };
MEAL_BOX_DISCARDED: { mealBoxId: string; portions: number; source: string };
MEAL_REMOVED: { mealId: string; recipeId: string; source: string };
+1
View File
@@ -412,6 +412,7 @@ export const EVENT_TYPES = [
"SHOPPING_COMPLETED",
"WEEK_PLAN_UPDATED",
"MEAL_BOX_CREATED",
"MEAL_BOX_UPDATED",
"MEAL_BOX_CONSUMED",
"MEAL_BOX_DISCARDED",
"MEAL_REMOVED",