Skip to content
Updated Sep 11, 2026 by Barča Dvořáková · Owner: analysisactivefeatureconfluence-migration Edit on GitHub

Contract Management (DSC) ​

Business Context ​

Business-Level Definition ​

Contract Management (DSC — databáze smluvních cen, "database of contractual prices") records the supplier, negotiated unit prices, and validity period for the metering points of a building, whether under an individual contract or as part of a joint/bulk purchase group.

Contract and pricing data needs a single authoritative model, so that gauge cost calculations, invoice validation and reporting all read from one source of truth for supplier and price information — regardless of whether a metering point is under its own individual contract or part of a larger joint purchase group.

Requirements Definition ​

  • Contract and pricing data needs a single authoritative model, so that gauge cost calculations, invoice validation and reporting all read from one source of truth for supplier and price information — regardless of whether a metering point is under its own individual contract or part of a larger joint purchase group.

Acceptance Criteria ​

  • A contract can be created covering one or more gauges of the same medium; a contract covering exactly one gauge behaves identically to one covering many (no special-casing of "individual" vs. "group" contracts)
  • Once a gauge is linked to an active contract, its supplier and price fields become read-only in the "Fakturace" (billing setup) section of the gauge edit form
    • important note: on the separate screen "Faktury" (where users manually enter the actual invoiced price) fields remain fully editable
  • Contract price components (contractItem) are always selected from the admin-managed catalogue of allowed type/rate combinations — a user cannot record a price in a unit that doesn't apply to the selected line-item type
    • contract line-item types, rates and their valid combinations are managed centrally by the superadmin and are identical across all tenants
  • A contract's validity period must not overlap another contract's validity period for the same gauge and the same medium
  • Fuel and PHM gauges are not eligible for the Contract model
  • A contract's gauge-selection behaviour depends on its purchase type: individual requires selecting exactly one gauge with no candidate list; bulkPurchase presents an editable, pre-filled list of candidate gauges for that medium
  • A contract's medium distinguishes small- vs. large-consumer category for electricity and gas; heat and water are not split this way

N/A — not available in the source material.

Technical Context ​

User Stories / Use Cases ​

Enter a new contract for a single metering point: A user opens Contract Management → selects a medium → fills in supplier, validity period and unit prices → links exactly one gauge. The system stores the contract and its price components, and locks the corresponding fields on the gauge's invoicing form. The same flow is also reachable from a gauge's detail screen (i.e. not only from the central Contract Management screen).

Enter a new contract for a bulk purchase group: A user enters the same information, but selects "bulk/joint purchase" as the contract's purchase type (contract.purchaseType) and, during gauge selection, reviews and adjusts a pre-filled list of all gauges of the selected medium already flagged as part of that purchase group. Only bulk-purchase contracts pre-fill this list or affect group-level flagging; an individual contract does neither.

Edit an existing contract: A user opens a previously saved contract → adjusts prices and/or the list of linked gauges. Gauges removed from the contract have their fields unlocked again; newly added gauges get locked.

Admin manages the line-item catalogue: A superadmin adds a new distribution tariff code or pricing unit to the global catalogue (contractItemType / contractItemRate / contractItemAllowedTypeRate) without a deployment, making it available to all tenants immediately.

UI/UX Design ​

Visual and interaction design (contract entry form layout, gauge-selection modal) is tracked separately, which consolidates the UI changes from product review (purchase type, second entry point, supplier picker, extended medium, locking display). Acceptance criteria above describe functional behaviour only; this section will be updated once the wireframes are approved.

Functional Requirements ​

A contract (contract) covers one medium and one supplier for a defined validity period, and is linked to one or more gauges via contractGauge. A contract linked to exactly one gauge is an individual contract; a contract linked to several gauges is a joint/bulk purchase group — both are the same entity, there is no separate "individual contract" concept.

The supplier itself is stored in a dedicated, lightweight per-tenant entity — contractSupplier — rather than in the general organisation registry: suppliers are external parties that only ever appear as a name on a contract, not subjects with independent legal standing tracked elsewhere in the system.

Each priced component of a contract is a contractItem record, referencing:

  • contractItemType — what is being priced (a specific distribution tariff rate, vodné, stočné, paušální poplatek, etc.), a global admin-managed catalogue
  • contractItemRate — in what unit the price is expressed, also global admin-managed
  • contractItemAllowedTypeRate — validates that the chosen type/rate pair is legitimate (e.g. vodné can only be priced in Kč/m³, never Kč/MWh)

This structure keeps the catalogue of priceable items, their units, and the valid combinations between them centrally managed and admin-extensible without requiring a deployment.

An ER diagram exists in the source Confluence page (embedded image, not extractable in this migration pass — flagged as incomplete).

Medium scope ​

Contract covers electricity and gas split by consumer size (small/large), plus heat and water — see enum ContractMedium (Confluence entity, out of scope for this pass) for the exact values. A single contract covers exactly one of these values — small- and large-consumer line items are never combined within one contract.

fuel, phm and kvp gauges are not eligible for a Contract and are not expected to become eligible; their supplier information (if any) is recorded as a plain attribute directly on the gauge, unrelated to this feature.

Field locking after contract assignment ​

Once a gauge is linked to an active contract (via contractGauge), its supplier and price fields become read-only in the Fakturace section of the invoicing form — the contract is now the single source of truth for those values. Removing a gauge from a contract unlocks its fields again for manual entry.

