# Contract: BOM Decomposition Mapping Version: 1.0 ## Purpose Defines the canonical way to represent a BOM row that decomposes one external/vendor item into multiple internal component or LOT rows. This is not an alternate-choice mapping. All mappings in the row apply simultaneously. Use this contract when: - one vendor part number expands into multiple LOTs - one bundle SKU expands into multiple internal components - one external line item contributes quantities to multiple downstream rows See `README.md` for full JSON and Go examples. ## Canonical Shape - A BOM row contains one quantity plus zero or more mapping entries in one array field. - `component_mappings[]` is the only canonical persisted decomposition format. - Each mapping entry has: - `component_ref` - `quantity_per_item` - Project-specific field names are allowed only if the semantics stay identical. ## Quantity and Persistence Rules - Downstream quantity is always `row.quantity * mapping.quantity_per_item`. - The persisted row payload is the source of truth. - The same mapping shape must be used for persistence, API read/write payloads, and downstream expansion logic. - If the mapping array is empty, the row contributes nothing downstream. - Row order is defined by `sort_order`. - Mapping entry order may be preserved for UX, but business logic must not depend on it. ## UI and Validation Rules - The first mapping row is not special. Every mapping row is equally editable and removable. - `quantity_per_item` is edited per mapping row, not once for the whole BOM row. - Blank mapping rows may exist temporarily in draft UI state, but they must not be persisted. - New UI rows should default `quantity_per_item` to `1`. - Before persistence: - trim `component_ref` - drop empty `component_ref` rows - reject `quantity_per_item <= 0` - merge duplicate `component_ref` values by summing quantities - preserve first-seen order when merging duplicates ## Forbidden Patterns - Do not introduce alternate persisted shapes such as `primary_lot`, `secondary_lots`, `main_component`, or `bundle_lots`. - Do not split the component and its quantity across unrelated fields outside the mapping array. - Do not treat the first mapping row as a special primary component. - Do not compute downstream decomposition from temporary UI-only fields instead of the persisted mapping array. - Do not store the same decomposition in multiple competing formats.