Skip to content
Updated Sep 25, 2026 by Barča Dvořáková · Owner: analysisactivefeature Edit on GitHub

Initial Permission Sets ​

Companion pages: Users & Access · Permission Model · API · Stories breakdown

The initial Permission Sets EM3 ships with are a direct migration of EM2's roles. EM2 ran two separate role systems — client-facing roles (Role, 8 roles) and Porsenna-staff admin-panel roles (AdminRole, 3 roles) — and both become Permission Sets here, one Permission Set per legacy role, carrying that role's full permission surface forward regardless of whether an EM3 endpoint for a given legacy capability exists yet. Where EM3 already has a permission code for a legacy capability, that code is reused. Where it does not, a new code is introduced here, following EM3's existing <scope>.<topic>.<verb> naming convention, and marked (new). These (new) codes have no enforcing endpoint yet; they exist so the Permission Set's shape is complete and correct from day one, and get wired up as their endpoints land.

One legacy distinction does not exist in EM3's rule model as a separate code and is instead expressed through the restriction collection on a Permission Set assignment, not through a scope value:

  • A legacy "sees only their own X" vs. "sees all X" pair (e.g. ROLE_CAN_SEE_ALL_BUILDINGS) becomes the same code either way. The "own X" version of a role's assignment carries a building restriction listing the buildings that role's holders are individually assigned to; the "all X" version carries no building restriction at all. Every code in this document is held at all breadth — the only breadth EM3 implements — and it's the restriction collection that narrows it, not a scope value.

A second legacy distinction has no EM3 equivalent at all:

  • A legacy "last record" vs. "all records" pair (e.g. editing only the most recent reading vs. any reading) collapses to the same code. This is a real loss of granularity from EM2, called out inline below.

Legacy → EM3 code mapping ​

Grouped by legacy scope. Existing EM3 codes are the ones already declared in the codebase; (new) codes are being introduced here.

Dashboard ​

  • ROLE_CAN_SEE_DASHBOARD → (new) tenant.dashboard.read

