diff --git a/CONTEXT.md b/CONTEXT.md index 6fcae81..f482ba4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -30,6 +30,9 @@ _Avoid_: Line item (implies a billing-only concept; this also carries preparatio A bearer credential issued to a guest customer (one without an account) at Order placement time. Since a guest has no session, this token is the only proof of ownership accepted on later calls against that Order (status lookup, cancellation, pickup). _Avoid_: Session (guests never get a session; this is a different, bearer-credential mechanism) +**Recipe**: +The ordered list of Preparation Steps required to prepare a Menu Item. A Menu Item's Recipe can be edited later by an operator; an Order Item freezes a copy of the Recipe at the moment it's ordered, so a later Recipe edit never reshapes a Step list a customer already paid for. + **Preparation Step**: One stage of preparing an Order Item, requiring a Machine carrying a specific Machine Tag, with an estimated duration. An Order Item consists of one or more Steps performed in sequence — the next Step cannot begin until the previous one finishes. The customer can see their Order Item's full Step list, current queue position, and approximate time to completion. _Avoid_: Stage (use Step consistently) diff --git a/docs/adr/0005-recipe-snapshot-per-order-item.md b/docs/adr/0005-recipe-snapshot-per-order-item.md new file mode 100644 index 0000000..5614e02 --- /dev/null +++ b/docs/adr/0005-recipe-snapshot-per-order-item.md @@ -0,0 +1,3 @@ +# Recipe snapshot per Order Item + +An Order Item's full Recipe (every Preparation Step, in order) is copied from the Menu Item's live Recipe into a per-Order-Item `orderItemSteps` table at order-placement time, rather than each Step being looked up from the live Recipe as the Order Item progresses. We chose this because price is already snapshotted per Order Item for the same reason ("later menu changes don't rewrite history") — an operator editing a Menu Item's Recipe (reordering Steps, changing a Step's Machine Tag or duration) should never reshape an Order Item a customer already paid for and is partway through cooking. The live `menuItemSteps` table remains the source of truth for menu browsing and for new Orders; only `orderItemSteps` is read once an Order Item exists. diff --git a/drizzle/0002_large_lake.sql b/drizzle/0002_large_lake.sql new file mode 100644 index 0000000..8e380bb --- /dev/null +++ b/drizzle/0002_large_lake.sql @@ -0,0 +1,10 @@ +CREATE TABLE "order_item_steps" ( + "id" serial PRIMARY KEY NOT NULL, + "order_item_id" integer NOT NULL, + "sequence" integer NOT NULL, + "machine_tag_id" integer NOT NULL, + "estimated_duration_seconds" integer NOT NULL +); +--> statement-breakpoint +ALTER TABLE "order_item_steps" ADD CONSTRAINT "order_item_steps_order_item_id_order_items_id_fk" FOREIGN KEY ("order_item_id") REFERENCES "public"."order_items"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "order_item_steps" ADD CONSTRAINT "order_item_steps_machine_tag_id_machine_tags_id_fk" FOREIGN KEY ("machine_tag_id") REFERENCES "public"."machine_tags"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..cceb306 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,590 @@ +{ + "id": "aecfb37f-fb88-44ba-8e02-f290a3929293", + "prevId": "816e0a1c-3005-433c-911b-2e006cc9e91b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.brands": { + "name": "brands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine_tags": { + "name": "machine_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "machine_tags_name_idx": { + "name": "machine_tags_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machines": { + "name": "machines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "machine_tag_id": { + "name": "machine_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "machines_machine_tag_id_machine_tags_id_fk": { + "name": "machines_machine_tag_id_machine_tags_id_fk", + "tableFrom": "machines", + "tableTo": "machine_tags", + "columnsFrom": [ + "machine_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.menu_item_steps": { + "name": "menu_item_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "menu_item_id": { + "name": "menu_item_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "machine_tag_id": { + "name": "machine_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "estimated_duration_seconds": { + "name": "estimated_duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "menu_item_steps_menu_item_id_menu_items_id_fk": { + "name": "menu_item_steps_menu_item_id_menu_items_id_fk", + "tableFrom": "menu_item_steps", + "tableTo": "menu_items", + "columnsFrom": [ + "menu_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "menu_item_steps_machine_tag_id_machine_tags_id_fk": { + "name": "menu_item_steps_machine_tag_id_machine_tags_id_fk", + "tableFrom": "menu_item_steps", + "tableTo": "machine_tags", + "columnsFrom": [ + "machine_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.menu_items": { + "name": "menu_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "brand_id": { + "name": "brand_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "menu_items_brand_id_brands_id_fk": { + "name": "menu_items_brand_id_brands_id_fk", + "tableFrom": "menu_items", + "tableTo": "brands", + "columnsFrom": [ + "brand_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.order_item_steps": { + "name": "order_item_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order_item_id": { + "name": "order_item_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "machine_tag_id": { + "name": "machine_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "estimated_duration_seconds": { + "name": "estimated_duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "order_item_steps_order_item_id_order_items_id_fk": { + "name": "order_item_steps_order_item_id_order_items_id_fk", + "tableFrom": "order_item_steps", + "tableTo": "order_items", + "columnsFrom": [ + "order_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "order_item_steps_machine_tag_id_machine_tags_id_fk": { + "name": "order_item_steps_machine_tag_id_machine_tags_id_fk", + "tableFrom": "order_item_steps", + "tableTo": "machine_tags", + "columnsFrom": [ + "machine_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.order_items": { + "name": "order_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "menu_item_id": { + "name": "menu_item_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'placed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "order_items_order_id_orders_id_fk": { + "name": "order_items_order_id_orders_id_fk", + "tableFrom": "order_items", + "tableTo": "orders", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "order_items_menu_item_id_menu_items_id_fk": { + "name": "order_items_menu_item_id_menu_items_id_fk", + "tableFrom": "order_items", + "tableTo": "menu_items", + "columnsFrom": [ + "menu_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.orders": { + "name": "orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "guest_name": { + "name": "guest_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "guest_contact": { + "name": "guest_contact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "guest_access_token": { + "name": "guest_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'placed'" + }, + "payment_charge_id": { + "name": "payment_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preparation_step_instances": { + "name": "preparation_step_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order_item_id": { + "name": "order_item_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "machine_tag_id": { + "name": "machine_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "estimated_duration_seconds": { + "name": "estimated_duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "machine_id": { + "name": "machine_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "done_at": { + "name": "done_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preparation_step_instances_order_item_id_order_items_id_fk": { + "name": "preparation_step_instances_order_item_id_order_items_id_fk", + "tableFrom": "preparation_step_instances", + "tableTo": "order_items", + "columnsFrom": [ + "order_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "preparation_step_instances_machine_tag_id_machine_tags_id_fk": { + "name": "preparation_step_instances_machine_tag_id_machine_tags_id_fk", + "tableFrom": "preparation_step_instances", + "tableTo": "machine_tags", + "columnsFrom": [ + "machine_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "preparation_step_instances_machine_id_machines_id_fk": { + "name": "preparation_step_instances_machine_id_machines_id_fk", + "tableFrom": "preparation_step_instances", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e5e0264..6145a92 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1788972318759, "tag": "0001_graceful_frank_castle", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1788976393301, + "tag": "0002_large_lake", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index 1d8570a..dbc62f0 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -102,6 +102,22 @@ export const orderItems = pgTable('order_items', { createdAt: timestamp('created_at').defaultNow().notNull(), }) +// A frozen copy of a Menu Item's Recipe as it existed when a given Order +// Item was placed. A later edit to the live Recipe (menuItemSteps) never +// reshapes an Order Item already in flight — see +// docs/adr/0005-recipe-snapshot-per-order-item.md. +export const orderItemSteps = pgTable('order_item_steps', { + id: serial().primaryKey(), + orderItemId: integer('order_item_id') + .notNull() + .references(() => orderItems.id), + sequence: integer().notNull(), + machineTagId: integer('machine_tag_id') + .notNull() + .references(() => machineTags.id), + estimatedDurationSeconds: integer('estimated_duration_seconds').notNull(), +}) + export const preparationStepStatusValues = [ 'queued', 'in_progress', diff --git a/src/domain/ordering.ts b/src/domain/ordering.ts index 09d03f4..f6db50b 100644 --- a/src/domain/ordering.ts +++ b/src/domain/ordering.ts @@ -170,31 +170,48 @@ export const assignQueuedStepsForTag = async ( } } -const createFirstStepForOrderItem = async ( +// Copies the Menu Item's live Recipe into orderItemSteps for this Order +// Item, then creates its first Preparation Step instance from that copy — +// so a later edit to the live Recipe never reshapes an Order Item already +// in flight (see docs/adr/0005-recipe-snapshot-per-order-item.md). +const initializeOrderItemRecipe = async ( db: DbOrTx, orderItemId: number, menuItemId: number, ) => { - const firstStep = firstRow( - await db - .select() - .from(schema.menuItemSteps) - .where( - and( - eq(schema.menuItemSteps.menuItemId, menuItemId), - eq(schema.menuItemSteps.sequence, 1), - ), - ), - ) + const liveSteps = await db + .select() + .from(schema.menuItemSteps) + .where(eq(schema.menuItemSteps.menuItemId, menuItemId)) + .orderBy(asc(schema.menuItemSteps.sequence)) // A menu item with no Preparation Steps at all is a data integrity bug // (see spec: every menu item's Recipe has at least one Step), not a valid // "nothing to do" case — surfacing it loudly here beats leaving the Order // Item permanently stuck with no Step and no way to progress or cancel. - if (!firstStep) { + if (liveSteps.length === 0) { throw new Error(`Menu item ${menuItemId} has no Preparation Steps defined`) } + const recipeSteps = await db + .insert(schema.orderItemSteps) + .values( + liveSteps.map((step) => ({ + orderItemId, + sequence: step.sequence, + machineTagId: step.machineTagId, + estimatedDurationSeconds: step.estimatedDurationSeconds, + })), + ) + .returning() + + const firstStep = recipeSteps.find((step) => step.sequence === 1) + if (!firstStep) { + throw new Error( + `Menu item ${menuItemId} has no Preparation Step with sequence 1`, + ) + } + const [instance] = await db .insert(schema.preparationStepInstances) .values({ @@ -304,7 +321,7 @@ export const placeOrder = async ( }) .returning() - const step = await createFirstStepForOrderItem( + const step = await initializeOrderItemRecipe( tx, orderItem.id, menuItem.id, @@ -461,22 +478,14 @@ export const advanceOrderItemStep = async (db: Db, orderItemId: number) => { .set({ status: 'done', doneAt: new Date() }) .where(eq(schema.preparationStepInstances.id, currentStep.id)) - const orderItem = firstRow( - await tx - .select() - .from(schema.orderItems) - .where(eq(schema.orderItems.id, orderItemId)), - ) - if (!orderItem) throw new Error('Order item not found') - const nextStepDefinition = firstRow( await tx .select() - .from(schema.menuItemSteps) + .from(schema.orderItemSteps) .where( and( - eq(schema.menuItemSteps.menuItemId, orderItem.menuItemId), - eq(schema.menuItemSteps.sequence, currentStep.sequence + 1), + eq(schema.orderItemSteps.orderItemId, orderItemId), + eq(schema.orderItemSteps.sequence, currentStep.sequence + 1), ), ), ) @@ -579,21 +588,25 @@ export const getOrderStatus = async ( ).map((row) => [row.id, row]), ) - const recipeRows = await db - .select({ - menuItemId: schema.menuItemSteps.menuItemId, - sequence: schema.menuItemSteps.sequence, - machineTag: schema.machineTags.name, - machineTagId: schema.menuItemSteps.machineTagId, - estimatedDurationSeconds: schema.menuItemSteps.estimatedDurationSeconds, - }) - .from(schema.menuItemSteps) - .innerJoin( - schema.machineTags, - eq(schema.menuItemSteps.machineTagId, schema.machineTags.id), - ) - .where(inArray(schema.menuItemSteps.menuItemId, menuItemIds)) - .orderBy(asc(schema.menuItemSteps.sequence)) + const recipeRows = + orderItemIds.length === 0 + ? [] + : await db + .select({ + orderItemId: schema.orderItemSteps.orderItemId, + sequence: schema.orderItemSteps.sequence, + machineTag: schema.machineTags.name, + machineTagId: schema.orderItemSteps.machineTagId, + estimatedDurationSeconds: + schema.orderItemSteps.estimatedDurationSeconds, + }) + .from(schema.orderItemSteps) + .innerJoin( + schema.machineTags, + eq(schema.orderItemSteps.machineTagId, schema.machineTags.id), + ) + .where(inArray(schema.orderItemSteps.orderItemId, orderItemIds)) + .orderBy(asc(schema.orderItemSteps.sequence)) const allInstances = orderItemIds.length === 0 @@ -652,9 +665,7 @@ export const getOrderStatus = async ( const itemViews: Array = items.map((item) => { const menuItem = menuItemsById.get(item.menuItemId) - const recipe = recipeRows.filter( - (step) => step.menuItemId === item.menuItemId, - ) + const recipe = recipeRows.filter((step) => step.orderItemId === item.id) const instances = instancesByOrderItem.get(item.id) ?? [] const instanceBySequence = new Map( instances.map((row) => [row.sequence, row]), diff --git a/tests/ordering.test.ts b/tests/ordering.test.ts index d796d01..3401dd1 100644 --- a/tests/ordering.test.ts +++ b/tests/ordering.test.ts @@ -1,3 +1,4 @@ +import { and, eq } from 'drizzle-orm' import { afterAll, afterEach, @@ -38,6 +39,7 @@ beforeEach(() => { // between tests instead of leaking a busy state into the next one. afterEach(async () => { await testDb.db.delete(schema.preparationStepInstances) + await testDb.db.delete(schema.orderItemSteps) await testDb.db.delete(schema.orderItems) await testDb.db.delete(schema.orders) }) @@ -335,6 +337,53 @@ describe('advancing steps and pickup', () => { }) }) +describe('recipe snapshot', () => { + it("keeps an Order Item on the Recipe as it existed at order time, even if the Menu Item's live Recipe is edited afterwards", async () => { + const order = await placeGuestOrder([ + { menuItemId: seed.menuItems.burger.id }, + ]) + + const burgerSecondStepEdit = and( + eq(schema.menuItemSteps.menuItemId, seed.menuItems.burger.id), + eq(schema.menuItemSteps.sequence, 2), + ) + // Edit the live Recipe after the Order was placed: swap the burger's + // second Step to a different Machine Tag and duration. + await testDb.db + .update(schema.menuItemSteps) + .set({ + machineTagId: seed.machineTags.oven.id, + estimatedDurationSeconds: 999, + }) + .where(burgerSecondStepEdit) + + try { + const beforeAdvance = await getStatus(order) + const burgerItemId = beforeAdvance.items[0].id + const pendingStep = beforeAdvance.items[0].steps[1] + expect(pendingStep.status).toBe('pending') + expect(pendingStep.machineTag).toBe('assembly') + expect(pendingStep.estimatedDurationSeconds).toBe(90) + + await ordering.advanceOrderItemStep(testDb.db, burgerItemId) + + const afterAdvance = await getStatus(order) + const secondStep = afterAdvance.items[0].steps[1] + expect(secondStep.status).toBe('in_progress') + expect(secondStep.machineTag).toBe('assembly') + expect(secondStep.estimatedDurationSeconds).toBe(90) + } finally { + await testDb.db + .update(schema.menuItemSteps) + .set({ + machineTagId: seed.machineTags.assembly.id, + estimatedDurationSeconds: 90, + }) + .where(burgerSecondStepEdit) + } + }) +}) + describe('access control', () => { it('refuses to show order status to someone without the access token', async () => { const order = await placeGuestOrder([