Files
QuoteForge/bible-local/09-vendor-spec.md
Mikhail Chusavitin b6fdac1caa feat: Nx BOM import — формат <qty>x <description>
Добавлен новый вариант импорта спеки: 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>
2026-06-17 07:42:46 +03:00

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_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 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 , в составе:) supplies server_model and name; the model is the last whitespace-separated token before the comma (so both Сервер X3 and Вычислительный GPU сервер X3 resolve to X3);
  • without a header, name falls back to the uploaded filename (without extension) and server_model is 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_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.

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 , в составе:) supplies server_model and name; the model is the last whitespace-separated token before the comma;
  • 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 <qty>x <description> are skipped;
  • no price data is present in the format; unit_price and total_price are 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 (ParsePastedBOMTextIsInspurBOM/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.