Skip to content

Roles and permissions

A member holds exactly one role per workspace. Roles and permissions are defined in one place in the code (App\Enums\Permission and WorkspaceUser::hasPermission()) and a matrix test must change with them. The UI never re-derives role logic; it reads a shared capability map.

Capability Viewer Staff Manager Admin Owner
View orders, products, inventory yes yes yes yes yes
Create orders and advance their status forward yes yes yes yes
Record offline payments (orders.payments, ADR-014) yes yes yes yes
Edit, cancel and delete orders; reverse a payment entry; issue a new status link yes yes yes
Create, edit and delete products yes yes yes
Manage inventory (suppliers, supplies, batches) yes yes yes
See purchase cost, margins and procurement prices (ADR-001) yes yes yes
See Finance and export it (finance.view, ADR-018) yes yes yes
Record, correct and remove operating expenses (finance.expenses) yes yes yes
Use AI generation yes yes yes
Invite users yes yes yes
Manage users and remove users yes yes
Edit workspace settings and provider credentials yes yes
Delete or transfer the workspace (ADR-003) yes

Additional protections by role:

  • Viewers receive masked customer phone numbers and email addresses.
  • Staff see only forward status options on an order.
  • Cross-tenant records return 404; actions a role lacks return 403.

Settings → Team (ADR-021) is where the roles are given:

  • managers and above invite by email;
  • admins and the owner also change roles and remove members.

Everyone acts only on roles below their own: the owner can make admins, admins can make managers, and managers can add staff and viewers. Nobody changes the owner or themselves. Invitation links are single-use, expire after seven days, and join only the invited address.

  1. IdentifyTenant resolves the workspace from the host before route binding.
  2. Route model binding is tenant-scoped, so a foreign identifier never resolves.
  3. Controllers call authorize(); form requests call can(); non-resource actions use a gate named after the permission value.
  4. Policies check tenant alignment first, then the role.
  5. SetWorkspaceContext shares a can map with the React front end for showing and hiding controls. It is a convenience, not a security boundary.