Skip to content

An order that changed under you is not saved over - #821

Merged
sridharkalaibala merged 2 commits into
developfrom
fix/an-order-changed-under-you
Sep 16, 2026
Merged

sridharkalaibala merged 2 commits into
developfrom
fix/an-order-changed-under-you

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

A handset sends the whole order when it saves, and the till keeps only what arrives:

const finalItems = updatedItems.filter((ex) => incomingProductIds.includes(String(ex.item_id)));

That is not a mistake. It is how a cancelled dish gets cancelled.

It is also how a floor with several handsets loses food:

Waiter A adds a biryani at 19:00. Waiter B saves at 19:01 from a screen opened at 18:58. B's list has no biryani in it, so the till removes one the kitchen has already cooked.

The bill goes out short and nobody is told: not A, not B, not the kitchen, and not the shop, which has just given away a plate of biryani with nothing recording that it did.

The guard

A caller may say which version of the order it was looking at (seen_at). A save written against an older one is refused with order_changed and the moment the order actually changed, so the client can reload, show what happened, and let a person decide again. Only a person knows whether that biryani was meant to go.

What it deliberately does not do

  • Silence still means yes. A caller that sends no seen_at is served exactly as before. Handsets already in shops are older than this code, and refusing their saves would turn a bug that loses one dish into one that takes no orders at all.
  • An unreadable timestamp is not a conflict. That is a caller this check cannot help, and blocking it helps nobody.
  • An order nobody has ever updated is judged by when it was created, so a first edit is guarded too rather than waved through.

Tests

Against a real mongod, because the question is what the database ends up holding.

✓ IS REFUSED, and the dish the other waiter added is still there
✓ says when the order actually changed, so the client can show it
✓ goes through, and a dish left out is still cancelled
✓ a caller that says nothing is served exactly as before
✓ an unreadable timestamp is not treated as a conflict
✓ an order nobody has ever updated is judged by when it was created

Three fail without the guard. The other three exist to catch the guard overreaching and pass either way - in particular, leaving a dish out must still cancel it, which is the behaviour this sits in front of.

The captain side, which sends seen_at and shows the waiter what changed, follows in Posnic/captain.

A handset sends the WHOLE order when it saves, and the till keeps only
what arrives. That is not a mistake: it is how a cancelled dish gets
cancelled.

It is also how a floor with several handsets loses food. Waiter A adds a
biryani at 19:00. Waiter B saves at 19:01 from a screen opened at 18:58,
so B's list has no biryani in it, and the till removes one the kitchen
has already cooked. The bill goes out short. Nobody is told: not A, not
B, not the kitchen, and not the shop, which has now given away a plate of
biryani and has nothing that says so.

So a caller may say which version of the order it was looking at, and a
save written against an older one is refused with `order_changed` and the
moment it actually changed. The client reloads and decides again, because
only a person knows whether that biryani was meant to go.

SILENCE STILL MEANS YES. A caller that sends no seen_at is served exactly
as before. Handsets already in shops are older than this code, and
refusing their saves would turn a bug that loses a dish into one that
takes no orders. An unreadable timestamp is not a conflict either: that
is a caller this check cannot help, and blocking it helps nobody.

An order nobody has ever updated is judged by when it was created, so a
first edit is guarded too rather than waved through.

Six tests against a real mongod. Three of them fail without the guard;
the other three are there to catch the guard overreaching, and pass
either way.
The refusal went back as 404, which says the order is not there. It is
there, the caller is welcome, and the request is simply out of date.

409 also gives the client something better to key on than the spelling of
a message, so the handset's handling stays correct when somebody rewords
it later.
@sridharkalaibala
sridharkalaibala force-pushed the fix/an-order-changed-under-you branch from 961324d to 4a81253 Compare September 16, 2026 10:03
@sridharkalaibala
sridharkalaibala merged commit 8f91c1e into develop Sep 16, 2026
7 of 9 checks passed
@github-actions github-actions Bot added the ready for QA Merged to develop and live on develop.posnic.io - anyone can test it label Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Merged to develop. Anyone can test this - you do not need write access.

Try it at https://develop.posnic.io, or run it yourself:

git fetch origin develop && git checkout develop
npm install && npm --prefix api install
npm run dev   # then http://localhost:3000

When you have tested it, say what you did and what happened, and set
QA passed or QA failed. If you cannot set labels, just comment -
a maintainer will.

Reporting that something is broken is as useful as fixing it. It is
better found here than by a shopkeeper.

sridharkalaibala added a commit to Posnic/captain that referenced this pull request Sep 16, 2026
* Somebody else got there first, and the waiter is told so

A save sends the WHOLE order and the till keeps only what arrives, which
is how a cancelled dish gets cancelled. On a floor with several handsets
it is also how food goes missing: another waiter adds a biryani while
this screen is open, this screen saves a list that never had it, and the
till removes a dish the kitchen has already cooked. The bill goes out
short and nobody is told.

So both saves now say which version of the order they were looking at -
the order's own timestamp, handed back exactly as it arrived, nothing
guessed - and the till refuses one written against an older version.
Posnic/POS#821 is that half.

Being refused is not an error message. The waiter did nothing wrong and
lost nothing, because what they typed was never sent. They are told that
somebody else changed the order, the sheet closes rather than sitting
there looking ready for a second tap at the same refusal, and the order
is fetched again so the screen tells the truth. What happens next is
theirs: only a person knows whether the dish somebody else added was
meant to go.

An order nobody has ever edited falls back to when it was opened, so a
first edit is guarded too - which is the case a busy table hits most.

An ordinary failure still reads as a failure. A server that is down is a
different problem, and a waiter must not be told somebody edited the
order when nobody did.

The new sentence is not in the Tamil pack yet: that file is being
rewritten in the coverage PR and adding a line here would collide with
it. It follows once that lands.

* And the waiter reads it in their own language

The sentence shown when somebody else has changed the order was left in
English because the pack was being rewritten in another branch at the
time and a line here would have collided with it. That branch has landed,
so the loose end closes rather than waiting to be noticed by a waiter who
reads Tamil and meets one English sentence at the worst moment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for QA Merged to develop and live on develop.posnic.io - anyone can test it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant