Skip to content
All guides

Inventory · Updated September 2, 2026

Stocktakes

How a count is opened, walked blind, submitted and decided, and why the person who typed the figures can never be the one who accepts them.


What a stocktake is here

A stocktake is a document (its code looks like CNT-000012) that says what one person found on one shelf at one moment. It's the only evidence the Variance report has that doesn't come out of the ledger itself, so the whole flow is built around keeping it independent: nothing tells the counter what the system expects, and nothing moves in the ledger until somebody other than the counter says the figures are true.

Counts are per storage location, never per outlet. A count covering the cold room, the dry store and the main bar in one go would be a single number across three rooms, and the variance report couldn't say which of them lost the stock. So you open one sheet per room.

The Stocktakes page needs one of perform_stock_count, approve_stock_count or view_inventory. Which of those you hold decides where a row takes you: counters land on the sheet, deciders land on the review. The server guards both routes on its own, so the routing is a courtesy rather than the control.

The list and its piles

Four tabs, and the tab lives in the URL, so "the counts waiting for a decision" is a link you can send:

  • Open: sheets being counted right now, oldest first. A sheet opened on Monday and still open on Friday is a count going stale in everyone's memory.
  • Awaiting decision: counting finished, expected figures computed, waiting for a second person. Also oldest first.
  • Closed: approved, rejected or cancelled.
  • All.

Each row shows the count's code, its location and outlet, its status, when counting finished (a dash while the sheet is still open, because that moment hasn't happened yet) and when it was opened. Export downloads the tab you're on. There is deliberately no "blind" column in that file; more on that below.

If any location has a counting rhythm, a Due to be counted card sits above the tabs listing the ones overdue, due today or never counted. Holders of perform_stock_count also get a Start the due count button (or Start all N due counts) that opens a sheet for each in one go, skipping any location that already has one open.

Opening a sheet

Open a count asks for an outlet, a location and an optional note ("Month-end count" is the placeholder), then takes you to the sheet. The server seeds it with every item that location has ever held, read from movement history rather than from current balances, so the list of lines tells you what the room stocks and nothing about how much. A location that has never held stock seeds empty, and the page says so.

Every seeded line starts unrecorded: an empty box, not the ledger figure, and it stays unrecorded until somebody types something. Pre-filling with what the system believes would reduce counting to pressing Next.

One rule bites here. There is one open sheet per location, and trying to open a second gets the open one's code back: "CNT-000012 is still open at this location. Continue that sheet, or cancel it". A submitted sheet doesn't block a new one; a slow approver must never freeze counting, and the fresher sheet simply becomes the better evidence.

Counting

The sheet lists Item, Unit and Found on the shelf. Type what is physically there, including 0 for an empty shelf. Zero is an answer. A blank is somebody who hasn't reached that shelf yet, and submit refuses a sheet with blanks on it: "These items have not been counted yet", followed by their names.

How saving works:

  • Save figures sends only the lines you touched. Recording a figure stamps your name on that line, and that set of names is what the approval check runs on later, so the page is careful never to stamp you onto figures you didn't count.
  • Re-typing a line replaces its recorder. "Who produced this number" means the number that's there now.
  • Negatives are refused: "A counted quantity cannot be negative. Zero means none on the shelf."
  • Finish counting stays disabled while you have unsaved figures. Submitting works on what the server has.

Spirits with an empty-bottle weight and a full-bottle weight on the item get a second box beside the quantity, labelled "open, g". Sealed bottles go in the first box; what the scale reads for the open one, glass included, goes in the second. The sheet shows a running total (something like "= 2.4985 btl") but the arithmetic that counts is the server's: observed minus empty, over full minus empty, added to the sealed count. The reading is stored next to the total, because "the scale said 525g" can be re-checked in six months and a bare 0.7642 cannot. A reading lighter than the empty bottle or heavier than a sealed one is refused rather than clamped, since either means a wrong item or wrong reference weights, and the row tells you which way it's out before you save.

Found something not on the sheet? searches the catalogue by name or SKU and adds the item with a quantity. Stock nobody expected is exactly what a count exists to surface. Items already on the sheet are shown but can't be added twice.

Loading a sheet from a file

Import a file loads a CSV into an open sheet. Download the template (bohari-count-template.csv); the columns are code, counted_quantity, observed_gross_grams and note. A row may carry a counted quantity, a scale reading, or both, and the two add: three sealed bottles plus one on the scale is the ordinary bar shelf. Check first, then commit. The check derives every weighed row with the same function the write uses, so an impossible reading shows against its row instead of failing halfway through a commit, and an item with no bottle weights is refused by name: "has no bottle weights recorded, so a scale reading cannot be interpreted".

The check also enforces: a row with neither column filled is an error, not a zero; the same code twice in one file is a contradiction and both rows are rejected; an unknown code is refused by name. Files cap at 2MB and 5,000 rows. Every accepted row goes through the same recording path a typed line does, so your name lands on those lines and the blind stamp is judged the same way.

Submitting

Finish counting stamps that moment as when counting finished, computes each line's expected figure from the ledger as at that moment, and moves the count to Awaiting decision. From the browser the moment is now. Nothing about the expected figures existed anywhere before this point, which is why the review page refuses an open count: "This count is still open. Expected quantities are not computed until it is submitted."

Once submitted, the sheet is read-only. You can't unsubmit it. A reviewer can send lines back.

Reviewing and deciding

