- Migrations 026-028: qt_partnumber_books + qt_partnumber_book_items tables; is_primary_pn on lot_partnumbers; version VARCHAR(30); description VARCHAR(10000) on items (required by QuoteForge sync) - Service: CreateSnapshot expands bundles, filters empty lot_name and ignored PNs, copies description, activates new book atomically, applies GFS retention (7d/5w/12m/10y) with explicit item deletion - Task type TaskTypePartnumberBookCreate; handlers ListPartnumberBooks and CreatePartnumberBook; routes GET/POST /api/admin/pricing/partnumber-books - UI: snapshot list + "Создать снапшот сопоставлений" button with progress polling on /vendor-mappings page - Bible: history, api, background-tasks, vendor-mapping updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.3 KiB
5.3 KiB
Vendor Mapping (Сопоставление partnumber → LOT)
Решение зафиксировано: 2026-02-18
Концепция
lot_partnumbers — канонический контракт сопоставления для внешнего конфигуратора.
Маппинг строго 1:1 по ключу (vendor, partnumber) → lot_name.
Правила
1. Единственная запись на ключ
Запрещено создавать несколько строк для одного ключа (vendor, partnumber).
2. Порядок резолвинга (фиксированный)
1. Точное совпадение: vendor + partnumber → lot_name
2. Fallback: vendor='' + partnumber → lot_name
Резолвер: internal/lotmatch/matcher.go
3. Составные маппинги — через бандлы
Если один внешний partnumber соответствует нескольким LOT — использовать внутренние бандл-таблицы:
lot_partnumbers: (vendor, partnumber) → bundle_lot_name
qt_lot_bundles: bundle_lot_name → [item1, item2, ...]
qt_lot_bundle_items: bundle_lot_name, lot_name, qty
- Bundle LOT — внутренний, скрыт в обычном UI LOT по умолчанию.
- Bundle expansion происходит в PriceForge: при расчёте warehouse-прайслиста и при создании partnumber book snapshot.
4. Ignore-логика
- Не использовать
stock_ignore_rulesдля новой логики. - Использовать
qt_vendor_partnumber_seen.is_ignored. qt_vendor_partnumber_seenхранится в формате 1 строка на partnumber (vendor/source не участвуют в уникальности).- Ignore применяется по
partnumber(одинаково для записей с vendor и без vendor).
5. Клиентская совместимость
- Клиент потребляет LOT-based прайслисты (как обычно).
- Bundle expansion/allocation происходит только внутри PriceForge.
Allocation при отсутствии estimate
Если у bundle-компонента нет estimate-цены:
- Fallback: взять из предыдущего активного warehouse-прайслиста.
- Если нет предыдущего — цена
0.
Таблицы БД
| Таблица | Назначение |
|---|---|
lot_partnumbers |
Канонические маппинги (vendor, partnumber) → lot_name; колонка is_primary_pn — ведущий PN для qty в BOM |
qt_lot_bundles |
Определения бандлов (bundle LOT → описание) |
qt_lot_bundle_items |
Состав бандла: (bundle_lot_name, lot_name, qty) |
qt_vendor_partnumber_seen |
Реестр seen-записей (уникально по partnumber) + флаг is_ignored |
qt_partnumber_books |
Версионированные снимки маппинга для QuoteForge (пишет PriceForge) |
qt_partnumber_book_items |
Строки снимка: (book_id, partnumber, lot_name, is_primary_pn, description); бандлы развёрнуты; пустые lot_name и ignored PN исключены |
Миграции:
migrations/023_vendor_partnumber_global_mapping.sqlmigrations/025_dedup_vendor_seen_by_partnumber.sqlmigrations/026_add_partnumber_books.sqlmigrations/027_fix_partnumber_books_version_length.sqlmigrations/028_add_description_to_partnumber_book_items.sql
Partnumber Book — инварианты снимка
При формировании qt_partnumber_book_items обязательно:
- Пустой
lot_nameисключается —TRIM(lot_name) = ''или NULL не попадают в снимок. - Ignored PN исключаются — партномера с
qt_vendor_partnumber_seen.is_ignored = trueне попадают в снимок. - Бандлы разворачиваются — каждая строка содержит конечный
lot_nameкомпонента, не имя бандла. descriptionберётся изlot_partnumbers.description; для развёрнутых бандлов — из родительской записи partnumber.- Удаление items при retention — явное (
DELETE WHERE book_id IN (...)), не через FK CASCADE.
Связанные модули
| Роль | Файл |
|---|---|
| Миграция | migrations/023_vendor_partnumber_global_mapping.sql |
| Миграция | migrations/025_dedup_vendor_seen_by_partnumber.sql |
| Миграции снимков | migrations/026–028 |
| Резолвер | internal/lotmatch/matcher.go |
| Сервис маппингов | internal/services/vendor_mapping.go |
| Сервис снимков | internal/services/partnumber_book.go |
| API | internal/handlers/pricing.go |
| Warehouse calc | internal/warehouse/snapshot.go |
| Stock import seen/ignore | internal/services/stock_import.go |
| Модели | internal/models/lot.go, internal/models/configuration.go |
| Роутинг | cmd/pfs/main.go |