Appearance
Entity: invoice
Entity Type: Database table
Description: An energy/expense invoice entered for a gauge, covering a billing period (dateFrom–dateTo) with the invoiced consumption's cost. Used to reconcile invoiced amounts and consumption against reading-derived values and contract-negotiated prices (see the Invoice Ingestion & Validation feature). Distinct from contract (which holds negotiated unit prices for a validity period) — invoice holds the actual billed period and amount as entered by a user or created via bulk import.
Data Attributes Table
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key of the entity. | 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 | Foreign Key → tenant; must exist | - | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| gaugeId | The gauge (metering point) this invoice was issued for. | UUID | - | Yes | No | UUID v7 | Foreign Key → gauge; must exist | name: idx_invoice_gaugeId_dateTo, type: btree | 018fa51f-fda3-7c63-b5a9-3fa33dc989de |
| invoiceNumber | Supplier-assigned invoice number, as printed on the invoice document. | String | - | Yes | No | - | - | - | 2026100234 |
| dateFrom | Start of the invoiced billing period. | Timestamp with time zone | - | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Must be ≤ dateTo. In practice only the date component is meaningful (billing periods are day-precision). | - | 2026-01-01T00:00:00Z |
| dateTo | End of the invoiced billing period. | Timestamp with time zone | - | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Must be ≥ dateFrom. | name: idx_invoice_gaugeId_dateTo, type: btree | 2026-01-31T00:00:00Z |
| price | Invoiced amount, gross (incl. VAT). | Numeric | - | Yes | No | - | - | - | 4520.50 |
| priceWithoutVat | Invoiced amount, net (excl. VAT). | Numeric | - | Yes | No | - | - | - | 3735.95 |
| vatRateId | The VAT rate used to derive whichever of the two amounts the user did not enter, recorded so the derivation stays reproducible after the catalogue changes. Null where both amounts were entered by hand, or where no rate was in force for the invoice's medium and period start. | UUID | null | No | No | UUID v7 | Foreign Key → vatRate; must exist if set | - | 018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21 |
| creditNote | Whether this record is a credit note (dobropis) rather than a standard invoice. | Boolean | false | Yes | No | - | - | - | false |
| fileId | The uploaded invoice document (PDF), if any. | UUID | - | No | No | UUID v7 | Foreign Key → document; must exist if set | - | 018fa51f-fda4-7e95-88e8-5f5675f0ddf8 |
| note | Free-text note attached to the invoice. | Text | - | No | No | - | - | - | Estimated reading, supplier confirmed by phone |
| importId | The bulk-import batch this record was created from, if any. | UUID | - | No | No | UUID v7 | Foreign Key → dataImport; must exist if set | - | 018fa51f-fda5-8a12-9c3d-1e2f3a4b5c6d |
| createdAt | Timestamp of entity creation. Is immutable. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-01-15T09:00:00Z |
| updatedAt | Timestamp of last entity update. Mutable. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-01-15T09:00:00Z |
| deletedAt | Timestamp of entity soft delete. 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 | - | null |
| createdBy | Identifier of the author who created this record. | String | - | Yes | No | type:actor | - | - | user:c2d3f586-1c9a-4f5f-b9ae-45f2c4f69f7e |
| updatedBy | Identifier of the author who last updated this record. | String | - | Yes | No | type:actor | - | - | system:invoice-import-v1 |
| legacyId | Legacy-system identifier; migration provenance. | String | null | No | No | - | - | UNIQUE (tenantId, legacySource, legacyId) WHERE legacyId IS NOT NULL | 3312 |
| legacySource | Which legacy table this invoice was migrated from. | String (enum) | null | No | No | enum: invoice | Must equal the listed value, or null | (composite with legacyId above) | invoice |
Note. Invoiced consumption and cost are modeled separately through invoiceValue.
Audited fields
Recorded on created (in full), updated (changed only) and deleted (in full): gaugeId, invoiceNumber, dateFrom, dateTo, price, priceWithoutVat, creditNote, fileId, note, importId, legacyId, legacySource.
Excluded: none.
price/priceWithoutVat are stored, not derived — nothing recomputes them from invoiceValue rows on write or read; the two are deliberately allowed to drift (EM2 stored both; confirmed not redundant).
legacyId/legacySource are real, migrated columns (202608182000-invoice-migration-fields.sql) not modeled in invoiceValidationMap — like the same pair on building/gauge/dataImport elsewhere in this batch, they're written only by CLI migration tooling, outside the domain layer, but are still real persisted columns and are documented and audited on that basis.
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, invoiceNumber is the natural candidate.