Closing a Count & Stock Adjustments
Business view
An open count ends one of three ways:
- Confirmed — the normal path. The count is locked in as the historical record of what was found, the storages it held are released, and the clinic chooses whether the system should also update live stock to match what was counted.
- Cancelled — the count is abandoned. Nothing about stock changes; the storages it held are simply released. Used when a count was opened by mistake, or interrupted and not worth finishing.
- Deleted — only possible while still open. Removes the count (and its lines) entirely, as if it never happened. Once a count is confirmed, it becomes a permanent record and can no longer be deleted — it's evidence of what stock looked like on a given day, and of the adjustment it may have triggered.
Confirming is the only moment that can change real inventory, and the app makes that an explicit choice rather than a side effect: Confirm without changing stock records the count and leaves live stock exactly as it was, while Confirm and update stock additionally books a proper stock adjustment for every line that came out different from what was expected. Lines nobody got around to counting are left alone either way — an uncounted shelf isn't treated as evidence that stock went to zero, it's just missing information.
When stock is updated, it isn't a silent database edit — the system creates a normal, visible inventory order (of type "Adjustment") carrying the count's number in its details, so the change shows up in the same Inventory Orders list as every purchase, sale, or transfer, with the same audit trail.
Technical view
Cancel
cancelStockCount (packages/server/src/resolvers/mutations/actions/stockCounts/cancelStockCount.js) is the simplest of the three: verify the count belongs to the caller's company and is still OPEN, release its storage locks (releaseStoragesForStockCount), and set status: 'CANCELED' with confirmedAt/confirmedById stamped (reusing the same fields a confirm would use, so "when/by whom this count stopped being open" has one place to look regardless of which way it ended).
Delete
deleteStockCount (packages/server/src/resolvers/mutations/actions/stockCounts/deleteStockCount.js) is gated the same way (status !== 'OPEN' → Stock Count Is Closed[Client Error]), releases locks, and hard-deletes the StockCount row — StockCountItem rows cascade via onDelete: Cascade on the schema relation (packages/prisma/schema.prisma:4478).
Confirm
confirmStockCount (packages/server/src/resolvers/mutations/actions/stockCounts/confirmStockCount.js) takes { stockCount, updateStock }:
- Loads the count, checks company ownership and
status === 'OPEN'(confirmStockCount.js:22-33). - If
updateStockis requested, separately checks the caller hasDO_ALLorADD_INVENTORY_ADJUSTMENT— counting and adjusting stock are different permissions, so a counter without adjustment rights can still confirm a count "as counted only" (confirmStockCount.js:35-41). - Computes the variance set via
getStockCountVariances(stockCountUtils.js:81-95): every line that was counted (countedAt: { not: null }) and whose storedvarianceAmountis non-zero. Uncounted lines never enter this set. - In a transaction: releases the count's storage locks, then flips
status: 'CONFIRMED'withconfirmedAt/confirmedById(confirmStockCount.js:47-60). The locks are released before the status flip specifically so that the adjustment order booked next isn't refused by the count's own lock (code comment,confirmStockCount.js:45-46). - If
updateStockand there's at least one variance, it calls the existingcreateNewInventoryOrderresolver directly — not a copy of its logic — passingtype: 'ADJUSTMENT',status: 'COMPLETED', and one input line per variance:{ inventoryItem, inventorySubItem, fromStorage, amount: item.variance, box: false, due }(confirmStockCount.js:64-82). Reusing the real resolver means an adjustment booked from a stock count goes through exactly the same stock-movement code path (handleCompleteInventoryTransactionsininventoryUtils.js) as any other adjustment — location amounts, sub-item/item rollups, run-out-date recalculation, notifications, all of it — rather than a stock-count-specific shortcut that could drift out of sync. - The resulting order's id is stored back on the count (
stockUpdated: true,inventoryOrderId) so the UI can link straight from the count to the order it produced (confirmStockCount.js:84-88).
Where the variance actually comes from
Variance isn't computed at confirm time from scratch — it's carried on each StockCountItem the moment it's counted (varianceAmount/varianceValue, written in editStockCountItems.js:62-63 and mirrored in the Excel path, handleParseStockCount.js:102-103), and rolled up onto the parent StockCount by recalculateStockCountTotals after every edit. This is a deliberate design choice, called out in a schema comment (packages/prisma/schema.prisma:4441-4442): "kept rather than derived so a list can sort and filter on the difference" — a stockCounts list can filter on varianceAmount > 0/< 0/= 0 as a plain indexed column query, without joining or aggregating lines.
Adjustment amounts are signed, not absolute
The amount passed into the adjustment order is the raw signed variance (counted − expected). handleCompleteInventoryTransactions always uses Prisma's increment operation for type ADJUSTMENT (inventoryUtils.js:89, decrementOrIncrement = type === 'ADJUSTMENT' ? 'increment' : 'decrement') — there's no runtime branch on the amount's sign. The correct direction falls out for free because increment with a negative value is a decrement at the database level. So a count that found more stock than expected books a positive adjustment; one that found less books a negative one — both in a single order per confirm, one line per varying item.