A holder of approve_stock_count opens the review. It shows when counting finished, how many lines disagree, and one of two stamps:

  • Blind count: nobody who recorded a figure could see what the system expected.
  • Sighted count: a recorder held a grant that can reach stock figures. Weigh agreement accordingly.

The stamp is computed by the server, never sent by a client, and it only ever narrows: a count starts blind, and any recorder or submitter holding a grant that can reach a quantity (view_inventory, the procurement grants, the report grants, manage_files, and more) takes it to sighted for good. The seeded Stock-taker role holds perform_stock_count and nothing that can reach a quantity, so its counts are blind. A Storekeeper or a Bar Manager also holds view_inventory, because receiving needs balances, so their counts are labelled sighted rather than forbidden. Counters never see the stamp anywhere.

The table lists Counted, Ledger expected and Variance, biggest disagreement first, with the variance written as "6 short" or "2 over" rather than a bare sign. Lines counted more than once carry an "attempt 2" chip; that history is kept on purpose, because "counted three times until it agreed" is a pattern worth seeing.

A line may also carry a note about open custody: "3 in custody when this was counted: 3 with Amina (VIP 3) on BSO-000045". Those are sealed bottles that were off the shelf in somebody's hands at the moment it was walked, from Bottle Service. It appears on the review and never on the counting sheet, because the sheet carries no expected figure of any kind. The expected quantity is not adjusted for it either: the bottle is still the bar's stock and the ledger is right about it. What custody changes is what the variance means, since "three short" and "three short, and three were on table 5" are different findings.

Three actions:

  1. Approve posts one count_variance movement per disagreeing line, dated when counting finished and signed to bring the ledger to what was counted. Lines that agree post nothing. The expected figure is re-read at approval, so a phone that synced a backdated issue between submit and approval can't leave the balance somewhere nobody counted. The toast tells you how many adjustments posted.
  2. Reject posts nothing at all and needs a reason: "A rejected count must say what was wrong with it."
  3. Send back for recount: tick lines and send them back. Those lines lose their figure and their recorder so the second attempt is as blind as the first, every other line loses its expected figure too, and the count returns to Open.

Whoever recorded a figure, and whoever pressed Finish, can't decide: "You produced the figures on this count, so somebody else has to accept it. A count that its own counter can approve is not evidence of anything." The person who merely opened the sheet may, since opening records scope and nothing else. See Roles & Permissions.

An approved count whose posted variance value reaches the Alert threshold (KES) on the Inventory Policy tab of Company settings (KES 10,000 by default) alerts everyone who can read reports.

Abandoning a sheet

Abandon sheet is the escape hatch the one-open-sheet rule needs. It posts nothing and deletes no figure; whatever was recorded stays as evidence of the walk, and the location is freed for a new count. The opener can abandon their own sheet, and a holder of approve_stock_count can abandon anybody's; anyone else gets "Only the person who opened this sheet, or somebody who may decide counts, can cancel it." Only open sheets qualify. A submitted count's way out is its reviewer's rejection, with a reason on record.

Rhythms

Holders of manage_outlets see a Rhythms button. One rhythm per storage location: daily, weekly on a named day, or monthly on day 1 to 28 (29 to 31 don't exist in every month). Whether a location is due is worked out on every read from its last approved count, so nothing is stored that can go stale. A rhythm creates no counts and sends no alerts. A missed one shows overdue, and nothing more.

On the phone

The mobile Stocktake screen splits the job in two on purpose: opening a sheet needs signal, counting and finishing don't. That split is what lets the server bound the finish time to the sheet's own lifetime. Once a sheet is open, every keystroke is saved on the phone, "Leave this sheet" keeps the draft, and Finish counting queues the figures and the submit together as one outbox row stamped with the moment you pressed it, not the moment it syncs. The row replays safely, so a dropped connection never counts a shelf twice. A finish time earlier than the sheet's opening or later than now (with five minutes of clock slack either way) is refused: "A count cannot have finished in the future. Check the device clock."

Right after Finish, holders of manage_shifts at a company with the Bar Suite get one more panel: the bar shift action that count implies. Start a shift where the bar has none, or Hand over where one is open, with the next person's name as a chip. It is one tap with no navigation, because the count is already known. The panel needs a connection, and it is a shortcut rather than the only door: the Bar Shifts screen is there when the app has reloaded, which at 2am it will have.

The phone's Approvals hub has a Stock counts pile for approve_stock_count holders, with the review and both decisions.

FAQ

Why can't I see what the system thinks is on the shelf while I count? Because then you'd be confirming, not counting, and the agreement you produce is exactly what the Variance report is supposed to test. The figure isn't hidden from the sheet; it doesn't exist in the record until you submit.

The count says 40 crates of Tusker and the ledger said 46. What happens on approval? One count_variance movement of 6 short posts to that location, dated when counting finished, and Stock on Hand reads 40. The count is the authority. If the shortfall matters, the variance report is where it shows up as unexplained.

I opened the count. Can I approve it? Yes, provided you typed no figures and didn't press Finish. Opening records scope only. That's what lets an admin open a session for an external stock-taker at a site with a single approver.

Somebody left a sheet open at the cold room last week and now I can't start one. Continue that sheet or abandon it. Abandoning posts nothing and keeps whatever was recorded. If it isn't yours, anyone holding approve_stock_count can abandon it for you.

A line came back for recount. Do I lose the rest of my figures? No. Only the lines the reviewer named are cleared, and each of them shows an attempt number so the history stays visible. Re-enter those, save, and finish again.