Appearance
Entity: clientModule
Entity Definition
- Entity Name:
clientModule - Entity Type: Database table (join table with metadata)
- Description: Records which add-on modules are enabled for a given client. A module that does not have a corresponding
clientModulerow is invisible to all users of that client.
Data Attributes Table
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key | UUID | Generated in code (app layer) | Yes | Yes | UUID v7 | - | Primary Key | 018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| tenantId | Tenant this record belongs to. | UUID | - | Yes | No | UUID v7 | FK → tenant; must exist | name: idx_client_module_tenantId, type: btree | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| clientId | Client the module is enabled for | UUID | - | Yes | No | UUID v7 | FK → client; must exist — see client | name: idx_client_module_clientId, type: btree | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| moduleKey | Identifies which module is enabled. | String | - | Yes | No | camelCase string | Non-empty; must be a value from the predefined list (see "Allowed moduleKey values" below); unique per clientId | name: idx_client_module_clientId_moduleKey, type: btree (unique) | remoteReadings |
| createdAt | Timestamp of when the record was created. Immutable after insert. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Cannot be null; cannot be modified after creation | - | 2025-03-16T18:00:00Z |
| updatedAt | Timestamp of the last update to the record. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Cannot be null | - | 2025-03-16T18:00:00Z |
| deletedAt | Timestamp of soft deletion. Null means the record is active. Once set, immutable. | Timestamp with time zone | - | No | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Immutable once set. Active records: WHERE deletedAt IS NULL | - | 2025-06-01T09:00:00Z |
| createdBy | Identifier of the actor who created the record | String | - | Yes | No | type:actor | Non-empty | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| updatedBy | Identifier of the actor who last updated the record | String | - | Yes | No | type:actor | Non-empty | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
Allowed moduleKey values
The following module keys are currently known (the list is not exhaustive):
| moduleKey | Description | Default on client creation |
|---|---|---|
remoteReadings | Remote meter reading collection | Yes |
remoteReadingsAnalysis | Analysis and reporting of remote readings | Yes |
buildingPassport | Building passport and energy certification | No |
iso50001 | EnMS management per ISO 50001 standard | No |
emissionFactors | CO₂ emission factor management per gauge | No |
energySharing | Energy community / energy sharing features | No |
settlement | Financial settlement for energy sharing | No |
consumptionMonitoring | Consumption monitoring and anomaly alerts | No |
tariffOptimisation | Electricity tariff optimisation tools | No |
Note:
moduleKeyis validated against this known list but is not modeled as a closed enum on the source page (it is typed as a plaincamelCase stringwith "the list is not exhaustive" stated explicitly). This looks like a candidate for the "open set" pattern described in the foundations doc (§7, open enum still gets a page) rather than an inline table — flagging for consideration, not changed here since the source documents it inline.
Audited fields
Recorded on created (in full) and deleted (in full) — a pure enable/disable join, so no field of an existing row is ever changed, only added or removed: clientId, moduleKey.
Excluded: none.
Every entry would carry subjectEntityType = client / subjectEntityId = clientId — the module being toggled is named by moduleKey on the entry itself, not as a separate subject.
Not registered for entityName resolution — renders id-only in the audit trail (architecture 61-audit-log.md §7.4 in the code repo: a valid, permanent state, not a gap). If registered, moduleKey is the natural candidate.