Klienti (Porsenna-staff client/tenant administration) ​

  • ROLE_CAN_SEE_CLIENT_LIST, ROLE_CAN_SEE_CLIENT_DETAIL → platform.tenants.read
  • ROLE_CAN_EDIT_CLIENT → platform.tenants.manage
  • ROLE_CAN_SEE_ALL_CLIENTS → platform.tenants.read; the restricted-set-of-tenants version carries a clientGroup restriction (the same mechanism as Admin-manažer's own reach, below), ROLE_CAN_SEE_ALL_CLIENTS means no such restriction
  • ROLE_CAN_EXPORT_CLIENT → (new) platform.tenants.export
  • ROLE_CAN_ASSIGN_CLIENT (self-assign to a client to start working in it) → (new) platform.tenants.self-assign
  • ROLE_CAN_ASSIGN_H_M_STATIONS → (new) platform.tenants.weather-stations.assign
  • ROLE_CAN_ASSIGN_DEFAULT_H_M_STATION → (new) platform.tenants.weather-stations.assign-default
  • ROLE_CAN_AUTO_PAIR_GAUGE_READINGS → tenant.remote-endpoints.pair + tenant.remote-sources.manage

Users (this feature) ​

  • ROLE_CAN_SEE_USER_LIST, ROLE_CAN_SEE_USER_DETAIL → platform.users.read
  • ROLE_CAN_EDIT_USER, ROLE_CAN_CREATE_USER → platform.users.invite
  • ROLE_CAN_DELETE_USER → platform.users.suspend
  • ROLE_CAN_EXPORT_USER → (new) platform.users.export

Objekty (Buildings) ​

  • ROLE_CAN_SEE_BUILDING_LIST, ROLE_CAN_SEE_BUILDING_DETAIL → tenant.buildings.read
  • ROLE_CAN_SEE_ALL_BUILDINGS → tenant.buildings.read; the assigned-buildings-only version carries a building restriction listing those buildings, ROLE_CAN_SEE_ALL_BUILDINGS means no building restriction
  • ROLE_CAN_EDIT_BUILDING, ROLE_CAN_CREATE_BUILDING, and the field-group edit flags that only ever disabled individual form inputs in EM2 (EDIT_BUILDING_FIELDS_IDENT_1/2/3, EDIT_GID, EDIT_BUILDING_FIELDS_CLASSIFICATION) → tenant.buildings.write
  • EDIT_BUILDING_FIELDS_PARAMS_1/2, EDIT_BUILDING_FIELDS_ENERGY_MANAGEMENT (each gated a separate legacy controller) → (new) tenant.buildings.parameters.write; not currently granted to any legacy role
  • ROLE_CAN_DELETE_BUILDING → tenant.buildings.delete
  • ROLE_CAN_DISABLE_BUILDING, ROLE_CAN_ENABLE_BUILDING → tenant.buildings.write (status toggle, not a hard delete)
  • ROLE_CAN_SEE_BUILDING_FILES_LIST → tenant.buildings.documents.read
  • ROLE_CAN_SEE_BUILDING_PASSPORT → (new) tenant.buildings.passport.read (no EM3 "passport" view exists; confirm this legacy feature is actually in scope before wiring it up)
  • ROLE_CAN_SEE_BUILDING_USER_LIST, ROLE_CAN_DELETE_BUILDING_USER → tenant.buildings.responsible-persons.read / .write
  • ROLE_CAN_EXPORT_BUILDING → (new) tenant.buildings.export

Měřidla (Gauges) ​

  • ROLE_CAN_SEE_GAUGE_LIST, ROLE_CAN_SEE_GAUGE_DETAIL → tenant.gauges.read
  • ROLE_CAN_EDIT_GAUGE, ROLE_CAN_CREATE_GAUGE, and the field-group edit flags that only ever disabled individual form inputs in EM2 (EDIT_GAUGE_END_POINT_1/2, EDIT_GAUGE_FIELDS_IDENT_1/2, EDIT_GAUGE_CONSUMPTION_1, EDIT_GAUGE_REMOTE_READINGS, CHANGE_GAUGE_REMOTE) → tenant.gauges.write
  • EDIT_GAUGE_FIELDS_IDENT_3, EDIT_GAUGE_CONSUMPTION_2 (gated a separate legacy controller) → (new) tenant.gauges.parameters.write
  • EDIT_GAUGE_INVOICING (gated a separate legacy controller) → (new) tenant.gauges.invoicing.write
  • ROLE_CAN_DELETE_GAUGE → tenant.gauges.delete
  • ROLE_CAN_DISABLE_GAUGE, ROLE_CAN_ENABLE_GAUGE → tenant.gauges.write (status toggle)
  • ROLE_CAN_DELETE_GAUGE_PARAMS → tenant.gauges.void-coefficient (exact existing match)
  • ROLE_CAN_CHANGE_GAUGE_BUILDING → tenant.gauges.reassign-building (exact existing match)
  • ROLE_CAN_EXPORT_GAUGE → (new) tenant.gauges.export
  • ROLE_CAN_IMPORT_GAUGES (bulk gauge edit) → (new) tenant.gauges.bulk-import

Odečty (Readings) ​

  • ROLE_CAN_CREATE_GAUGE_READINGS, ROLE_CAN_EDIT_ALL_GAUGE_READINGS → tenant.readings.write
  • ROLE_CAN_EDIT_LAST_GAUGE_READINGS → (new) tenant.readings.write-last
  • ROLE_CAN_DELETE_ALL_GAUGE_READINGS → tenant.readings.delete
  • ROLE_CAN_DELETE_LAST_GAUGE_READINGS → (new) tenant.readings.delete-last
  • Both -last codes are enforced by a domain guard in the write use-case — the same pattern EM2's GaugeReadingVoter uses (a plain *_ALL_* permission checked first, a separate *_LAST_* permission combined with a live isLast() query as fallback) and the one EM3's READINGS_CODES.METER_REPLACEMENT/OVERRIDE_MONOTONICITY already use. None of the four codes above is currently granted to any legacy tenant role.
  • ROLE_CAN_CHANGE_GAUGES (meter replacement) → tenant.readings.meter-replacement (exact existing match)
  • ROLE_CAN_IMPORT_GAUGE_READINGS, ROLE_CAN_IMPORT_GAUGE_REMOTE_READINGS → tenant.readings.import
  • ROLE_CAN_DELETE_IMPORT_GAUGE_READINGS → tenant.readings.delete
  • ROLE_CAN_EXPORT_GAUGE_READINGS → (new) tenant.readings.export

Faktury (Invoices) ​

  • ROLE_CAN_CREATE_INVOICES, ROLE_CAN_EDIT_ALL_INVOICES → tenant.invoices.write
  • ROLE_CAN_EDIT_LAST_INVOICES → (new) tenant.invoices.write-last
  • ROLE_CAN_DELETE_ALL_INVOICES → tenant.invoices.delete
  • ROLE_CAN_DELETE_LAST_INVOICES → (new) tenant.invoices.delete-last
  • Both -last codes use the same domain-guard pattern as readings, above (EM2's InvoiceVoter is identical to GaugeReadingVoter); none of these four codes is currently granted to any legacy tenant role.
  • ROLE_CAN_EXPORT_INVOICES → (new) tenant.invoices.export
  • ROLE_CAN_IMPORT_INVOICES → (new) tenant.invoices.import (check against the data-imports app before building — may already exist there under a different name)
  • ROLE_CAN_DELETE_IMPORT_INVOICES → tenant.invoices.delete

Dokumenty (Documents / Files) ​

  • ROLE_CAN_SEE_FILE_LIST, ROLE_CAN_SEE_FILE_DETAIL → tenant.files.read
  • ROLE_CAN_CREATE_FILE, ROLE_CAN_EDIT_FILE → tenant.files.write
  • ROLE_CAN_CREATE_FILE_CONTEXT_CLIENT (client-level, not building-level, document) → tenant.files.write with no building restriction
  • ROLE_CAN_CREATE_COMMON_FILE, ROLE_CAN_EDIT_COMMON_FILE, ROLE_CAN_DELETE_COMMON_FILE (shared across every app user) → (new) tenant.files.common.write / (new) tenant.files.common.delete
  • ROLE_CAN_DELETE_DOCUMENT_DEPENDENCIES → (new) tenant.files.dependencies.delete

Smluvní ceny (Contracts) ​

  • ROLE_CAN_VIEW_CONTRACTS → tenant.contracts.read
  • ROLE_CAN_CREATE_CONTRACT, ROLE_CAN_EDIT_CONTRACT → tenant.contracts.write
  • ROLE_CAN_DELETE_CONTRACT → tenant.contracts.delete

Tolerance (no EM3 equivalent feature yet) ​

  • ROLE_CAN_SEE_TOLERANCE, ROLE_CAN_SEE_GAUGE_DATA_CONTROL → (new) tenant.gauge-data-control.read
  • ROLE_CAN_EDIT_TOLERANCE, ROLE_CAN_CREATE_GAUGE_DATA_CONTROL, ROLE_CAN_EDIT_GAUGE_DATA_CONTROL → (new) tenant.gauge-data-control.write
  • ROLE_CAN_DELETE_GAUGE_DATA_CONTROL → (new) tenant.gauge-data-control.delete

Výhřevnosti (Heating power / calorific values) ​

  • ROLE_CAN_EXPORT_HEATING_POWER → (new) tenant.heating-power.export

Upozornění a neshody (Notifications & nonconformities) ​

  • ROLE_CAN_EDIT_TYPE_CUSTOM_MESSAGE → (new) tenant.notifications.custom-message.write

Obecné (General — page visibility & shared filters) ​

  • ROLE_CAN_SEE_GRAPHS_PAGE → (new) tenant.graphs.read
  • ROLE_CAN_SEE_REPORTS_PAGE → (new) tenant.reports.read
  • ROLE_CAN_SEE_INPUTS_PAGE → (new) tenant.inputs.read
  • ROLE_CAN_SEE_OVERVIEW_PAGE → (new) tenant.overview.read
  • ROLE_CAN_CREATE_SHARED_FILTERS, ROLE_CAN_EDIT_SHARED_FILTERS → (new) tenant.shared-filters.write
  • ROLE_CAN_DELETE_SHARED_FILTERS → (new) tenant.shared-filters.delete

Klimadata (weather-station short-term & daily data) ​

  • ROLE_CAN_SEE_H_M_SHORT_TERM_DATA, ROLE_CAN_SEE_H_M_DAY_DATA → tenant.weather-stations.climate.read
  • ROLE_CAN_EDIT_H_M_SHORT_TERM_TEMPERATURE, ROLE_CAN_EDIT_H_M_SHORT_TERM_HEATING_DAYS, ROLE_CAN_CREATE_H_M_SHORT_TERM_DATA, ROLE_CAN_EDIT_H_M_DAY_DATA → tenant.weather-stations.climate.write
  • ROLE_CAN_EXPORT_H_M_SHORT_TERM_DATA → (new) tenant.weather-stations.climate.export

Tenant Permission Sets (from Role) ​

Eight Permission Sets, one per legacy tenant role. ROLE_ADMIN and ROLE_ADMIN_MANAGER carry cross-tenant, Porsenna-staff capabilities (client administration) even though EM2 modeled them on the same Role table as the client-side roles — that's flagged inline rather than silently moved, since whether they belong here or among the platform sets below is worth confirming with the team.

Every code below is held at all breadth. Where a role lacks the legacy ROLE_CAN_SEE_ALL_BUILDINGS flag (Manažer, Pracovník manažer, Pracovník, Host), its assignment carries a building restriction to that user's assigned buildings; where a role has the flag (Admin-manažer, Vedení organizace, Host expert), it carries none.

Administrátor (ROLE_ADMIN) ​

Every code in the mapping above (85 legacy flags, full access) — this is the direct migration target for EM3's already-seeded Správce tenanta (plný přístup) / AllAccessSet.TENANT_ADMIN. Recommendation: keep using the existing all-access sentinel for this set (empty rule list, all-access flag) rather than enumerating every code — that's what the all-access mechanism exists for, and it's already built.

Admin - manažer (ROLE_ADMIN_MANAGER) ​

  • platform.tenants.read, platform.tenants.manage, (new) platform.tenants.export, (new) platform.tenants.self-assign
  • tenant.remote-endpoints.pair, tenant.remote-sources.manage
  • platform.users.read, platform.users.invite, platform.users.suspend, (new) platform.users.export
  • tenant.buildings.read, tenant.buildings.write, tenant.buildings.delete, (new) tenant.buildings.export, tenant.buildings.responsible-persons.write
  • tenant.gauges.read, tenant.gauges.write, tenant.gauges.delete, tenant.gauges.void-coefficient, (new) tenant.gauges.parameters.write, (new) tenant.gauges.invoicing.write, (new) tenant.gauges.export
  • tenant.files.read, tenant.files.write, tenant.files.write (client-level, no building restriction)
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • tenant.readings.import
  • tenant.contracts.read, tenant.contracts.write, tenant.contracts.delete
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

Admin-manažer's EM3 assignment is a permissionSetUserAssignment row with tenantId = null — platform-scoped, the same shape as a Porsenna employee's grant, not a tenant Permission Set and not multiple per-tenant rows — carrying a required clientGroup restriction entry in its restrictions collection. The API rejects the assignment with 400 ERR_USERS_ACCESS_CLIENT_GROUP_REQUIRED if that entry is missing. clientGroup and clientGroupMembership are specified in entities/clientGroup and entities/clientGroupMembership — a named grouping of tenants via a many-to-many join, the same shape as EM2's ClientGroup (EM2's ClientVoter bounds ROLE_ADMIN_MANAGER's reach to the actor's home Client plus siblings sharing its ClientGroup; ROLE_ADMIN gets unconditional access regardless of client). building and clientGroup are the two restriction dimensions defined in Permission Model; restrictions cascade through linked resources at evaluation time. Every code above is bounded by the same clientGroup restriction — platform.tenants.read and the tenant-data codes alike.

Manažer (ROLE_MANAGER) ​

Same as Admin - manažer, minus every platform.tenants.* code — concretely:

  • platform.users.read, platform.users.invite, platform.users.suspend, (new) platform.users.export
  • tenant.buildings.read, tenant.buildings.write, (new) tenant.buildings.export, tenant.buildings.responsible-persons.write
  • tenant.gauges.read, tenant.gauges.write, tenant.gauges.delete, tenant.gauges.void-coefficient, (new) tenant.gauges.parameters.write, (new) tenant.gauges.invoicing.write, (new) tenant.gauges.export
  • tenant.files.read, tenant.files.write, tenant.files.write (client-level, no building restriction)
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • tenant.readings.import (remote variant only — legacy ROLE_MANAGER lacks plain IMPORT_GAUGE_READINGS)
  • tenant.contracts.read, tenant.contracts.write, tenant.contracts.delete
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

This set replaces EM3's currently-seeded manager set in permission-sets.seed.ts, which needs updating to match: the seed's manager grants tenant.readings.read/write, tenant.weather-stations.*, tenant.invoices.*, and tenant.organisations.* that legacy ROLE_MANAGER never had, and is missing platform.users.* and tenant.contracts.* that this migrated set includes.

Vedení organizace (ROLE_CLIENT_BOSS) ​

  • platform.users.read, (new) platform.users.export
  • tenant.buildings.read, tenant.buildings.write, (new) tenant.buildings.export
  • tenant.gauges.read, tenant.gauges.write, tenant.gauges.delete, (new) tenant.gauges.export
  • tenant.files.read
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

Pracovník manažer (ROLE_WORKER_MANAGER) ​

  • platform.users.read, platform.users.invite, platform.users.suspend, (new) platform.users.export
  • tenant.buildings.read, tenant.buildings.write, (new) tenant.buildings.export
  • tenant.gauges.read, tenant.gauges.write, tenant.gauges.delete, (new) tenant.gauges.invoicing.write, (new) tenant.gauges.export
  • tenant.files.read, tenant.files.write
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

Pracovník (ROLE_WORKER) ​

  • tenant.buildings.read, tenant.buildings.write, (new) tenant.buildings.export
  • tenant.gauges.read, tenant.gauges.write, tenant.gauges.delete, (new) tenant.gauges.invoicing.write
  • tenant.files.read, tenant.files.write
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

No user-management codes at all — matches legacy exactly (Worker never had SEE_USER_LIST etc.).

Host expert (ROLE_GUEST_EXPERT) ​

  • platform.users.read, (new) platform.users.export
  • tenant.buildings.read, tenant.buildings.write, (new) tenant.buildings.export
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

Host (ROLE_GUEST) ​

  • tenant.buildings.read
  • tenant.gauges.read
  • tenant.buildings.documents.read
  • tenant.files.read
  • (new) tenant.graphs.read, (new) tenant.overview.read, (new) tenant.reports.read
  • (new) tenant.notifications.custom-message.write
  • (new) tenant.dashboard.read

Purely read-only, as in legacy.

Note — Host expert supersedes permission-sets.seed.ts's current design note excluding an expert-guest equivalent from the global sets ("expert-guest access is configured per-tenant via custom organization sets by the tenant admin"); the seed needs updating to add this Permission Set.


Platform Permission Sets (from AdminRole) ​

Three Permission Sets for Porsenna-internal admin-panel staff. Legacy admin-panel permissions are autogenerated CRUD triples per entity (SEE_<X>_LIST, DELETE_<X>, plus list/edit variants) across ~20 platform reference-data entities. Only a handful of those entities have an EM3 platform-side permission code today (platform.clients.*, platform.weather-stations.*); the rest are (new).

Host (ROLE_GUEST, admin panel) ​

  • (new) platform.dashboard.read (mirrors the tenant-side dashboard code, platform variant)

Administrátor (ROLE_ADMINISTRATOR) ​

All platform admin-panel permissions — direct migration target for a full-access platform set. As with tenant ROLE_ADMIN, recommend using the existing all-access sentinel (AllAccessSet.PLATFORM_ADMIN) rather than enumerating roughly 130 codes.

SUPERADMIN (ROLE_SUPER_ADMIN) ​

Administrátor's full set, plus:

  • (new) platform.clients.daily-status.read (legacy ROLE_ADMIN_CLIENT_DAILY_STATUS, scope superadmin in EM2 — the only permission legacy reserves above plain Administrátor)

Also recommend the all-access sentinel here, since it's a superset of Administrátor which is itself all-access.


Not migrated: ClientPermission (tenant feature-module toggles) ​

EM2 also has ClientPermission (Settlement, Import odečtů, Auto-pairing, Import faktur, Chat, QR kódy) — six per-tenant feature-module flags, defaulted per client. This is not a user Permission Set and is out of scope for this document; it is a tenant-level feature/plan configuration concept and needs its own EM3 home if it's still needed.