Freeze an Order Item's Recipe at order time
Price was already snapshotted per Order Item so later menu changes don't rewrite history, but the Recipe (Step list) wasn't: advancing an Order Item looked up its next Step from the live menuItemSteps table, so an operator editing a Menu Item's Recipe mid-cook could silently reshape an Order already in flight (different Machine Tag, duration, or Step order than what the customer paid for). Adds order_item_steps as a per-Order-Item copy of the Recipe, taken at placement time; placeOrder, advanceOrderItemStep, and getOrderStatus now all read from that snapshot instead of the live menu. Recorded as docs/adr/0005 and added Recipe as a formal CONTEXT.md term. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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([
|
||||
|
||||
Reference in New Issue
Block a user