Appearance
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 abuildingrestriction listing the buildings that role's holders are individually assigned to; the "all X" version carries nobuildingrestriction at all. Every code in this document is held atallbreadth — 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.readROLE_CAN_EDIT_CLIENT→platform.tenants.manageROLE_CAN_SEE_ALL_CLIENTS→platform.tenants.read; the restricted-set-of-tenants version carries aclientGrouprestriction (the same mechanism as Admin-manažer's own reach, below),ROLE_CAN_SEE_ALL_CLIENTSmeans no such restrictionROLE_CAN_EXPORT_CLIENT→ (new)platform.tenants.exportROLE_CAN_ASSIGN_CLIENT(self-assign to a client to start working in it) → (new)platform.tenants.self-assignROLE_CAN_ASSIGN_H_M_STATIONS→ (new)platform.tenants.weather-stations.assignROLE_CAN_ASSIGN_DEFAULT_H_M_STATION→ (new)platform.tenants.weather-stations.assign-defaultROLE_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.readROLE_CAN_EDIT_USER,ROLE_CAN_CREATE_USER→platform.users.inviteROLE_CAN_DELETE_USER→platform.users.suspendROLE_CAN_EXPORT_USER→ (new)platform.users.export
Objekty (Buildings)
ROLE_CAN_SEE_BUILDING_LIST,ROLE_CAN_SEE_BUILDING_DETAIL→tenant.buildings.readROLE_CAN_SEE_ALL_BUILDINGS→tenant.buildings.read; the assigned-buildings-only version carries abuildingrestriction listing those buildings,ROLE_CAN_SEE_ALL_BUILDINGSmeans nobuildingrestrictionROLE_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.writeEDIT_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 roleROLE_CAN_DELETE_BUILDING→tenant.buildings.deleteROLE_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.readROLE_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/.writeROLE_CAN_EXPORT_BUILDING→ (new)tenant.buildings.export
Měřidla (Gauges)
ROLE_CAN_SEE_GAUGE_LIST,ROLE_CAN_SEE_GAUGE_DETAIL→tenant.gauges.readROLE_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.writeEDIT_GAUGE_FIELDS_IDENT_3,EDIT_GAUGE_CONSUMPTION_2(gated a separate legacy controller) → (new)tenant.gauges.parameters.writeEDIT_GAUGE_INVOICING(gated a separate legacy controller) → (new)tenant.gauges.invoicing.writeROLE_CAN_DELETE_GAUGE→tenant.gauges.deleteROLE_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.exportROLE_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.writeROLE_CAN_EDIT_LAST_GAUGE_READINGS→ (new)tenant.readings.write-lastROLE_CAN_DELETE_ALL_GAUGE_READINGS→tenant.readings.deleteROLE_CAN_DELETE_LAST_GAUGE_READINGS→ (new)tenant.readings.delete-last- Both
-lastcodes are enforced by a domain guard in the write use-case — the same pattern EM2'sGaugeReadingVoteruses (a plain*_ALL_*permission checked first, a separate*_LAST_*permission combined with a liveisLast()query as fallback) and the one EM3'sREADINGS_CODES.METER_REPLACEMENT/OVERRIDE_MONOTONICITYalready 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.importROLE_CAN_DELETE_IMPORT_GAUGE_READINGS→tenant.readings.deleteROLE_CAN_EXPORT_GAUGE_READINGS→ (new)tenant.readings.export
Faktury (Invoices)
ROLE_CAN_CREATE_INVOICES,ROLE_CAN_EDIT_ALL_INVOICES→tenant.invoices.writeROLE_CAN_EDIT_LAST_INVOICES→ (new)tenant.invoices.write-lastROLE_CAN_DELETE_ALL_INVOICES→tenant.invoices.deleteROLE_CAN_DELETE_LAST_INVOICES→ (new)tenant.invoices.delete-last- Both
-lastcodes use the same domain-guard pattern as readings, above (EM2'sInvoiceVoteris identical toGaugeReadingVoter); none of these four codes is currently granted to any legacy tenant role. ROLE_CAN_EXPORT_INVOICES→ (new)tenant.invoices.exportROLE_CAN_IMPORT_INVOICES→ (new)tenant.invoices.import(check against thedata-importsapp 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.readROLE_CAN_CREATE_FILE,ROLE_CAN_EDIT_FILE→tenant.files.writeROLE_CAN_CREATE_FILE_CONTEXT_CLIENT(client-level, not building-level, document) →tenant.files.writewith nobuildingrestrictionROLE_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.deleteROLE_CAN_DELETE_DOCUMENT_DEPENDENCIES→ (new)tenant.files.dependencies.delete
Smluvní ceny (Contracts)
ROLE_CAN_VIEW_CONTRACTS→tenant.contracts.readROLE_CAN_CREATE_CONTRACT,ROLE_CAN_EDIT_CONTRACT→tenant.contracts.writeROLE_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.readROLE_CAN_EDIT_TOLERANCE,ROLE_CAN_CREATE_GAUGE_DATA_CONTROL,ROLE_CAN_EDIT_GAUGE_DATA_CONTROL→ (new)tenant.gauge-data-control.writeROLE_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.readROLE_CAN_SEE_REPORTS_PAGE→ (new)tenant.reports.readROLE_CAN_SEE_INPUTS_PAGE→ (new)tenant.inputs.readROLE_CAN_SEE_OVERVIEW_PAGE→ (new)tenant.overview.readROLE_CAN_CREATE_SHARED_FILTERS,ROLE_CAN_EDIT_SHARED_FILTERS→ (new)tenant.shared-filters.writeROLE_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.readROLE_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.writeROLE_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-assigntenant.remote-endpoints.pair,tenant.remote-sources.manageplatform.users.read,platform.users.invite,platform.users.suspend, (new)platform.users.exporttenant.buildings.read,tenant.buildings.write,tenant.buildings.delete, (new)tenant.buildings.export,tenant.buildings.responsible-persons.writetenant.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.exporttenant.files.read,tenant.files.write,tenant.files.write(client-level, nobuildingrestriction)- (new)
tenant.graphs.read, (new)tenant.overview.read, (new)tenant.reports.read tenant.readings.importtenant.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.exporttenant.buildings.read,tenant.buildings.write, (new)tenant.buildings.export,tenant.buildings.responsible-persons.writetenant.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.exporttenant.files.read,tenant.files.write,tenant.files.write(client-level, nobuildingrestriction)- (new)
tenant.graphs.read, (new)tenant.overview.read, (new)tenant.reports.read tenant.readings.import(remote variant only — legacyROLE_MANAGERlacks plainIMPORT_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.exporttenant.buildings.read,tenant.buildings.write, (new)tenant.buildings.exporttenant.gauges.read,tenant.gauges.write,tenant.gauges.delete, (new)tenant.gauges.exporttenant.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.exporttenant.buildings.read,tenant.buildings.write, (new)tenant.buildings.exporttenant.gauges.read,tenant.gauges.write,tenant.gauges.delete, (new)tenant.gauges.invoicing.write, (new)tenant.gauges.exporttenant.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.exporttenant.gauges.read,tenant.gauges.write,tenant.gauges.delete, (new)tenant.gauges.invoicing.writetenant.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.exporttenant.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.readtenant.gauges.readtenant.buildings.documents.readtenant.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 expertsupersedespermission-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(legacyROLE_ADMIN_CLIENT_DAILY_STATUS, scopesuperadminin 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.