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

3.9 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 Сервер <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.