Dentolize · Stock Counts Walkthrough
On this pageBusiness viewTechnical view

Permissions & Visibility

Business view

Stock counts introduce four new permissions, mirroring the pattern used everywhere else in Dentolize: View, Add, Edit, Delete. What's new is a fifth, narrower permission — View Created Stock Count — that lets a clinic hand counting duty to staff without giving them visibility into every count anyone has ever opened.

A clinic can, for example, give a receptionist or assistant Edit Stock Count + View Created Stock Count (but not the plain View Stock Count): that person can open and work counts, but when they look at the stock counts list, they only see the ones they created — not the manager's counts, not other staff's counts. It's the same pattern used elsewhere for personal-scope visibility (e.g. "view created expenses").

Out of the box, only the seeded Inventory Admin permission group gets stock count access (all four base permissions). Everyone else needs it granted explicitly, same as any other module.

Technical view

The permissions

Defined in packages/server/src/enums.graphql:540 and around it: VIEW_STOCK_COUNT, VIEW_CREATED_STOCK_COUNT, ADD_STOCK_COUNT, EDIT_STOCK_COUNT, DELETE_STOCK_COUNT. They're surfaced in the permission-group editor UI via packages/clinic-web/src/components/dashboard/settings/Groups/tableData.js:443.

Seeded default: only the Inventory Admin group gets VIEW_STOCK_COUNT / ADD_STOCK_COUNT / EDIT_STOCK_COUNT / DELETE_STOCK_COUNT (packages/server/src/seedDatabase/permissionGroups.js:950-953). VIEW_CREATED_STOCK_COUNT isn't assigned to any seeded group — it exists for clinics to build their own narrower "counter" role.

Query-time gating

Every stock count query (stockCounts, totalStockCounts, stockCountDetails, stockCountItems, totalStockCountItems, stockCountItemBySku) is permitted by or(hasPermission('VIEW_STOCK_COUNT'), hasPermission('VIEW_CREATED_STOCK_COUNT')) (packages/server/src/permissions/permissions.js:2445-2470) — either permission is enough to query, but which permission you hold changes what you see, enforced in two separate places:

  1. List filteringgetWhereForStockCounts (packages/server/src/utils/helpers.js:3566-3577) adds createdById: request.session.user.id to the where clause's AND array whenever the caller has VIEW_CREATED_STOCK_COUNT, alongside whatever other filters (date range, storage, branch, search) are active. This scopes the stockCounts list and its count query.
  2. Direct-access checksisSameCompanyAsStockCount (packages/server/src/permissions/rules.js:3015-3036), the rule guarding every mutation and detail query that takes a stockCount id argument, does the same scoping again independently: if the caller has VIEW_CREATED_STOCK_COUNT, the rule additionally requires item.createdById === request.session.user.id. This matters because list filtering alone wouldn't stop someone from guessing or reusing another count's id directly against stockCountDetails or a mutation — the rule closes that gap at the object level, not just the list level.

Mutation permissions

packages/server/src/permissions/permissions.js:4068-4072:

addNewStockCount:    isAuthenticated + hasGeneratedID + ADD_STOCK_COUNT
editStockCountItems: isAuthenticated + isSameCompanyAsStockCount + EDIT_STOCK_COUNT
confirmStockCount:   isAuthenticated + isSameCompanyAsStockCount + EDIT_STOCK_COUNT
cancelStockCount:    isAuthenticated + isSameCompanyAsStockCount + EDIT_STOCK_COUNT
deleteStockCount:    isAuthenticated + isSameCompanyAsStockCount + DELETE_STOCK_COUNT

Confirming and cancelling both only require EDIT_STOCK_COUNT — there's no separate "confirm" permission. The one place a second, unrelated permission gets checked is inside confirmStockCount itself: requesting updateStock: true additionally requires DO_ALL or ADD_INVENTORY_ADJUSTMENT (checked in the resolver, not the permission shield — see Closing a Count). That's a deliberate split: the ability to count and the ability to let a count change live stock are different privileges, and a clinic can grant one without the other.

Frontend mirroring

Both clients derive simple booleans from the raw permission list for the UI to gate buttons on — stockCounts, addStockCount, editStockCount, deleteStockCount — in packages/clinic-mobile/src/shared/utils/getUserPermissions.js:43-46 (shared by both web and mobile, since clinic-web imports mobile's store code). These are UI conveniences only; the server-side rules above are the actual enforcement.

What's returned either way

Regardless of which view-permission a caller holds, the shape of the data is the same — stockCountSelect / stockCountItemSelect (packages/server/src/resolvers/queries/actions/stockCounts/stockCountsSelect.js) don't vary by permission. There's no field-level redaction; the distinction is entirely about which rows a caller can reach, not which columns.