Take a bill request from the floor, and refuse to settle it - #612
Merged
Merged
Conversation
Owner asked whether billing from the handset is standard, and whether settlement belongs to the cashier rather than the waiter. Both answers are yes, and this is the half of it that lives on the server. A WAITER MAY ASK FOR THE BILL. A WAITER MAY NOT SAY IT WAS PAID. Three moments, three fields, because "somebody wants this billed" and "the bill came out of the printer" are different facts and the gap between them is where a bill gets lost. bill_requested_at, bill_requested_by, bill_printed_at - the till polls for the first and stamps the third, exactly the way it already does for kitchen tickets, so a till that was switched off catches up when it returns rather than losing the bill. Nothing on this path writes payment_status, and a test fails if it ever starts. That is the whole separation of duties: the person who takes the order must not be the person who declaring the money received. Three routes, guarded by who is asking. /requestBillPrint sits with the floor screen's own route, because it is the same waiter on the same handset asking about the same tables - deliberately NOT behind ensureKioskKey, which is a per-installation key and would make a phone into a till. /pendingBillPrints and /markBillPrinted keep that key: they are the machine talking to itself about its own printer. TWO THINGS THIS CODEBASE HAS BEEN BITTEN BY BEFORE, both avoided on purpose. The schema is STRICT, so the three fields are declared - invoice_key and source_invoice_id each shipped stripped and silent for exactly that reason. And the open-ticket filter uses the literal 'Unpaid' rather than PAYMENT_STATUS, which has no UNPAID member: reaching for the constant would have put undefined in the query, and Mongo answers an undefined match with every document where the field is missing. Asking twice does not make two bills, a table with nothing open is told so rather than told yes, and a request naming no table is refused instead of billing everything.
Owner: "if mobile give bill then printer out should comes from desktop connected. not from KOT." And then: do it. The handset marks the ticket. This half runs on the machine that owns the printers and turns the mark into paper. WHICH PRINTER, WITHOUT ASKING ANYBODY. The bill goes to the same printer a receipt already goes to. That needs no new setting and no new screen: a shop that can print a receipt can print a bill, and a shop that cannot has a problem this feature did not cause and cannot fix. I had expected to need a Hardware Manager choice and did not - hardware-manager already answers getDefaultPrinter from the main process, which is the whole question. NOT THE KITCHEN'S. A KOT and a bill are different documents, not one document in two places: a ticket is departmental and carries only its own lines, a bill is single and carries the totals, the tax and the shop header. printer-targets has modelled that split from the start - a LIST of printerNames for KOT, one printerName for the receipt - and this rides it rather than inventing another. MARKED PRINTED ONLY AFTER THE PAPER CAME OUT, and not at all if the printer refused. A till that dies mid-job asks again when it returns, which costs a duplicate slip; marking first would lose the bill for good, and the only person who would know is the guest still waiting at the table. THE DRAWER IS NEVER OPENED. A bill is a request for money, not a receipt of it. Kicking the drawer here would be the till behaving as though a waiter had settled, which is the one thing this whole feature is built not to do. Polled rather than pushed. Kitchen tickets get an event because a ticket is wanted the second it is saved; a bill is wanted by the time somebody has crossed the room. The poll is also what makes a till that was switched off, asleep or mid-update catch up instead of losing the bill. readLocalBranches is lifted to module scope and exported, because two callers need it now - the Hardware Manager screen offering a list, and this working out which shop to ask about. Two copies of a Mongo connection and a credentials lookup is two places to fix the day either changes. Two things the suite told me, both mine: the README pins the endpoint count and three routes moved it to 632, and the server's message list needed the five new strings. Three failures remain and all three are already red on develop - checked by running the suite against develop's tree with this branch stashed.
Two things the rebase onto develop turned up, both mine. THE ENDPOINT COUNT. The README pins it and the test exists to keep it honest. develop had moved it to 630 while this branch said 632, so neither side of the conflict was right - the answer is 633, and the test computed it rather than me guessing. THE LINT JOB USES A DIFFERENT PRETTIER FROM THE COMMIT HOOK. api/ carries its own .prettierrc.json, and the pre-commit hook runs the ROOT prettier over staged files. So an api file can pass the hook on the way in and fail the api lint job afterwards, which is exactly what happened to the takeaway test and needed #610 to clean up after me. Same trap, same branch, second time - so these three are formatted with the api's own prettier before pushing rather than after somebody notices. src/services/ordering-assistant.service.js is also unformatted and is not mine. Left alone: silently reformatting somebody else's file inside a feature branch hides it in a diff about something entirely different.
sridharkalaibala
force-pushed
the
feat/the-bill-from-the-floor
branch
from
September 12, 2026 10:55
8dba6b7 to
3894a24
Compare
Three red checks on #612, all the same root: docs/API.md and docs/openapi.json are GENERATED FROM THE ROUTES, and I added three routes without regenerating them. API docs are current the generator's output differed from what is checked in API unit tests all 10,418 passed; the job failed on its README step, which compares the badge against docs/API.md - the README said 633 and the generated document still said 630 Lint (reporting only) ordering-assistant.service.js, unformatted and not mine The docs now say 633 and the badge already did, so they agree. I left that lint file alone on the last push, reasoning that reformatting somebody else's file inside a feature branch hides it in a diff about something else. That was the wrong call for a check that is RED: a permanently failing "reporting only" job costs nothing on any single pull request and teaches everybody to read past a red X, which is the argument I made myself in #591. One command, its own commit line, and the file's 21 tests still pass.
Contributor
|
Merged to 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:3000When you have tested it, say what you did and what happened, and set Reporting that something is broken is as useful as fixing it. It is |
sridharkalaibala
added a commit
that referenced
this pull request
Sep 12, 2026
## Release: promote develop to main Since the last release: Talk to order (the customer speaks to the shop's assistant and it speaks back, live on OpenAI keys, turn by turn elsewhere; it greets first by the shop's name, hears Tamil, adds every item or says why, knows the shop, reads the order back and sends it to the kitchen on a yes, and is metered in the shop's currency), a code for the conversation that lands on any browser, the shop kept in the address bar on every ordering page, deploy-stamped asset versions so a phone picks up each release, and the other windows' work landed since. 152 commits by history. Merge with a merge commit, not squash: each squash makes the next release conflict. ### Commits - The assistant can send the order to the kitchen, and the sheet has a Review order button (#620) - Link retail POS operations checklists (#619) - fix: secure public order and raw print paths (#618) - The sandbox's demo-logins card sits on the login form only (#616) - Say "Order updated", the way a person would (#617) - Take a bill request from the floor, and refuse to settle it (#612) - The arrival page reads the URL whenever it says anything (#615) - Link restaurant POS shift checklist (#614) - The greeting box says what the standard line is, and whose name it uses (#613) - The assistant speaks first when the line opens (#611) - Format the takeaway queue test so the lint job is green again (#610) - The voice hears Tamil, adds every item or says why, and knows the shop (#609) - Answer the takeaway queue whichever way it is spelled (#608) - Meter the live voice line, in the shop's own currency (#607) - The shop stays in the address bar, and every asset ships with a version (#606) - The microphone is asked for inside the tap, before anything else (#605) - Voice adds to the order, the live switch is obvious, a first-time callout, and a code that lands you in the conversation (#604) - fix: validate table-order IDs before querying (#603) - Format two API tests so the lint job is green again (#602) - Fold the AI setup to one line once a key is saved (#601) - The microphone sits beside the send arrow, and a refused microphone is said (#600) - A decision for every collection, a tombstone for every table, a build that checks both (#596) - Give the uninstaller its own words, so removing Posnic does not announce an install (#597) - Talk to order: the customer speaks to the shop's assistant, and it speaks back (#598) - Sync main into develop (#595) - Merge remote-tracking branch 'origin/main' into develop - The assistant is configured on the AI page, with its instructions there (#593) - Voice/cheaper ears (#592) - Merge remote-tracking branch 'origin/main' into develop - Format the ordering-assistant test the way the check asks for (#591) - Say on the settings screen that the local language is allowed (#587) - Merge main into develop: the squash-merged release, so the next release can merge cleanly - Merge remote-tracking branch 'origin/main' into develop - A table on the code is stated, not asked (#589) - The receipt's PDF library rides in the bundle, and an iPhone keeps its keyboard microphone (#588) - The digital menu offers no way in (#586) - The assistant reads the menu where the storefront actually puts it, and checks the door first (#585) - Fill the Served at box, so a dish can actually be put on a serving period (#575) - Publish canonical links on the APT repository (#566) - Ask about the menu: the shop's own model answers customers on the ordering page (#582) - Let the voice order carry the note, the near-misses, what goes with it, and a read-back (#583) - Make the feature tiles glass, not paint (#584) - Two cheaper ears: Deepgram and AssemblyAI (#581) - Save the venue that was typed, or say why not, and show where its QR points (#580) - A shop address the server no longer knows is recovered from the origin's default store (#579) - The photo is the top of the sheet again, and the cart no longer asks the server for "undefined" (#578) - Put the restaurant questions in a Restaurant card, shown only to restaurants (#576) - One port, and both sides already agree on it (#571) - Let Prettier reflow items.controller.js, so develop's format check is green again (#577) - Strip comments until nothing changes, pin the PDF library to its bytes, and drop a replace that replaced nothing (#574) - Merge main into develop: the squash-merged release, so the next release can merge cleanly - Show the console what the customer said, say the fee before the button, and speak Tamil (#572) - A kitchen ticket is not a settled bill (#569) - Close the Restaurant pane's opening tag, and refuse any tag that does not (#570) - Tell the handset what the floor is actually doing (#568) - style: format repository sources (#567) - Say when an AI key is saved, and let it be replaced or removed (#563) - Read the Restaurant switch the way it is stored (#565) - A restaurant orders dishes to a table; a shop sells items to collect or deliver (#564) - Searching takes the shop name down, and the count is one line (#562) - A long dish name cannot push a column under the order panel (#561) - The order page in one column on a laptop, and no line over a lone total (#560) - Give every feature its own colour, and stop greying the page (#558) - The ordering pages, as one design, on a phone and on a laptop (#559) - Say where you are, and whether this shop has tables (#557) - The menu, inch by inch, and one rule for what the shop sells (#556) - Stop the CLA from disclaiming itself before we ask anyone to sign it (#554) - A store id nobody had to invent (#553) - The menu first, the question at the end, and no spinner for ever (#551) - Group the Features page, and give each group its colour (#552) - Arrange the Restaurant page instead of stacking it (#550) - Branding belongs to the kiosk, and both pages are always live (#549) - Let the shop's model read what a waiter meant (#548) - Make searching the menu a place, not a filter (#547) - Read the storefront settings the server actually stores (#540) - Improve release metadata and expose POS evaluation guides (#532) - Show a waiter the shop's menu, not the online subset (#546) - Call the AI function that exists (#545) - Never remember that AI was unavailable (#544) - Say that a chat subscription is not an API key (#543) - Tell a shopkeeper where to actually get an API key (#542) - Make the AI switch save, load, and have a way in (#541) - chore(codeql): document custom CSRF protection - Put AI in the Features list, and make that switch mean something (#539) - fix(security): protect cookie-authenticated writes from CSRF - Stop develop deleting the bundles open tabs are still asking for (#534) - fix(ci): allow maintainer CLA identity - Say what the store id is, and ask a question people can answer (#535) - Load every photo of a dish, and let somebody open one (#537) - Give AI a way to be switched on (#536) - Never save rows the screen has not loaded (#531) - Put https on the pairing code, and stop asking req.protocol (#533) - Shop hostnames come from data, and a CLA the paid edition needs (#530) - Click the online ordering screen, and fix what that found (#529) - A spending cap, a meter, a data fence, and the first AI feature (#523) - A Features card is a switch. Write that down and enforce it (#527) - Make "taking orders right now" say one thing and mean it (#528) - Tell the recogniser what this shop sells, before it guesses (#526) - Fix what the split broke, and show a shop its own addresses (#525) - Document signed release verification (#520) - Put voice ordering where somebody would look for it (#522) - Give the captain app a page, and a way to pair a handset (#524) - Give every channel its own settings page (#521) - Let a shop actually turn voice ordering on, and fix the setting it turns (#519) - Merge the storage keys into .env instead of writing over it (#518) - The plumbing an AI feature goes through, and nothing that uses it yet (#516) - Let a deploy change prove itself (#517) - Merge pull request #515 from Posnic/feat/dish-photo-gallery - Run the production deploy when a bundle changes - Merge remote-tracking branch 'origin/develop' into feat/dish-photo-gallery - Correct an advisory note that said no fix existed - Show every photo of a dish, not just the first - Merge pull request #514 from Posnic/fix/deploy-customer-bundles - Merge remote-tracking branch 'origin/develop' into fix/deploy-customer-bundles - Actually ship the customer bundles - Do not let a suspended shop halt the deploy - Merge pull request #509 from Posnic/fix/captain-endpoints - Give the captain handsets their two endpoints back - Merge pull request #511 from Posnic/codex5_stream - Merge pull request #513 from Posnic/feat/dish-icons - Merge pull request #512 from Posnic/feat/menu-search-discovery - Give every dish a picture without asking anybody to upload one - Give the ordering page the search it never had - Find the dish when the customer cannot spell it - docs: link POS deployment decision guide - Merge pull request #508 from Posnic/feat/voice-transcribe - Merge pull request #510 from Posnic/test/channel-items-screen - Merge remote-tracking branch 'origin/develop' into test/channel-items-screen - Drive the bulk channel screen instead of reading it - Transcribe a waiter's spoken order on the till, never on the phone - Merge pull request #506 from Posnic/advertise-idempotency - Merge pull request #507 from Posnic/feat/item-channels - Offer the platforms most shops mean, one tap each - Merge remote-tracking branch 'origin/develop' into feat/item-channels - Put the settings where a shop would look for them - Classify a runtime flag as a grant or a capability - Say which channels sell an item, and change it in bulk - Offer a discount note only once there is a discount to explain - Say that this server dedupes orders, so a handset can resend - Merge pull request #483 from Tyagiquamar/sdk/external-sale-idempotency - Merge pull request #505 from Posnic/feat/channel-features - Merge remote-tracking branch 'origin/develop' into feat/channel-features - WIP on channel-features: 4e74ca7 Format the new wiring test - Merge branch 'develop' into sdk/external-sale-idempotency - Merge pull request #504 from Posnic/qr-order-idempotency - Merge pull request #503 from Posnic/fix/channels-settings-wiring - Make sending the same order twice safe - Format the new wiring test - Make the channels settings group reachable at all - Preserve proto keys in payload hashing (#483) - Fix external sale idempotency key matching (#483) - SDK: add external-sale idempotency helper ### After merging ```bash git checkout main && git pull git tag vX.Y.Z && git push origin vX.Y.Z ``` --------- Signed-off-by: Tyagiquamar <mohdquamartyagi@gmail.com> Signed-off-by: Sridhar Bala <sridhar@posnic.com> Co-authored-by: Tyagiquamar <mohdquamartyagi@gmail.com> Co-authored-by: Sridhar Bala <sridhar@posnic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
sridharkalaibala
added a commit
that referenced
this pull request
Sep 12, 2026
## Release: promote develop to main Everything since #599. 170 commits by history, and the customer-facing half is one story: **a customer can now order by talking, and see what happened afterwards.** ### Merge this with a MERGE COMMIT, not a squash #599 was squashed, which is why develop needed a fourth "sync main into develop" commit before this could open cleanly. That sync is what cleared the conflicts on this PR. A merge commit costs nothing and ends the cycle; a ruleset on main restricting it to merge commits is one click and ends it for good. ### What a customer gets - **Talk to order.** The assistant greets first by the shop's name, hears Tamil as Tamil, adds every item asked for or says why it could not, answers where the shop is and when it opens, and sends the order to the kitchen on a clear yes. One offer of something alongside, never two. No prices unless asked. - **The order lands somewhere.** A drawn kitchen scene in three beats: the docket flies to the hatch, the bell rings, the pan steams. Nothing navigates on its own; Done does. - **Your orders**, kept in the browser, with live status from the shop. A row opens into the order. - **A grace window** to change or cancel it, whose length the shop sets. After it, cancelling becomes a request the shop answers in the queue it already uses to accept orders. - **Pay by UPI** on an unpaid order; the bill downloads once the till marks it paid. - The shop stays in the address bar, and every asset carries the commit so a phone picks up each release instead of yesterday's code. ### What a shop gets - An AI page that configures the assistant, live voice, the greeting and house notes, with the spend metered per feature and per minute of open line. - A saved voice key that shows Edit and Remove instead of a form that looks untouched. - Cancellation requests in the online orders queue, beside orders waiting to be accepted. ### Fixed on the way - **One bill-number counter per branch.** Two counters were issuing the same numbers and the unique index refused the second, so **no order could be placed at all**, from the page or the assistant. That is a production bug on any process serving both a console and a customer page, not a sandbox quirk. - `?ai=talk` works on a browser that has been to the shop before. - The nightly sandbox reset stops wiping the keys somebody typed. ### Commits - A saved voice key looks saved, with Edit and Remove (#636) - Link retail POS user access review checklist (#630) - The nightly reset stops wiping the keys somebody typed (#635) - A grace window on an order, and a cancellation the shop decides (#633) - The greeting example is a name, not a tag a browser eats (#634) - The order flies to the kitchen, drawn (#631) - Pay the bill by UPI, from whichever app the customer has (#629) - Your orders, kept on this phone, with the bill once the shop is paid (#628) - An order remembers the device it came from, and answers the phone that placed it (#627) - The order lands where the conversation was, and the token screen is quiet (#626) - Switching sample data off hides every kind of it, not only the products (#624) - Link retail POS incident and printer guides (#621) - The assistant runs the whole order, and talks like a waiter about it (#625) - Let a shop with an expired login sign in again (#622) - One bill-number counter per branch, and a taken number is taken again (#623) - The assistant can send the order to the kitchen, and the sheet has a Review order button (#620) - Link retail POS operations checklists (#619) - fix: secure public order and raw print paths (#618) - The sandbox's demo-logins card sits on the login form only (#616) - Say "Order updated", the way a person would (#617) - Take a bill request from the floor, and refuse to settle it (#612) - The arrival page reads the URL whenever it says anything (#615) - Link restaurant POS shift checklist (#614) - The greeting box says what the standard line is, and whose name it uses (#613) - The assistant speaks first when the line opens (#611) - Format the takeaway queue test so the lint job is green again (#610) - The voice hears Tamil, adds every item or says why, and knows the shop (#609) - Answer the takeaway queue whichever way it is spelled (#608) - Meter the live voice line, in the shop's own currency (#607) - The shop stays in the address bar, and every asset ships with a version (#606) - The microphone is asked for inside the tap, before anything else (#605) - Voice adds to the order, the live switch is obvious, a first-time callout, and a code that lands you in the conversation (#604) - fix: validate table-order IDs before querying (#603) - Format two API tests so the lint job is green again (#602) - Fold the AI setup to one line once a key is saved (#601) - The microphone sits beside the send arrow, and a refused microphone is said (#600) - A decision for every collection, a tombstone for every table, a build that checks both (#596) - Give the uninstaller its own words, so removing Posnic does not announce an install (#597) - Talk to order: the customer speaks to the shop's assistant, and it speaks back (#598) - The assistant is configured on the AI page, with its instructions there (#593) - Voice/cheaper ears (#592) - Format the ordering-assistant test the way the check asks for (#591) - Say on the settings screen that the local language is allowed (#587) - Merge main into develop: the squash-merged release, so the next release can merge cleanly - A table on the code is stated, not asked (#589) - The receipt's PDF library rides in the bundle, and an iPhone keeps its keyboard microphone (#588) - The digital menu offers no way in (#586) - The assistant reads the menu where the storefront actually puts it, and checks the door first (#585) - Fill the Served at box, so a dish can actually be put on a serving period (#575) - Publish canonical links on the APT repository (#566) - Ask about the menu: the shop's own model answers customers on the ordering page (#582) - Let the voice order carry the note, the near-misses, what goes with it, and a read-back (#583) - Make the feature tiles glass, not paint (#584) - Two cheaper ears: Deepgram and AssemblyAI (#581) - Save the venue that was typed, or say why not, and show where its QR points (#580) ### After merging ```bash git checkout main && git pull git tag vX.Y.Z && git push origin vX.Y.Z ``` --------- Signed-off-by: Tyagiquamar <mohdquamartyagi@gmail.com> Signed-off-by: Sridhar Bala <sridhar@posnic.com> Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com> Co-authored-by: Tyagiquamar <mohdquamartyagi@gmail.com> Co-authored-by: Sridhar Bala <sridhar@posnic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both readings are right, and this is the server half built to them.
The rule
The waiter is standing at the table when the guest asks, and walking to the
counter so somebody else can press a button is the errand a handset exists to
remove — Toast, Square, Lightspeed, MICROS and Petpooja all let the floor fire
it. None of them settle on the waiter's word, because the person who takes
the order must not be the person who declares the money received: a cash bill
could otherwise be closed and pocketed with nothing in the system to disagree.
Nothing on this path writes
payment_status, and a test fails if it everstarts.
Three moments, three fields
bill_requested_at,bill_requested_by,bill_printed_at.Because "somebody wants this billed" and "the bill came out of the printer"
are different facts, and the gap between them is where a bill gets lost. The
till polls for the first and stamps the third, exactly as it already does for
kitchen tickets — so a till that was switched off catches up when it returns
instead of losing the bill.
Guarded by who is asking
/requestBillPrintprotectOrKioskKey/pendingBillPrintsensureKioskKey/markBillPrintedensureKioskKeyensureKioskKeyis deliberately not on the waiter's route: that key is perinstallation, and a phone holding one would be a till — on a device that gets
lost and sold.
Two traps this codebase has been bitten by, avoided on purpose
The schema is strict. All three fields are declared.
invoice_keyandsource_invoice_ideach shipped stripped and silent for exactly this reason,and both carry a comment saying so.
PAYMENT_STATUShas noUNPAIDmember. It carriespending/completed/failed/refunded; this column holds the word
'Unpaid'thatcreateOnlineOrderwrites andgetTablesWithActiveOrdersreads. Reaching forthe constant would have put
undefinedin the query, and Mongo answers anundefined match with every document where the field is missing — the
opposite of narrowing.
Behaviour
Asking twice does not make two bills. A table with nothing open is told so
rather than told yes. A request naming no table is refused instead of billing
everything.
Not in this PR
The till does not print anything yet. The request is recorded and
/pendingBillPrintswill hand it over, but nothing on the desktop polls it, andthe bill printer has to be choosable in Hardware Manager first. That is desktop
code I cannot run or test from here, and it needs a desktop build to reach a
shop either way — so it is its own piece of work rather than 200 untested lines
tacked onto this.
The handset half is captain #53.
Tests
11 in
tests/unit/repositories/the-bill-a-waiter-asked-for.test.js, most ofthem about what this refuses to do.
🤖 Generated with Claude Code