feat: PN code в импорте BOM, объединённый список LOT (world ∪ estimate)
Колонка «PN code» в таблице импорта BOM: HPE-спецификации хранят партномер в двух столбцах, канонический вид — P52534-B21#B19. Склейка попадает в vendor_partnumber и сравнивается с книгой партномеров как есть; при загрузке разбирается обратно на две колонки. Схему БД менять не пришлось. Список допустимых LOT собирается из world ∪ estimate (componentUniverse), а не только из estimate. Прежняя область видимости молча теряла lot_mappings: резолвер сопоставлял PN по книге, которая про прайслисты не знает, а фронт отбрасывал LOT, которого нет в estimate — при этом в корзину он всё равно попадал. World-only позиции отдают price_quality 0. Кнопка «Пересопоставить» — перерешать BOM по актуальной книге, так как при открытии конфигурации сопоставления остаются замороженными. Итоги на вкладке «Ценообразование»: звёздочки убраны (переносились на новую строку), все три суммы красные, при наведении — попап с долей цен из прайслиста WORLD. Колонка «PN вендора» больше не переносится. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0fa7b0b1b6
commit
905f9a4952
@@ -0,0 +1,57 @@
|
||||
# Decision: the component universe is `world` ∪ `estimate`, not `estimate` alone
|
||||
|
||||
**Date:** 2026-07-24
|
||||
**Status:** active
|
||||
|
||||
## Context
|
||||
|
||||
Every component read path in `internal/localdb/components.go` was scoped to the latest
|
||||
active `estimate` pricelist. That pricelist is therefore what defined the set of LOTs the
|
||||
configurator considers to exist, and the frontend used it as its validation list
|
||||
(`_bomLotValid` in `index.html`, fed by `/api/components`).
|
||||
|
||||
That silently broke BOM mappings. The resolver matches a vendor partnumber against the
|
||||
partnumber book, which knows nothing about pricelists, so it legitimately returns LOTs the
|
||||
estimate pricelist does not carry. The frontend then displayed such a row as mapped —
|
||||
`_getRowBaseLot` does not validate `resolved_lot` — and `applyBOMToEstimate` pushed the LOT
|
||||
into the cart, but `_getRowCanonicalLotMappings` filtered it out through `_bomLotValid`, so
|
||||
the mapping vanished on save. Result: the LOT sat in the cart while its BOM row showed
|
||||
"н/д", and the pricing tab listed it at the bottom as an orphan with an empty vendor PN.
|
||||
|
||||
Prices for such LOTs were already available — see
|
||||
[2026-07-10-world-pricelist-fallback.md](2026-07-10-world-pricelist-fallback.md) — only
|
||||
membership was missing.
|
||||
|
||||
## Decision
|
||||
|
||||
`componentUniverse()` in `internal/localdb/components.go` is the single source of the LOT
|
||||
set, and every component read path goes through it: `ListComponents`,
|
||||
`SearchLocalComponents`, `SearchLocalComponentsByCategory`, `GetLocalComponent`,
|
||||
`GetLocalComponentCategories`, `GetLocalComponentCategoriesByLotNames`, `CountComponents`.
|
||||
|
||||
- The universe is the latest active `world` pricelist — the widest list available — plus
|
||||
the latest active `estimate` pricelist as a safety net.
|
||||
- Deduplication is on `UPPER(lot_name)`. The estimate row wins a collision, so its
|
||||
category, description and `price_quality` stay authoritative and nothing changes for
|
||||
LOTs that were already visible.
|
||||
- A world-only row reports `price_quality = 0`, rendering at the red end of the shared
|
||||
scale in `web/static/price-quality.js`. This is the **one** place QF sets that field
|
||||
instead of only reading it (contrast `bible-local/03-database.md`), and it deliberately
|
||||
overrides whatever the external pricing tool wrote on the world row.
|
||||
- The helper always returns a derived table (`(?) AS c`), so callers apply their own
|
||||
`Select`/`Count`/`Where` without knowing which pricelists exist.
|
||||
- Either pricelist may be absent; only both missing is an error.
|
||||
|
||||
On the frontend, `_bomLotPersistable` additionally lets a LOT with
|
||||
`resolution_source === 'book'` be persisted into `lot_mappings[]` even when it is in
|
||||
neither pricelist. Hand-typed LOTs stay validated against the universe.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The configurator's pickers, category list and search now surface world-only LOTs. They
|
||||
are selectable and appear in the reddest quality colour.
|
||||
- `CountComponents` — the "is there data" check — counts the union.
|
||||
- A LOT present in neither pricelist (nothing to price it with) still resolves from the
|
||||
book and now survives a save, instead of being dropped without a message.
|
||||
- Covered by `internal/localdb/component_universe_test.go`: union and dedup, estimate
|
||||
winning a collision, forced quality 0, and each single-source / empty case.
|
||||
Reference in New Issue
Block a user