Добавлен новый вариант импорта спеки: quantity-first формат, где каждая строка начинается с `<qty>x <description>` (например, «2x Intel Xeon 8570»). Порядок детекции: Inspur → Nx → Text BOM. Заголовок «, в составе:» работает так же, как в Text BOM — последний токен перед запятой становится server_model. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.6 KiB
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:
{
"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_bookslocal_partnumber_book_items
Server tables:
qt_partnumber_booksqt_partnumber_book_items
Resolution flow:
- load the active local book;
- find
vendor_partnumber; - copy
lots_jsonintolot_mappings[]; - 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
ProprietaryGroupIdentifierbecomes 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:
namefrom primary rowProductNameserver_countfrom primary rowQuantityserver_modelfrom primary rowProductDescriptionarticleorsupport_codefromProprietaryProductIdentifier
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
nameis derived from the uploaded filename (without extension); - lines that do not contain
*<digits>are skipped; - no price data is present in the format;
unit_priceandtotal_priceare 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 ending with , в составе: 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, commas, and digits inside the description (e.g. 8-GPU-2304GB, RAID0,1,10) are preserved.
Example:
Вычислительный GPU сервер G5500V7, в составе:
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 header (any line ending with
, в составе:) suppliesserver_modelandname; the model is the last whitespace-separated token before the comma (so bothСервер X3andВычислительный GPU сервер X3resolve toX3); - without a header,
namefalls back to the uploaded filename (without extension) andserver_modelis empty; - each line is trimmed, so leading/trailing whitespace never enters
vendor_partnumber; - the format carries no partnumbers — each line's description is stored as both
vendor_partnumberanddescription, 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_priceandtotal_priceare left nil.
Nx BOM import (quantity-first)
The same endpoint POST /api/projects/:uuid/vendor-import also accepts a quantity-first BOM
where each item line begins with <qty>x <description>.
Format: an optional header line ending with , в составе: followed by one component per line as
<qty>x <description>. The x separator is case-insensitive; parentheses, commas, and hyphens
inside the description are preserved as-is.
Example:
Сервер G893-SD1-AAX3, в составе:
1x 8U 2CPU 8GPU Server System (32x DDR5 DIMM Slots,8x 2.5" Hot-Swap Drive Bays, 4+4 3000W, 2x 10Gb/s RJ45, 2x IPMI RJ45)
2x Intel Xeon 8570 (56 cores, 2.1GHz, 300MB, 350W)
32x 64GB DDR5 ECC RDIMM
1x GPU Nvidia HGX H200 141GB 8GPU
3x 1.92TB NVMe PCIe SFF RI
5x 7.68TB NVMe PCIe SFF RI
8x 1-port 400G NDR OSFP CX7
2x 2-port 100GbE QSFP56 CX6
1x 2-port 10GbE RJ45
Rules:
- the entire file becomes a single configuration (
server_count = 1); - the header (any line ending with
, в составе:) suppliesserver_modelandname; the model is the last whitespace-separated token before the comma; - without a header,
namefalls back to the uploaded filename (without extension) andserver_modelis empty; - the format carries no partnumbers — each line's description is stored as both
vendor_partnumberanddescription, so rows resolve through the active partnumber book when matched and otherwise stay unresolved and editable in the UI; - lines that do not match
<qty>x <description>are skipped; - no price data is present in the format;
unit_priceandtotal_priceare left nil; - detection runs before Text BOM in the format switch (Inspur → Nx → Text).
Pasted BOM text parsing
POST /api/vendor-spec/parse-text is a stateless endpoint that parses pasted single-column text BOM
(Inspur and Russian text BOM) into rows. Request body: {"text": "..."}. Response:
{"rows": [{vendor_partnumber, quantity, description}], "format": "Inspur"|"Text"|""}.
This shares the exact detectors and parsers used by the file-import path
(ParsePastedBOMText → IsInspurBOM/parseInspurBOM, IsNxBOM/parseNxBOM, IsTextBOM/parseTextBOM),
so paste and upload behave identically — there is no second parser in the frontend. The configurator's BOM
paste box calls this endpoint; an empty rows result (or any payload containing tabs, i.e. a real
spreadsheet table) falls back to the manual column-mapping grid.