Files
PriceForge/bible-local/vendor-mapping.md
2026-03-07 23:11:42 +03:00

122 lines
6.6 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
## Концепция
`qt_partnumber_book_items` — канонический контракт сопоставления для внешнего конфигуратора.
Одна строка на `partnumber`; состав хранится в `lots_json` как список `{lot_name, qty}`.
---
## Правила
### 1. Единственная запись на PN
Запрещено создавать несколько строк для одного `partnumber` в `qt_partnumber_book_items`.
### 2. Резолвинг
- Сначала точное совпадение по `partnumber` в `qt_partnumber_book_items`.
- Если состав PN содержит несколько LOT, резолвер возвращает весь список `{lot_name, qty}`.
- Vendor хранится как metadata в `qt_vendor_partnumber_seen`; канонический vendor-aware mapping в БД отсутствует.
### 4. Ignore-логика
- **Не использовать** `stock_ignore_rules` для новой логики.
- Использовать `qt_vendor_partnumber_seen.is_ignored`.
- `qt_vendor_partnumber_seen` хранится в формате **1 строка на partnumber** (vendor/source не участвуют в уникальности).
- Ignore применяется по `partnumber` (одинаково для записей с vendor и без vendor).
- Если внешняя система ошибочно записала в `qt_vendor_partnumber_seen.partnumber` значение, равное `lot.lot_name`,
такая строка считается мусором и не должна попадать в Global Vendor Mappings UI.
Такие seen-строки подлежат очистке, если для этого значения нет явной строки в `qt_partnumber_book_items`.
---
## Таблицы БД
| Таблица | Назначение |
|---------|------------|
| `qt_vendor_partnumber_seen` | Реестр seen-записей (уникально по `partnumber`) + флаг `is_ignored` |
| `qt_partnumber_books` | Версионированные книги PN; каждая книга хранит `partnumbers_json` — список PN, входящих в книгу |
| `qt_partnumber_book_items` | Глобальный source-of-truth каталог `partnumber -> lots_json`; без дубликатов по `partnumber`; `lots_json` хранит список `{lot_name, qty}` |
Миграции:
- `migrations/023_vendor_partnumber_global_mapping.sql` (historical)
- `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`
- `migrations/031_drop_is_primary_pn.sql`
- `migrations/032_drop_legacy_vendor_mapping_tables.sql`
---
## Partnumber Book — инварианты снимка
При формировании partnumber book обязательно:
1. **`qt_partnumber_book_items` содержит одну строку на `partnumber`** — дубликаты по PN не допускаются.
2. **`lots_json`** содержит полный состав PN как JSON-массив объектов `{lot_name, qty}`.
3. **Ignored PN исключаются** — партномера с `qt_vendor_partnumber_seen.is_ignored = true` не попадают ни в каталог, ни в `partnumbers_json` книги.
4. **Многокомпонентные PN не разворачиваются в отдельные строки** — PN сохраняется одной записью, а состав компонентов уходит в `lots_json`.
5. **`description`** берётся из `qt_partnumber_book_items.description`.
6. **`qt_partnumber_books.partnumbers_json`** хранит отсортированный список PN, входящих в конкретную книгу.
7. **Конфликт разных составов для одного PN недопустим** — если при построении книги один и тот же PN даёт разные `lots_json`, создание snapshot должно завершиться ошибкой.
## QuoteForge Read Contract
QuoteForge must read the active book header first:
```sql
SELECT id, version, created_at, created_by, partnumbers_json
FROM qt_partnumber_books
WHERE is_active = 1
ORDER BY created_at DESC, id DESC
LIMIT 1;
```
Then load item payloads from the catalog by PN list:
```sql
SELECT partnumber, lots_json, description
FROM qt_partnumber_book_items
WHERE partnumber IN (...PNs from partnumbers_json...);
```
Contract notes:
- `partnumbers_json` is the membership snapshot of the selected book.
- `qt_partnumber_book_items` is the current canonical catalog of PN compositions.
- `lots_json` format is JSON array: `[{"lot_name":"CPU_X","qty":2},{"lot_name":"RAM_X","qty":4}]`
- QuoteForge must not expect `lot_name` or `is_primary_pn` columns in `qt_partnumber_book_items`.
---
## Связанные модули
| Роль | Файл |
|------|------|
| Миграция | `migrations/025_dedup_vendor_seen_by_partnumber.sql` |
| Миграции снимков | `migrations/026032` |
| Резолвер | `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`