Skip to content

Release: an order says when it will usually be ready - #792

Merged
sridharkalaibala merged 13 commits into
mainfrom
develop
Sep 16, 2026
Merged

sridharkalaibala merged 13 commits into
mainfrom
develop

Conversation

@sridharkalaibala

@sridharkalaibala sridharkalaibala commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Promotes develop to main. One merged pull request.

Merge with a merge commit, never a squash. The repository ruleset enforces that, so the button should only offer it.

(An earlier version of this description said it also carried #791. It does not - #791 is still open against develop. Corrected rather than left to be found at the button.)

#790 - when the food will be ready

A customer places an order, gets a token number, and hears nothing. The list of this phone's orders said "With the kitchen" and that was the whole of it, which is the moment somebody walks up to the counter to ask.

It now says "Usually ready by about 8:05", estimated from the slowest dish on the order plus the queue that was ahead of it - not the sum of the dishes, because a kitchen cooks several at once and the order leaves the pass when the slowest thing is done.

Nothing here knows when food is actually finished: no cook marks a ticket done, so there is no ready signal and none is invented. The word "usually" is doing real work.

It stays silent wherever it would be guessing: a shop that has stated no prep times, an order the shop has not accepted yet (which says a length instead, because a clock time before anybody has started cooking is a fiction), a cancelled order, and a time that has already passed.

Worth knowing before you merge

It shows nothing on any shop today. Production carries 272 dishes and not one prep time, so the estimate has nothing to work from - which is exactly the honest fallback behaving as designed. It starts speaking the moment somebody uses Tools → How long a section takes from the last release, which is three clicks.

Verified before opening

Root suite 2,645 of 2,648 on the develop tip; the three remaining are the release-artifact tests that need frontend/public built, which CI does. API suite 10,754 of 10,754.

They place an order, get a token number, and then hear nothing. The list
of this phone's orders said "With the kitchen" and that was the whole of
it - which is the moment somebody walks up to the counter to ask, the one
interruption an ordering channel exists to remove. Every food app they
have used shows a time.

Nothing here knows when food is actually finished: no cook marks a ticket
done, so there is no ready signal and this does not invent one. It is an
estimate from two things the shop really has told us - the slowest dish on
the order, and the queue that was ahead of it - and the word "usually"
does real work in the sentence.

The slowest dish and not the sum of them. A kitchen does not cook the
biryani, then the naan, then the dal; several hands work at once and the
order leaves the pass when the slowest thing is done. Adding them up would
quote an hour for a meal that takes twenty-five minutes, and an estimate
always wrong in the same direction stops being read.

Frozen at the moment it is made, and stored on the order. A figure
recomputed on every refresh would creep as other orders arrive, and a
promise that moves while somebody watches it is worse than one that is a
little wrong.

It stays silent wherever it would be guessing: a shop that has stated no
prep times, an order still waiting for the shop to accept it (which says a
length instead, because a clock time before anybody has started cooking is
a fiction), a cancelled order, and a time that has already passed - an
estimate counting backwards at somebody still waiting is worse than none.

Found while wiring it: the estimate was computed after the document that
uses it, which is a temporal dead zone and would have thrown on every
single online order.
A customer is told when the kitchen usually has it ready
Two things, both about the moment a customer changes their mind.

MORE FOOD NEEDS NOBODY'S PERMISSION. An extra naan costs the kitchen a
naan it is glad to sell: nothing is wasted, nothing already cooked is
thrown away, and the only answer anybody was ever going to give is yes.
Holding that in a queue until somebody notices is a customer waiting on a
decision that was not one. Taking something away is the opposite - the
biryani may be in the pan, and whether it can be called back is a
judgement only somebody in the kitchen can make.

So the window now gates only the taking away. Inside it nothing has
started and the order is still the customer's, exactly as before. Outside
it, a request carrying both has its halves answered separately: the naan
is already being made by the time the shop reads the question about the
biryani. A delivery or a hotel room is still refused outright in both
directions, because that is somebody else's money in the total rather
than a question about the kitchen.

AND THE CARD SAYS WHAT IT WOULD DO. It listed only the lines that moved,
so "Chicken Biryani: 2 to 1" told a person nothing about whether that was
most of the order or a detail of it - they had to open the sales screen
to find out, by which time they were no longer deciding in a hurry. Every
line is drawn now, the untouched ones stepped back, with what it was and
what it would become side by side.

And every request said "Asked to change", whether a customer had dropped
one naan or emptied the order. Now that adding never becomes a request,
every one of them is something being taken away and the card can say
which: cancel the whole order, remove everything on it, remove an item,
remove some items, or simply asked for fewer. A line going to nothing
says REMOVED rather than "1 to 0", which is a sentence nobody reads at a
glance.

A request made before this shipped can still be sitting in the queue
carrying an addition, so the card still reads one.
Owner: "we need year pattern required in the sales bill number example
attached have 26 in the year. and then number increasing would be okay.
you suggest per day increase or year wise reset better tell me
international standards." And on the answer: "i accept recommandation and
may configurable if people from EU and international. Blocker fix that. i
dont know how its got SB1d14 and all."

SB1D14-000051 is S, the branch's code, this till's gateway-assigned code,
and a running number. The till code is what stops two tills in one shop
minting the same number; it has been there since bill numbers went atomic.

THE RULE. India, CGST Rule 46(b): a consecutive serial number, NOT
EXCEEDING SIXTEEN CHARACTERS, of letters, digits, "-" and "/", UNIQUE FOR
A FINANCIAL YEAR. The reference invoice he sent is exactly sixteen -
VR26-27VIR006782 - because whoever built that hit the same wall. The EU
VAT Directive asks only for a sequential number, with no length limit and
no prescribed reset, so the year and the reset are settings and the
charset is not.

Not per day. A daily reset repeats numbers inside the year unless the
date is in the number, and a date costs six to eight of the sixteen. It
also destroys what an auditor actually uses: one consecutive run per year
has visible gaps when something is cancelled, and three hundred and
sixty-five short runs do not.

utils/bill-number.js builds the number and shortens the RUNNING NUMBER
when something has to give - never the year, which uniqueness depends on,
and never the till code, without which two tills collide. With no year
asked for it produces exactly what the template literal it replaced
produced, pinned over 72 combinations.

WHOSE MIDNIGHT. A bill rung up at 00:30 on 1 April in Chennai belongs to
the new financial year. A cloud instance running in UTC would still call
it March and number it into a year that closed half an hour ago. The
period is worked out in the shop's own clock.

THE ROLL-OVER IS ONE ATOMIC STEP. A read, a compare and a write would be
three, and a year turns over at midnight in a restaurant that is still
serving. One pipeline update on the counter row, so two tills billing in
that second cannot both be given number one. Checked against a real
mongod, not a mock of one. A counter written before any of this reads as
"no period" rather than as a mismatch, or the first bill on every shop in
the estate would restart at one.

And it found a real one: the counter sale formatted its own number while
the customer's ordering page called generateSalesIdForBranch, with both
drawing on one counter. A shop turning the year on would have had it on
its online orders and not on its counter bills, and the reset would have
had the till reissuing numbers it had already given out. Both now come
through one door.

Off by default. Ninety shops are mid-year with a running series on their
invoices; switching them on an upgrade would change the shape of every
number overnight and restart the count in the middle of a year. A shop
turns it on, best on the first day of its own year.
@sridharkalaibala sridharkalaibala changed the title Release: a request says what it would do, and an order says when it will be ready Release: an order says when it will usually be ready Sep 16, 2026
…ay-carry

A bill number a tax invoice may carry
Adding is never a request, and a request says what it would do
Stage 5 of the print roadmap - "the customer knows". The thank-you page
asked the shop one question, once, as it opened: is this paid yet.
Everything that happened to the order after that - a person accepting it,
a ticket coming out of a kitchen printer, the shop refusing it at 2am -
happened behind the customer's back, and the only way to find out was to
walk to the counter and ask.

The stage's own warning is what shaped this: "Do not ship a stage nothing
can move off. If Ready is unreachable because nobody presses anything, a
frozen tracker is worse than none." Three of the four rungs everyone has
seen on a delivery app are unreachable here. Nothing marks an order ready.
Nothing says a cook started; we know a ticket PRINTED. And a shop on
automatic never decides anything, so "Accepted" would describe a decision
nobody made.

So there is no ladder. There is a TRAIL: what has already happened, each
with the moment it happened, newest last. A trail cannot freeze, because
it never claims anything about what comes next - a shop whose kitchen
printer is off simply has a shorter trail, not a stuck one.

Placed, from created_date. Accepted, only where order_state_by names the
person who worked the queue. In the kitchen, only where a till reported
that a printer produced paper. Refused and cancelled as endings, drawn
after the history rather than in place of it: a customer who cancelled two
minutes late needs to know the kitchen had already started.

The one thing named before it happens is a held order's acceptance,
because order-approval.js allows pending to move nowhere else. It carries
the shop's own recent answering speed, median over a fortnight, worded as
a description of the past. No cooking time: nothing in the product knows
when food is finished, and an invented ETA is the number a customer waits
against and then comes to the counter about.

Keys and times on the wire, words on the page, because /order carries its
own Tamil dictionary keyed by the English sentence and a sentence built on
the server arrives as English nothing can translate.

The history page is fixed by the same shape: it called every accepted
order "With the kitchen", including ones whose ticket never printed.

The page polls with backoff and asks NOTHING while the phone is in a
pocket, resuming the moment somebody looks.
The thank-you page resolved the shop through knownBranchId() and the order
through rememberedOrders(). Both live in indexedDB.js. This page does not
load indexedDB.js and never has.

Both calls were written behind `typeof ... === "function"` guards, so
nothing threw and nothing showed: the shop id came out empty, the function
returned before its first request, and everything behind it - the bill
when the shop marks the order paid, the offer to pay by UPI - has silently
done nothing on that page since the day it was written. A guard that turns
a missing dependency into a quiet nothing is how a shipped feature runs for
months without ever running once, and the trail would have joined it.

Loading indexedDB.js here is not the fix: it starts a timer that refetches
the shop's whole menu every ten seconds, behind a page whose only job is to
show a token.

So the page answers from what it already holds. The order comes from
?order= on the way in from the history page, or from the sale id on the
receipt this phone was handed at checkout. The shop comes from
posnic_store, which indexedDB.js writes on every menu load. The two
spellings of that key are pinned together, because a rename on one side
would put this page straight back where it was.
Owner: "let restaurent owner decide that. give option auto cancel or auto
accept. based ont time he defines it. by default dont accpept or reject."

waiting-order-policy.js was written, tested and merged, and decided
perfectly for nobody. Two dead ends, one on top of the other:

setPolicy() on the desktop OrderAlert was called from nowhere, so the
policy was always { onSilence: 'nothing', decideAfterMinutes: 0 } and
decide() could only ever answer "nothing". There was no setting to feed
it either. So "auto cancel after ten minutes" has never once happened.

And when it did speak, the till emitted posnic:order-decided on the
process bus, WHICH NOTHING LISTENED TO - and then deleted the order from
its pending map. The alarm went quiet with the order still sitting there
and the customer still waiting. An alarm that stops is a promise that
somebody dealt with it, and that promise was false. That is the exact
failure this whole area exists to prevent, and it was built in.

The rule now fires in the API. The till is a sound module, and its own
comment says deciding belongs where the order lives; it is also the wrong
half, because a shop served from the cloud has no till and its held
orders would sit for ever. Both halves read the same pure policy for
different fields: the till reads alert and reach and makes a noise, the
server reads decide and moves the order. Two copies, because the API
ships outside the asar archive, pinned by a behavioural test over a
thousand combinations.

It goes through decideOnOrder, the same door a person at the queue uses,
so an automatic acceptance prints its kitchen ticket exactly the way a
tapped one does rather than by a second path that would drift from it.

AND IT READS THE ORDER BACK BEFORE IT ANNOUNCES ANYTHING. decideOnOrder
narrows its write by whatever tenant the process was last serving and
answers success on what it asked for rather than on what changed, so a
write that matched nothing still says yes. Stopping the alarm on that
would be the same bug arriving by a different door.

Nothing happens unless a shop asked. Absent settings mean nothing, both
halves of the rule are required, an order older than twelve hours is left
for a person, and an aggregator's own deadline is never overrun.

Settings sit under New online orders, beside "When an order arrives", and
are hidden entirely while a shop is on automatic.
…-order

A customer is told what happened, and never what might
The shop's declared default, actually firing
salesChannels.load() called PosnicPro.settings.showSilenceRule(). The
channels screen is lifted out and run on its own in
tests/online-ordering-screen.test.js, where PosnicPro.settings does not
exist, so load() threw on the reach across.

Not only a harness problem. A load() that throws leaves Delivery Partners
with no partners and Restaurant with no venues or charges - and collect()
reads those same rows back out of the DOM, so the next Save would write
the empty screen over the real ones. That is the shape of the channel
settings split bug, arriving through a namespace instead of a tab id.

So the helper moves onto the module whose screen the control is on, and
its handler is bound beside the other channel-screen handlers.
…its-own

And drawing the channels screen does not depend on another namespace
@sridharkalaibala
sridharkalaibala merged commit d8b0476 into main Sep 16, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant