Engineering · Operations

Deleting a dish that is on last month's orders

Archive, never delete, is easy to say and easy to write down. Our own Delete on a recipe ran one unconditional statement, and a dish sold a thousand times could have been destroyed by the vendor who sold it.


Every hospitality system tells you it archives rather than deletes. We wrote that rule down ourselves, at the top of the file every new person reads: archive, never delete; permanent IDs on items, recipes and orders.

Then we went and looked at what the Delete button on a recipe actually did. It checked you owned the recipe, and it ran one statement. Remove the row.

What a dish is attached to

Eleven tables in our database carry a recipe ID. Nine carry a product ID. One of the eleven is the line on an order — the record that says a guest bought this thing, at this price, on this night.

Delete the recipe and those lines survive. They just point at nothing. Nobody gets an error, no report goes red, and the month still adds up to the same money. What you lose is the ability to ever ask what did we sell and get a name back. Sales history does not disagree with itself loudly. It quietly stops being able to answer.

Not history is a different thing from history

The fix is not to refuse every deletion. A line typed wrong five minutes ago is not history and should not clutter a menu forever — that is how archive lists become the place nobody looks.

So the question is asked before anything happens. Has this been sold, made, ordered or moved? If yes, it is archived: the row stays, marked inactive, still joinable from the order line that names it, and the answer says which of those held it — it is on orders reads very differently from a shrug. If nothing ever happened to it, it really goes, and the setup rows that only ever belonged to it go with it.

One extra rule earns its place: a stock product that a recipe still uses is held too. Not because it is history, but because removing it leaves a dish costed against something that is gone, and a margin computed from a missing row is worse than no margin at all.

Nobody had pressed it

Worth being straight about the scope. No screen in our app calls that route, so this has never actually happened to anyone. That is not a defence. The wrapper was already sitting in the front end waiting for the next screen that wanted a Delete button, and any vendor admin could call it directly today.

A rule that lives only in a document is a rule until the first person who has not read the document ships a button. This one is now a piece of code that runs on every deletion, and two of its tests do nothing but refuse: no file may delete a recipe or a product without asking it first, and orders and their lines are never deleted at all. There is no nothing ever happened to this order case, because an order is a thing that happened.


Try it on tonight’s service.

Nothing to install, no card. Not better by the weekend? Close the tab.