Files
PriceForge/bible/vendor-mapping.md
2026-02-27 16:49:39 +03:00

122 lines
6.1 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.
# 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-цены:
1. Fallback: взять из предыдущего активного warehouse-прайслиста.
2. Если нет предыдущего — цена `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.sql`
- `migrations/025_dedup_vendor_seen_by_partnumber.sql`
- `migrations/026_add_partnumber_books.sql`
- `migrations/027_fix_partnumber_books_version_length.sql`
- `migrations/028_add_description_to_partnumber_book_items.sql`
---
## Partnumber Book — инварианты снимка
При формировании `qt_partnumber_book_items` обязательно:
1. **Пустой `lot_name` исключается**`TRIM(lot_name) = ''` или NULL не попадают в снимок.
2. **Ignored PN исключаются** — партномера с `qt_vendor_partnumber_seen.is_ignored = true` не попадают в снимок.
3. **Бандлы разворачиваются** — каждая строка содержит конечный `lot_name` компонента, не имя бандла.
4. **`description`** берётся из `lot_partnumbers.description`; для развёрнутых бандлов — из родительской записи partnumber.
5. **Удаление 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/026028` |
| Резолвер | `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` |
---
## CSV Import (Global Vendor Mappings UI)
- Формат CSV для Excel (RU locale): разделитель `;`
- Кодировка: `UTF-8` (BOM допускается и поддерживается)
- Рекомендуемые колонки: `vendor;partnumber;lot_name;description;ignore`
- Допустим импорт как с заголовком, так и без заголовка (в фиксированном порядке колонок выше)
- Пустые строки пропускаются
- Для строки обязательны `partnumber` и (`lot_name` или заполненный `ignore`)
- Если `ignore` заполнен и `lot_name` пустой, строка помечается как ignored в `qt_vendor_partnumber_seen`