Cross-cutting · Role & module permission layer

Access control is the product of two independent dimensions — the user’s staff level and the centre’s enabled modules — expressed as 55 ember-can ability classes that gate menu entries, route entry and individual actions.

AreaCross-cutting
Routes(every route)
Centre modulesalways available
Staff levelsroot · admin · manager · scmanager · storeManager · commerce · crmmanager · crmoperator · weboperator · content_creator · calibrator · owner · external · sales · services
Modelsmodules · staff-member
UI label(abilities)
Coverage5 use cases · 2 happy · 5 failure

Known issues (1)

  • 🟠 Medium — [[R07 · campaign canDelete compares a role to a status|campaign#canDelete compares a role to a status]]

How it works

The pattern is consistent: a module-* ability holds a moduleName plus a roles array and computes canAccess as ‘role is allowed AND centre has the module’; entity abilities extend the relevant module ability and add canCreate/canEdit/canDelete/etc. on top, often also conditioned on record state (campaign status, proposal status, receipt state, month state). This is why permissions in this product are genuinely three-dimensional: role × module × record state. The navbar applies the same logic to sections (section.levels, section.module, section.disabledByModule, or section.ability). Worth knowing: a role that is not in enums/staff-levels yields an empty menu rather than an error; some abilities contain latent logic bugs (the campaign#canDelete check compares the CONTENT_CREATOR role constant against model.status); and intranet-file accepts an owner role value in its inclusion rule that the UI never offers.

Use cases

Why someone opens this (5)

  • Giving a store-level user access only to their own sales and documents.
  • Letting a content creator draft proposals but requiring a manager to publish them.
  • Selling modules per centre so one tenant has loyalty and another does not.
  • Protecting closed activities and validated months from further edits.
  • Restricting the CLI to root and imports to root/admin.

Path map

flowchart LR
    F(["Role and module permission layer"]):::feat
    F --> H1["Menu and actions reflect role and modules"]:::ok
    F --> H2["Record state tightens permission"]:::ok
    F -.-> X1["Module disabled"]:::bad
    F -.-> X2["Role not allowed"]:::bad
    F -.-> X3["Unrecognised role"]:::bad
    F -.-> X4["Centre not loaded"]:::bad
    F -.-> X5["Latent ability logic bug"]:::bad
    classDef feat fill:#8c5a3c,stroke:#8c5a3c,color:#ffffff,font-weight:bold
    classDef ok fill:#e3f1ea,stroke:#0d6b42,color:#0d4a2e
    classDef bad fill:#f7e6e2,stroke:#9a2d1c,color:#6e1f13

Happy paths

Menu and actions reflect role and modules

  1. User signs in; auth.load() resolves their role and the centre’s modules.
  2. nf-navbar#menu filters sections by level, module and ability.
  3. Each list screen renders only the row actions whose abilities pass.
  4. Direct URL entry to a blocked route is rejected by the route’s own can check.

Record state tightens permission

  1. An activity is marked finished.
  2. campaign#canEdit/canDelete now return false for non-root/admin roles.
  3. canShow becomes true, so the report opens instead.

Failure paths

QA checklist

Tick these off while regression-testing this feature. The boxes are plain Markdown, so progress is visible in git diffs.

  • ✅ Menu and actions reflect role and modules
  • ✅ Record state tightens permission
  • ⛔ Module disabled
  • ⛔ Role not allowed
  • ⛔ Unrecognised role
  • ⛔ Centre not loaded
  • ⛔ Latent ability logic bug

Implementation


Generated from doc/feature-inventory.json · id xc-permissions · commit 276788ca6