Roles, permissions & data scope
The eight roles, the module × action permission matrix, delegate and reseller data scopes, least privilege and server-side enforcement on every operation.
This area defines who may do what, and on which records (cahier §3, §3.1, §4.7, §10, §12). Permissions are module × action keys attached to roles; data scopes limit delegates to their zones and assigned resellers and resellers to their own data. The server's access module enforces both on every operation of every client; the back-office edits the matrix (BO-62); every app hides what the current user cannot do. The authoritative cell-by-cell default matrix is the permissions matrix; this page states the rules that matrix must respect.
| ID | Requirement | Release | Priority | Source | Apps |
|---|---|---|---|---|---|
ACL-01 | Eight roles: super admin, commercial admin, stock, finance, delivery, direction, delegate, reseller | V1 | MUST | §3 | API · BO |
ACL-02 | Permissions per module and action (view, create, update, delete, validate, export, administer), editable matrix | V1 | MUST | §3.1 | API · BO |
ACL-03 | Data scope: delegates see only their zones, tours, assigned resellers and own performance; resellers only their own data | V1 | MUST | §3.1 | API |
ACL-04 | Least privilege by default; financial data (purchase prices, margins, credit) strictly limited | V1 | MUST | §3.1, §4.7 | API · BO |
ACL-05 | Every API operation enforces permission and scope server-side; UI hides what the user cannot do | V1 | MUST | §10, §12 | API · BO · RA · RW · DA |
Requirements
ACL-01 — Eight system roles
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §3 | API · BO | SRV-06 |
Statement — The system shall provide eight system roles — super administrator, commercial administrator, stock manager, finance manager, delivery manager, direction, delegate and reseller — each with a default permission set.
Rules
-
Role keys (
RoleKey) are seeded by the first migration (SRV-02) and are permanent: they cannot be deleted or re-keyed (409 ROLE_PROTECTED). Their French labels come from the enum. -
A staff user holds one or more staff roles; the effective permissions are the union of their roles.
DELEGATEandRESELLERare exclusive and follow the user type: a delegate user has exactlyDELEGATE, a reseller user exactlyRESELLER, and neither can receive a staff role (422 ROLE_INCOMPATIBLE). -
Default intent of each role (cell-level defaults in the permissions matrix):
Role Default permissions SUPER_ADMINEvery permission of every module, including roles.*,users.administer,settings.administerandaudit.*. Cannot be reduced.COMMERCIAL_ADMINdashboard.view;crm.*;field.*;orders.view/create/update/validate;promotions.*;communication.*;support.*;reports.view/export;catalog.view. Nopricing.*(purchase prices, margins) and nofinance.*by default.STOCK_MANAGERdashboard.view;catalog.*;stock.*;orders.view;reports.view/export. No purchase prices (pricing.view).FINANCE_MANAGERdashboard.view;finance.*;pricing.*(the three selling prices, purchase prices, margins);orders.view/validate(credit decisions);crm.view;catalog.view;reports.view/export.DELIVERY_MANAGERdashboard.view;delivery.*;orders.view;stock.view;reports.view.DIRECTIONviewandexporton dashboard, reports, crm, orders, catalog, pricing, stock, finance, field and promotions; no create, update, delete or validate.DELEGATENo back-office access. The keys of this row (listed in the matrix) are always applied inside the delegatescope (ACL-03).RESELLERNo back-office permission. Reseller operations are governed by the selfscope. -
Roles are linked to users through
UserRole. A change applies to the user's next request (permission cache at most 60 seconds) and is logged asPERMISSION_CHANGE. -
Custom roles beyond these eight are not part of V1; HOCO adjusts the permissions of the existing roles instead (ACL-02).
| Value | UI label (fr) | Meaning |
|---|---|---|
SUPER_ADMIN | Super administrateur | Full access, global settings, roles, permissions and audit. |
COMMERCIAL_ADMIN | Administrateur commercial | CRM, resellers, delegates, tours, promotions, orders and reporting. |
STOCK_MANAGER | Responsable stock | Products, categories, variants, quantities, inventories and alerts. |
FINANCE_MANAGER | Responsable finance | Payments, credit, invoices, due dates and financial reports. |
DELIVERY_MANAGER | Responsable livraison | Preparation, shipping, carrier assignment and proof of delivery. |
DIRECTION | Direction | Read-only dashboards, KPIs and consolidated reports. |
DELEGATE | Délégué commercial | Tours, visits, sign-ups, invitations, assisted orders and reports — own scope only. |
RESELLER | Revendeur | Catalog, cart, orders, invoices, promotions, loyalty and support — own data only. |
Acceptance criteria
- On a fresh database the 8 roles exist with the keys above; deleting
STOCK_MANAGERreturns409 ROLE_PROTECTED. - A staff user holding
STOCK_MANAGERandFINANCE_MANAGERcan both adjust stock and record a payment. - Giving
COMMERCIAL_ADMINto a delegate user returns422 ROLE_INCOMPATIBLE. - A
DIRECTIONuser gets403 PERMISSION_DENIEDonadmin.orders.validateand can request an export of the orders report. me.getreturns the user's role keys and the effective permission keys.
Data — Role, Permission, RolePermission, UserRole, User, AuditLog.
Interfaces
- Screens: none dedicated
- API: no dedicated operation
Notes / open points — The default role contents are a proposal derived from cahier §3; HOCO validates them during cadrage (PRJ-03).
ACL-02 — Permission matrix by module and action
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §3.1 | API · BO | SRV-06 |
Statement — The system shall express permissions as module × action keys and let a super administrator edit the permission set of each staff role in a matrix, within guard rails that prevent lock-out and privilege escalation.
Rules
- Keys are
module.actionwith modulesdashboard,crm,catalog,pricing,stock,orders,delivery,finance,promotions,loyalty,field,support,communication,reports,users,roles,audit,settingsand actionsview,create,update,delete,validate,export,administer. The catalogue (admin.permissions.list) lists only the pairs that some operation checks, each with a French description — e.g.stock.validate: « Valider un inventaire ou autoriser une commande au-delà du stock ». - Every non-
viewaction impliesviewon the same module (a user who validates orders can see them); otherwise no key implies another —orders.administerdoes not grantorders.validate. - The matrix (BO-62) shows modules as rows and actions as columns for one role at a time; saving calls
admin.roles.updatePermissions, which replaces the role's set atomically. - Guard rails: the
SUPER_ADMINset cannot be changed, and theDELEGATEandRESELLERrows are not editable (409 ROLE_PROTECTED);roles.administerandusers.administercan be granted toSUPER_ADMINonly (422 PERMISSION_NOT_GRANTABLE); at least oneACTIVEuser must holdSUPER_ADMIN(409 LAST_SUPER_ADMINwhen removing the role from, suspending or disabling the last one). - Editing needs
roles.administerand a fresh session (AUTH-01 R8). Each save writesAuditLogPERMISSION_CHANGEwith the added and removed keys. - A change applies to the next request of every holder of the role; open SSE streams re-evaluate permissions on every event they emit.
- « Rétablir les valeurs par défaut » restores the seeded set of a role (logged like any change).
Acceptance criteria
- Removing
orders.validatefromCOMMERCIAL_ADMINmakesadmin.orders.validatereturn403 PERMISSION_DENIEDfor a commercial admin on the next request, and the activity log shows the removed key. - Saving a set that contains an unknown key such as
stock.adjustreturns400 VALIDATION_FAILED. - Granting
orders.validateautomatically includesorders.viewin the saved set. - Editing the
SUPER_ADMINorDELEGATErow returns409 ROLE_PROTECTED; grantingroles.administertoDIRECTIONreturns422 PERMISSION_NOT_GRANTABLE. - Suspending the only active super administrator returns
409 LAST_SUPER_ADMIN.
Data — Permission, RolePermission, Role, AuditLog.
Interfaces
- Screens: BO-62 Roles & permissions matrix
- API:
me.getGET /me·admin.roles.updatePermissionsPUT /admin/roles/{roleKey}/permissions·admin.permissions.listGET /admin/permissions
ACL-03 — Data scope for delegates and resellers
Statement — The system shall limit delegates to their zones, their tours, the resellers assigned to them and their own performance, and resellers to their own data, on every read and every write.
Rules
- Reseller scope (
self). The reseller id is taken from the session (User→ itsReseller), never from the request. Everyselfoperation filters on it; an id belonging to another reseller answers404 NOT_FOUND. - Delegate scope (
delegate). A reseller is in delegate D's scope when: (a) its current assignment is D; or (b) its commune belongs to one of D's zones (cahier §3.1 « leur zone »: D sees the points of sale of their zone and can prospect without creating duplicates); or (c) it is a stop of a tour of D that is in progress or dated today or later (D covers a colleague). - Records attached to a reseller (orders, visit history, photos, notes) follow the reseller's scope, with three exceptions: D sees only their own follow-up tasks; positions and trails of other delegates are never visible; financial amounts follow ACL-04.
- Delegate-owned records — tours, visits, location pings, objectives, performance, invitations, assisted orders — are visible to their delegate only.
- Reads of a record outside the scope answer
404 NOT_FOUND, so existence is not leaked; writes that reference an out-of-scope record (an assisted order, a note, an unplanned visit for a reseller outside the scope) answer403 OUT_OF_SCOPE. - Scope is evaluated at request time, so reassignment applies at once. Data already on a delegate's phone is cleaned by the next
sync.delta, which sends removals for records that left the scope; queued offline mutations on such records are rejected item by item withOUT_OF_SCOPEand shown in the sync centre (DA-27). - Staff have no row-level restriction in V1: they see every record of the modules they hold
viewon. - Scopes are implemented as reusable query filters in the data layer (SRV-06), never by filtering results after the query, so totals, counters and pagination are correct.
Acceptance criteria
- Reseller A calling
orders.getwith an order id of reseller B gets404 NOT_FOUND. - Delegate D's
delegate.resellers.listreturns the resellers assigned to D, every point of sale of D's communes and the stops of D's upcoming tours — and no reseller of a commune outside D's zones unless D has it on a tour. - D creating an assisted order for a reseller of another zone, not on D's tours, gets
403 OUT_OF_SCOPE. - After a reseller of another zone that was assigned to D is reassigned to E, D's next
sync.deltaremoves it from the phone, and a note D queued offline on it comes backREJECTEDwithOUT_OF_SCOPE. orders.summaryfor a reseller counts only that reseller's orders.- D requesting another delegate's tour gets
404 NOT_FOUND;admin.tours.trackis not reachable with a delegate session.
Data — Reseller, ResellerAssignment, DelegateZone, ZoneArea, Tour, Visit, Order, CrmTask, LocationPing.
Interfaces
- Screens: DA-13 Prospects & resellers (list / map) · DA-18 Assisted orders & orders of my resellers
- API:
me.getGET /me·delegate.resellers.listGET /delegate/resellers·delegate.orders.listGET /delegate/orders
Notes / open points — Restricting a commercial administrator to a region is not in the cahier and not in V1; it can be added later as a staff scope (see the decision register).
ACL-04 — Least privilege and restricted financial data
Statement — The system shall grant nothing by default and shall expose purchase prices, margins, credit and other financial data only to users holding the corresponding finance permission.
Rules
-
Deny by default: a staff user without a role can only sign in and call
me.get; a permission added to the catalogue is granted toSUPER_ADMINonly; a back-office operation without a declared permission is unreachable — a contract test fails the build if one exists. -
Financial data and the keys that open it:
Data Visible with Changed with Purchase price (prix d'achat) of a variant, unit cost of a stock receipt, price history pricing.viewpricing.updateMargins and margin rates of products and order lines (computed) pricing.view— The three selling prices of a variant (super-gros, demi-gros, détail) catalog.viewpricing.updateInvoices, payments and declarations of all resellers finance.viewfinance.create,finance.update,finance.validateCredit limit, used and available credit, blocking finance.view(andorders.validateinside an order's credit control)finance.administer -
Fields the caller may not see are omitted from the response — absent, not
null— in lists, details, exports, global search and SSE payloads; the contract declares them optional. -
Delegates never receive purchase prices, margins, credit amounts or invoices; in assisted orders they see only the price of the reseller's customer type. For a reseller in their scope they receive one boolean,
financialAlert(an invoice is overdue or the credit is blocked), so they can raise it during a visit. -
Resellers receive only the price of their own customer type — never the two other prices — and their own invoices, payments and credit (PRC-05, FIN-10). Guests receive no price at all (AUTH-10).
-
Exports re-check permissions when the file is generated: if the requester lost
finance.viewin the meantime, the financial columns are left out. -
Error details follow the same rule: credit amounts in an order's controls go to holders of
orders.validateorfinance.view, others see only the control's status; the reseller receives the amounts of their own credit inCREDIT_LIMIT_EXCEEDED.
Acceptance criteria
admin.products.getreturnspurchasePriceto a finance manager and omits the field for a stock manager; both see the three selling prices.- An XLSX export of products requested by a commercial administrator has no purchase-price or margin column.
delegate.resellers.getfor a reseller with an overdue invoice returnsfinancialAlert: trueand no amount.admin.orders.getreturnspurchasePriceandmarginHton order lines only to holders ofpricing.view; on a credit order over the limit, a delivery manager (noorders.validate, nofinance.view) sees the credit control status without figures.- A new staff user without roles gets
403 PERMISSION_DENIEDon everyadmin.*operation.
Data — ProductVariant (three selling prices, purchase price), CreditAccount, Invoice, Payment, RolePermission.
Interfaces
- Screens: none dedicated
- API:
admin.products.getGET /admin/products/{productId}
ACL-05 — Server-side enforcement, hidden actions in the UI
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §10, §12 | API · BO · RA · RW · DA | SRV-06 · DSH-01 · SRV-28 |
Statement — The system shall check version, authentication, account status, permission and scope on the server for every API operation, and every client shall hide the actions the current user is not allowed to perform.
Rules
- Every request goes through the same pipeline, in this order: (1) mobile version policy —
426 UPGRADE_REQUIRED; (2) authentication —401 UNAUTHENTICATED; (3) account status —403 ACCOUNT_SUSPENDEDor403 ACCOUNT_PENDING_VALIDATION; (4) the operation's permission or scope type —403 PERMISSION_DENIED; (5) input validation —400 VALIDATION_FAILED; (6) record lookup within scope —404 NOT_FOUNDor403 OUT_OF_SCOPE; (7) business rules —409or422. Permission comes before validation so that a caller without access learns nothing about the input. - Each contract procedure declares its access —
public,self,delegate, a permission key, orany(any signed-in staff user, used by global search and the event stream) — and a single middleware enforces the declaration. State-dependent rules (cancellation by status, visit lock) live in the domain services, not in the clients. - Detail responses for orders, resellers, tours, visits, invoices and promotions include
allowedActions— the actions the caller may perform now (e.g.["VALIDATE", "REFUSE", "MODIFY", "CANCEL"]) — computed by the same code as the operations. Clients render their buttons from it rather than re-implementing the rules. - Clients hide navigation entries and buttons for which the user lacks the permission. When the user has the permission but the record's state forbids the action, the button stays visible but disabled with the reason (« Commande déjà validée : contactez l'assistance pour l'annuler »).
- Opening a back-office URL the user cannot access shows « Accès refusé » (403 page) and never partial data.
- Global search (
admin.search) returns only the entity types and records the caller can view; the SSE stream filters each event by the recipient's permissions and scope. - Denied calls are written to the application log with user, operation and request id (NFR-08).
- An automated permission sweep (SRV-28) calls every operation with a user of each role and compares allow or deny with the matrix; it runs in CI and blocks the merge on a mismatch.
Acceptance criteria
- A suspended user with an outdated app version gets
426first, then403 ACCOUNT_SUSPENDEDafter updating. - A delivery manager sending an invalid body to
admin.promotions.crudgets403 PERMISSION_DENIED, not400. admin.orders.geton aSUBMITTEDorder returnsallowedActionscontainingVALIDATEfor a commercial administrator and not for a delivery manager.- A stock manager does not see the Finance menu, and typing
/finance/invoicesshows the 403 page. admin.search?q=REV-000123for a stock manager returns no CRM result.- The permission sweep reports zero mismatches on the default matrix.
Data — Permission, RolePermission, UserRole, Session; application logs.
Interfaces
- Screens: BO-03 App shell: navigation by role, global search, notifications
- API:
admin.searchGET /admin/search
Error codes
| Code | HTTP | Raised when |
|---|---|---|
PERMISSION_DENIED | 403 | The user's roles lack the operation's permission |
OUT_OF_SCOPE | 403 | A write references a record outside the delegate's or reseller's scope |
NOT_FOUND | 404 | The record does not exist or is outside the reader's scope |
ROLE_PROTECTED | 409 | Deleting a system role, or editing SUPER_ADMIN, DELEGATE or RESELLER permissions |
LAST_SUPER_ADMIN | 409 | The change would leave no active super administrator |
ROLE_INCOMPATIBLE | 422 | A staff role given to a delegate or reseller user (or the reverse) |
PERMISSION_NOT_GRANTABLE | 422 | roles.administer or users.administer granted to a role other than SUPER_ADMIN |
Authentication & accounts
Sign-in for staff, delegates and resellers, guest browsing, reseller sign-up with a declared customer type, account claim, one-time codes, sessions, account statuses, devices and app versions.
Audit trail & history
The append-only activity journal of sensitive actions, the business histories kept on orders, stock, prices and resellers, and the back-office viewer.