Refactor vendor mappings to canonical PN catalog

This commit is contained in:
Mikhail Chusavitin
2026-03-07 23:11:42 +03:00
parent 96572be712
commit 3f26a2935a
25 changed files with 1334 additions and 754 deletions

View File

@@ -168,13 +168,12 @@ Reworked partnumber book storage so `qt_partnumber_book_items` is a deduplicated
- `qt_partnumber_book_items` now stores one row per `partnumber` with fields:
- `partnumber`
- `lots_json` (`[{lot_name, qty}, ...]`)
- `is_primary_pn`
- `description`
- `qt_partnumber_books` now stores `partnumbers_json`, the sorted list of PN values included in that book.
- QuoteForge read contract is now:
- read active book from `qt_partnumber_books`
- parse `partnumbers_json`
- load PN payloads via `SELECT partnumber, lots_json, is_primary_pn, description FROM qt_partnumber_book_items WHERE partnumber IN (...)`
- load PN payloads via `SELECT partnumber, lots_json, description FROM qt_partnumber_book_items WHERE partnumber IN (...)`
- `PartnumberBookService.CreateSnapshot` now:
- builds one logical item per PN,
- serializes bundle composition into `lots_json`,
@@ -209,6 +208,106 @@ Reworked partnumber book storage so `qt_partnumber_book_items` is a deduplicated
- Tests: `internal/services/partnumber_book_test.go`
- Docs: `bible-local/vendor-mapping.md`
## 2026-03-07: Remove `is_primary_pn` From Partnumber Books
### Decision
Removed `is_primary_pn` from PriceForge partnumber book storage and from the PriceForge → QuoteForge sync contract.
### What changed
- Added migration `031_drop_is_primary_pn.sql` dropping `is_primary_pn` from:
- `lot_partnumbers`
- `qt_partnumber_book_items`
- Removed `IsPrimaryPN` from `internal/models/lot.go`.
- `PartnumberBookService` no longer copies, compares, or upserts `is_primary_pn`.
- `VendorMappingService` no longer writes `is_primary_pn` into `qt_partnumber_book_items`.
- The sync contract for QuoteForge is now:
`SELECT partnumber, lots_json, description FROM qt_partnumber_book_items WHERE partnumber IN (...)`
### Rationale
- The flag is obsolete and does not participate in current business logic.
- PN composition is now fully represented by `lots_json`; qty semantics belong there.
- Keeping a dead compatibility field increases drift between documented and actual architecture.
### Constraints
- `lots_json` is the only quantity-bearing field in the PN book contract.
- Any consumer still relying on `is_primary_pn` must be updated in the same change wave.
### Files
- Migration: `migrations/031_drop_is_primary_pn.sql`
- Models: `internal/models/lot.go`
- Services: `internal/services/partnumber_book.go`, `internal/services/vendor_mapping.go`
- Docs: `bible-local/vendor-mapping.md`
## 2026-03-07: Drop Legacy `lot_partnumbers` And Bundle Tables
### Decision
Removed `lot_partnumbers`, `qt_lot_bundles`, and `qt_lot_bundle_items` from active runtime architecture. The only canonical PN mapping store is `qt_partnumber_book_items`, one row per `partnumber`, with full composition in `lots_json`.
### What changed
- Added migration `032_drop_legacy_vendor_mapping_tables.sql` dropping:
- `qt_lot_bundle_items`
- `qt_lot_bundles`
- `lot_partnumbers`
- `internal/models/models.go` no longer auto-migrates legacy mapping and bundle tables.
- Active runtime paths now read canonical mappings from `qt_partnumber_book_items`.
- Tests were updated to seed `qt_partnumber_book_items` instead of `lot_partnumbers` and bundle tables.
- Active Bible docs now describe `qt_partnumber_book_items` as the only source of truth.
### Rationale
- QuoteForge and partnumber books already use `lots_json`.
- Keeping legacy mapping tables in runtime created two conflicting contracts.
- Multi-LOT PN composition belongs in `lots_json`, not in auxiliary bundle tables.
### Constraints
- `qt_partnumber_book_items` must remain deduplicated by `partnumber`.
- Multi-LOT PN composition exists only in `lots_json`.
- Vendor is metadata in `qt_vendor_partnumber_seen`, not part of the canonical mapping key.
- Deprecated Go structs may remain temporarily for in-memory test compatibility, but runtime must not auto-create or depend on the dropped tables.
### Files
- Migration: `migrations/032_drop_legacy_vendor_mapping_tables.sql`
- Models: `internal/models/models.go`, `internal/models/lot.go`
- Services: `internal/services/vendor_mapping.go`, `internal/services/stock_import.go`, `internal/services/partnumber_book.go`
- Warehouse: `internal/warehouse/snapshot.go`
- Matcher: `internal/lotmatch/matcher.go`
- Docs: `bible-local/BIBLE.md`, `bible-local/vendor-mapping.md`, `bible-local/architecture.md`, `bible-local/pricelist.md`, `bible-local/data-rules.md`
## 2026-03-07: Vendor Mapping LOT Autocomplete Must Not Clip Long Names
### Decision
LOT autocomplete in the Vendor Mapping modal must use a custom popup, not native browser `datalist`, because operators work with long monospaced LOT names that must remain fully readable.
### What changed
- Vendor Mapping modal uses a custom positioned suggestion popup for LOT inputs.
- Popup width may exceed the input width and is constrained by viewport, not by the text field width.
- Native `datalist` is not used for this field anymore.
### Rationale
- Native browser suggestion UIs clip long LOT names unpredictably and differ across platforms.
- Operators need to distinguish long hardware LOT names visually before selection; clipped suffixes are operationally unsafe.
### Constraints
- LOT suggestion popup must allow long names to be fully visible or significantly less clipped than the input width.
- Popup must stay above the modal overlay and follow the active LOT input.
### Files
- UI: `web/templates/vendor_mappings.html`
---
## 2026-02-20: Pricelist Formation Hardening (Estimate/Warehouse/Meta)