Files
QuoteForge/bible-local/09-vendor-spec.md
Mikhail Chusavitin 7233a0780f feat: импорт человекочитаемого текстового BOM (формат "<описание> - N шт.")
Новый формат vendor-import: опциональный заголовок "Сервер <модель>,
в составе:" и строки вида "<описание> - <кол-во> шт." (дефис/тире,
пробел перед "шт" и точка опциональны). Количество якорится в конце
строки, поэтому дефисы и цифры внутри описания (8-GPU-2304GB) сохраняются.

Описание пишется и в vendor_partnumber, и в description: строки
резолвятся через активную книгу партномеров, иначе остаются
нерезолвленными и редактируемыми. Весь файл — одна конфигурация.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 09:06:26 +03:00

111 lines
3.9 KiB
Markdown

# 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.
## Inspur BOM import
The same endpoint `POST /api/projects/:uuid/vendor-import` also accepts Inspur text BOM exports.
Format: one component per line, `<partnumber>*<quantity>`. A leading `|` character is optional and stripped. Trailing whitespace around `*` is normalised.
Example:
```
|CPU_AMD_9535-EPYC2.4_64C_256M_300W*1
|PowerSupply_1300W_Titanium_220VACor240VDC_GaN*2
```
Rules:
- the entire file becomes a single configuration (`server_count = 1`);
- configuration `name` is derived from the uploaded filename (without extension);
- lines that do not contain `*<digits>` are skipped;
- no price data is present in the format; `unit_price` and `total_price` are left nil.
## Text BOM import
The same endpoint `POST /api/projects/:uuid/vendor-import` also accepts a human-readable Russian text BOM.
Format: an optional header line `Сервер <model>, в составе:` followed by one component per line as
`<description> - <quantity> шт.`. The separator may be a hyphen, en-dash, or em-dash; the space before
`шт` is optional; a trailing `.` after `шт` is optional. Quantities are anchored to the end of the line,
so hyphens and digits inside the description (e.g. `8-GPU-2304GB`) are preserved.
Example:
```
Сервер KR9288X3, в составе:
GPU-NVIDIA HGX B300 8-GPU-2304GB HBM3E - 1 шт.
CPU Intel 6760P Xeon 2.2GHz 64C 320M 330W - 2 шт.
Mem 128G DDR5-6400MHz ECC-RDIMM - 16 шт.
NVIDIA twin port transceiver, 800Gbps, OSFP - 8шт.
```
Rules:
- the entire file becomes a single configuration (`server_count = 1`);
- the `Сервер <model>, в составе:` header supplies the configuration `name` and `server_model`;
- without a header, `name` falls back to the uploaded filename (without extension) and `server_model` is empty;
- the format carries no partnumbers — each line's description is stored as both `vendor_partnumber` and
`description`, so rows resolve through the active partnumber book when matched and otherwise stay
unresolved and editable in the UI;
- lines that do not match `<description> - <quantity> шт.` are skipped;
- no price data is present in the format; `unit_price` and `total_price` are left nil.