# SalesOrderFieldChange

## Description

SalesOrderFieldChange is the per-field delta record attached to a `SalesOrderRevision` envelope. One row is written per changed field per amendment, regardless of whether the change affects the sales order header or one of its lines. Header changes and line changes coexist in the same table and are discriminated by `recordType`; `recordId` points to the affected `SalesOrder.id` or `SalesOrderLine.id`.

Values are stored as strings in `oldValue` and `newValue`. The amendment command serializes the live typed value per the underlying field's type (ISO date for dates, decimal string for decimals, uuid string for foreign keys, primitive `true` / `false` for booleans). This single-string-column shape mirrors the per-field delta tables in SAP `CDPOS` and NetSuite `SystemNote`, and keeps the schema able to record changes to app-extension fields the kit does not know about at compile time.

## Domain Model Definitions

### Model type

AppendOnly

### Command Definitions

None — rows are created as a side effect of [amendConfirmedSalesOrder](../command/AmendConfirmedSalesOrder.md). They have no dedicated commands.

### Query Definitions

None — no dedicated query exists; rows are reachable via the `fieldChanges` backward relation on SalesOrderRevision.

### Models

- SalesOrderFieldChange

### Invariants

- Each row belongs to exactly one `SalesOrderRevision` via `revisionId`
- `recordType` is `HEADER` when `recordId` is a `SalesOrder.id`, `LINE` when `recordId` is a `SalesOrderLine.id`
- `fieldName` is the schema field name on the targeted record type, including app-extension fields registered via the module's `fields` parameter; protected fields (`id`, `companyId`, `customerAccountId`, lifecycle/status fields, snapshot fields, timestamps) cannot appear here
- `changeKind` is `MODIFIED` when both `oldValue` and `newValue` reflect the diff, `ADDED` when the line or value did not previously exist (`oldValue` is null), `REMOVED` when the line or value no longer exists (`newValue` is null)
- For a single amendment, the same `(recordType, recordId, fieldName)` triple appears at most once
- Rows are immutable once written

### Relationships

- **Belongs To SalesOrderRevision**: Each row references one [SalesOrderRevision](./SalesOrderRevision.md) via `revisionId`
- **References SalesOrder or SalesOrderLine (polymorphic, no FK)**: `recordId` is the id of the affected record; no foreign-key constraint because removed lines may no longer exist when audit history is queried