This locking applies only to the gauge's billing-setup fields (supplier, unit price) in its edit form. It has no effect on the separate invoice-recording screen, where the actual invoiced amount is entered manually and is never overwritten or locked by a contract.

Future (v2): the contract's unit price is intended to feed a comparison mechanism that checks the actual invoiced amount against the price expected from the contract, flagging discrepancies. No such comparison exists yet — the two values simply coexist today.

Gauge selection UX ​

When saving a contract, the user reviews a pre-filled list of candidate gauges — all gauges of the selected medium already flagged for the given purchase group — with the ability to add or remove individual gauges before confirming.

Behaviour depends on contract.purchaseType:

  • individual — the user selects exactly one gauge directly. No candidate list is shown.
  • bulkPurchase — the user reviews and adjusts a pre-filled list of candidate gauges: all gauges of the selected medium already flagged as part of the given purchase group, with the ability to add or remove individual gauges before confirming.
Automatic actions ​

Adapted to the EM3 Actions domain (Notice lifecycle):

  • a Notice is raised when a contract document is saved, and again a configurable number of days before a contract's contract.validTo is reached
  • a Notice is raised for a medium at a tenant that has no active contract at all — prompting the user to enter prices for the next period

Open question (flagged for follow-up): The exact recurrence/threshold parameters (e.g. "30 days before expiry") and the precise Notice copy should be confirmed against the current Actions/Notice conventions established elsewhere in EM3, once the Actions domain (Initiative 6) reaches this feature.

Note: detecting/flagging a contract whose validity has ended does not itself depend on Initiative 6 — it is tracked as a standalone implementation task. Only the actual Notice creation and copy remain gated on the Actions domain above.

Supplier entry ​

Supplier selection uses a combined lookup field — the user can either pick an existing contractSupplier record, or type a new name to create one on the fly scoped to the current tenant (identical pattern as EM2's supplier picker).

Contract continuity ​

No explicit chaining or grouping between successive contracts is introduced. Behaviour matches the legacy system (flat list ordered by validity date, overlap check per gauge/medium only).

Non-Functional Requirements ​

Transactional Operations ​

  • Saving a contract together with its contractItem rows and its contractGauge links, plus locking the corresponding fields on all linked gauges, must be a single atomic transaction — all succeed or all roll back.

API Analysis ​

GET    /v1/contracts                                 List contracts (filterable by medium, supplier, gauge, validity)
GET    /v1/contracts/:id                             Contract detail, including linked gauges and price items
POST   /v1/contracts                                  Create contract (with items and linked gauges)
PATCH  /v1/contracts/:id                              Update contract fields, items and linked gauges
DELETE /v1/contracts/:id                              Soft-delete contract (unlocks all linked gauges)

GET    /v1/contract-item-types                        List the global line-item type catalogue
GET    /v1/contract-item-rates                        List the global pricing unit catalogue
GET    /v1/contract-item-types/:id/allowed-rates       List rates allowed for a given line-item type

GET    /v1/contract-suppliers                         List suppliers for the current tenant
POST   /v1/contract-suppliers                         Create a new supplier name for the current tenant

Domain Model (ER diagram) & Data Attribute Table ​

Entities introduced or extended by this feature (Confluence entity pages, not yet migrated into this repo's Entity DAT catalog):

  • contract
  • contractGauge
  • contractItem
  • contractItemType
  • contractItemRate
  • contractItemAllowedTypeRate
  • contractSupplier

Existing entities referenced (not modified beyond adding the relationships described above): building, gauge, and document (File Storage domain — entity page not yet created; see Initiative 9).

Data ​

The contractItemType, contractItemRate and contractItemAllowedTypeRate catalogues require an initial seed of the national tariff/unit catalogue: distribution tariff codes such as C01d–D57d for high/low tariff electricity, vodné/stočné/srážkovné for water, paušální poplatek and pohyblivá/pevná složka for gas and heat. This seed is superadmin-managed and can be extended later without a deployment. No per-tenant seed data is required.

Test Data ​

N/A — not covered in source material; no Test Data location was specified in the source page.

Logging ​

.info

  • contract created / updated / deleted (contractId, medium, supplierId, affected gauge count)
  • gauge added to / removed from a contract

Monitoring ​

N/A — not covered in source material.

Caching ​

N/A — not covered in source material.

Backward Compatibility and Migration ​

Existing gauge data will be migrated into this model as part of the rollout of this feature — every gauge's supplier/pricing data is brought into the unified contract model. (Responsible-person migration is now handled separately — see a child page under Building Management, out of scope for this pass.)

Resolved — Supplier migration target. Legacy Supplier.name migrates to the new contractSupplier entity, not to organisation. Confirmed by the product owner: suppliers are external parties delivering a service to clients, not subjects that belong in the organisation registry. Full rationale recorded in a decision-log Confluence page out of scope for this pass.

Resolved — migration classification for all four legacy data situations, including the ~4634 gauges with only a legacy supplier flag and no price history — confirmed via sample verification (decision-log page, out of scope for this pass).

N/A — not addressed in the source Confluence page; migrated as a reference copy without new legal analysis.

Cybersecurity Considerations ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new security analysis.

Risk Assessment ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new risk analysis.

Auditing, Reporting & Measurement ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new analysis.