Refactor partnumber book catalog storage

This commit is contained in:
Mikhail Chusavitin
2026-03-07 22:10:05 +03:00
parent b2b2f4774c
commit 08de9006ef
8 changed files with 304 additions and 68 deletions

View File

@@ -108,6 +108,8 @@ Another application writes non-partnumber LOT identifiers into `qt_vendor_partnu
## 2026-02-21: Partnumber Book Snapshots for QuoteForge
Superseded in storage shape by the 2026-03-07 decision below. This section is retained as historical context.
### Decision
Implemented versioned snapshots of the `lot_partnumbers` → LOT mapping in `qt_partnumber_books` / `qt_partnumber_book_items`. PriceForge writes; QuoteForge reads (SELECT only).
@@ -154,6 +156,59 @@ QuoteForge needs a stable, versioned copy of the PN→LOT mapping to resolve BOM
- Routes: `cmd/pfs/main.go`
- UI: `web/templates/vendor_mappings.html`
## 2026-03-07: Partnumber Book Catalog Deduplication
### Decision
Reworked partnumber book storage so `qt_partnumber_book_items` is a deduplicated source-of-truth catalog by `partnumber`, and each snapshot book stores its included PN list in `qt_partnumber_books.partnumbers_json`.
### What changed
- Migration `029` replaces expanded snapshot rows with `lots_json` in `qt_partnumber_book_items`.
- `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 (...)`
- `PartnumberBookService.CreateSnapshot` now:
- builds one logical item per PN,
- serializes bundle composition into `lots_json`,
- upserts the global catalog,
- writes PN membership into the book header.
- `ListBooks` now derives item counts from `partnumbers_json`.
- Added regression tests covering:
- direct PN -> one LOT with qty `1`
- bundle PN -> multiple LOT with explicit quantities
- deduplication of catalog rows across multiple books
### Rationale
- A real vendor PN may consist of several different LOT with different quantities.
- Expanded rows in `qt_partnumber_book_items` lost quantity semantics and duplicated the same logical item across books.
- The new shape keeps PN composition intact and makes the items table the canonical catalog.
### Constraints
- `qt_partnumber_book_items` must not contain duplicate `partnumber` rows.
- `lots_json` is the only source of truth for PN composition.
- `qt_partnumber_books.partnumbers_json` stores membership only; the resolved PN composition comes from `qt_partnumber_book_items`.
- `qt_partnumber_book_item_links` is not part of the architecture and must not exist.
- If one snapshot build encounters multiple distinct compositions for the same PN, the build must fail instead of choosing one silently.
- Historical books remain snapshots of included PN membership; item payloads are read from the current catalog.
### Files
- Migration: `migrations/029_change_partnumber_book_items_to_lots_json.sql`
- Models: `internal/models/lot.go`
- Service: `internal/services/partnumber_book.go`
- Tests: `internal/services/partnumber_book_test.go`
- Docs: `bible-local/vendor-mapping.md`
---
## 2026-02-20: Pricelist Formation Hardening (Estimate/Warehouse/Meta)