# 09 - Vendor BOM

## Storage contract

Vendor BOM is stored in `local_configurations.vendor_spec` and synced with `qt_configurations.vendor_spec`.

Each row uses this canonical shape:

```json
{
  "sort_order": 10,
  "vendor_partnumber": "ABC-123",
  "quantity": 2,
  "description": "row description",
  "unit_price": 4500.0,
  "total_price": 9000.0,
  "lot_mappings": [
    { "lot_name": "LOT_A", "quantity_per_pn": 1 }
  ]
}
```

Rules:
- `lot_mappings[]` is the only persisted PN -> LOT mapping contract;
- QuoteForge does not use legacy BOM tables;
- apply flow rebuilds cart rows from `lot_mappings[]`.

## Partnumber books

Partnumber books are pull-only snapshots from PriceForge.

Local tables:
- `local_partnumber_books`
- `local_partnumber_book_items`

Server tables:
- `qt_partnumber_books`
- `qt_partnumber_book_items`

Resolution flow:
1. load the active local book;
2. find `vendor_partnumber`;
3. copy `lots_json` into `lot_mappings[]`;
4. keep unresolved rows editable in the UI.

## CFXML import

`POST /api/projects/:uuid/vendor-import` imports one vendor workspace into an existing project.

Rules:
- accepted file field is `file`;
- maximum file size is `1 GiB`;
- one `ProprietaryGroupIdentifier` becomes one QuoteForge configuration;
- software rows stay inside their hardware group and never become standalone configurations;
- primary group row is selected structurally, without vendor-specific SKU hardcoding;
- imported configuration order follows workspace order.

Imported configuration fields:
- `name` from primary row `ProductName`
- `server_count` from primary row `Quantity`
- `server_model` from primary row `ProductDescription`
- `article` or `support_code` from `ProprietaryProductIdentifier`

Imported BOM rows become `vendor_spec` rows and are resolved through the active local partnumber book when possible.
