Files
QuoteForge/bible-local/decisions/2026-07-24-component-universe-world-union.md
T
Mikhail ChusavitinandClaude Opus 4.8 905f9a4952 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>
2026-07-24 17:01:31 +03:00

58 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.