docs: add bible/ as single source of architectural truth

- Created bible/ with hierarchical documentation (architecture, pricelists,
  vendor mapping, background tasks, data rules, patterns, API, operations, history)
- CLAUDE.md reduced to one instruction: read and follow the bible
- README.md reduced to quick start only
- Removed MEMORY.md and csv_export.md (content consolidated into bible/)
- Fixed stale facts found during audit: weighted_avg (not weighted_median),
  correct API route names (/export-csv, /recalculate-all)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-02-20 14:16:47 +03:00
parent c22328bf03
commit 7d402b756d
14 changed files with 1109 additions and 606 deletions

158
CLAUDE.md
View File

@@ -1,157 +1,3 @@
# PriceForge - Claude Code Instructions
# PriceForge
## Overview
Администратор цен для работы с прайслистами, складскими справками и алертами.
Источник данных: MariaDB. Локальная БД используется только для настроек и техданных приложения.
## Scope
- Управление ценами и котировками компонентов
- Управление прайслистами и их версиями
- Импорт складских справок
- Алерты по ценам/свежести данных
- Подготовка к интеграции с API B2B площадок для автоматических котировок
## API Endpoints
| Group | Endpoints |
|-------|-----------|
| Setup | GET/POST /setup, POST /setup/test, GET /setup/status |
| System | GET /health, GET /api/ping, GET /api/db-status, GET /api/current-user |
| Components | GET /api/components, GET /api/components/:lot_name, GET /api/categories |
| Pricelists | CRUD /api/pricelists, GET /api/pricelists/latest |
| Pricing Admin | /api/admin/pricing/* |
| Sync (diagnostics/minimal) | GET /api/sync/status, GET /api/sync/info, POST /api/sync/components, POST /api/sync/pricelists |
## Commands
```bash
# Development
go run ./cmd/pfs
make run
# Build
make build
make build-release
# Version
./bin/pfs -version
```
## Tech Stack
Go 1.22+ | Gin | GORM | MariaDB 11 | htmx + Tailwind CDN | excelize
## Pricelist Sources
### Estimate
- Создается из snapshot текущих цен компонентов (`qt_lot_metadata`)
- Сохраняет все настройки ценообразования: `price_period_days`, `price_coefficient`, `manual_price`, `meta_prices`
- Это основной прайслист для расчетов и оценок
### Warehouse
- Создается из складских справок (`stock_log`)
- **ВАЖНО**: НЕ должен загружать настройки цен из `lot_metadata`
- **КРИТИЧНО**: В warehouse прайслист попадают ТОЛЬКО сопоставленные partnumbers (есть в `lot_partnumbers`)
- Несопоставленные partnumbers НЕ должны попадать в прайслист ни в каком виде
- Использует только weighted median (взвешенная медиана по количеству) из stock_log
- Поле `price_method` всегда содержит "weighted_median"
- Не содержит `price_period_days`, `price_coefficient`, `manual_price`, `meta_prices`
- Это фактические складские цены без дополнительной обработки
### Competitor
- Зарезервировано для будущих интеграций с API B2B площадок
## Background Tasks (Фоновые задачи)
**ВАЖНО**: Все долгие операции выполняются через Task Manager, НЕ через SSE (Server-Sent Events).
### Принцип работы:
1. **Backend**: Хендлер создает фоновую задачу через `taskManager.Submit()` и возвращает `task_id`
2. **Frontend**: Получает `task_id` и делает polling через `/api/tasks/:id` каждые 500ms
3. **Уведомления**: Система автоматически показывает toast-уведомления при завершении задачи
### Типы задач:
- `TaskTypeRecalculate` - пересчет всех цен компонентов
- `TaskTypeStockImport` - импорт складской справки из .mxl/.xlsx
- `TaskTypePricelistCreate` - создание прайслиста (estimate/warehouse/competitor)
### Структура задачи:
```go
type Task struct {
ID string // UUID задачи
Type TaskType // Тип задачи
Status TaskStatus // running | completed | error
Progress int // 0-100
Message string // Текущее сообщение для UI
Result map[string]interface{} // Результат при completed
Error string // Текст ошибки при error
CreatedAt time.Time
DoneAt *time.Time
}
```
### Пример использования:
```go
taskID := h.taskManager.Submit(tasks.TaskTypeStockImport, func(ctx context.Context, progressCb func(int, string)) (map[string]interface{}, error) {
// Выполнение задачи
progressCb(50, "Половина выполнена")
// Возврат результата
return map[string]interface{}{
"rows_total": 100,
"inserted": 95,
}, nil
})
c.JSON(http.StatusOK, gin.H{"task_id": taskID})
```
## Категории товаров (lot_category)
### Источник данных:
Категории берутся из таблицы `lot` (поле `lot_category`), НЕ из `qt_lot_metadata`.
### При создании прайслистов:
1. **Estimate**: Загружаем категории из `lot.lot_category` для всех компонентов
2. **Warehouse**: Загружаем категории из `lot.lot_category` для всех позиций
3. Сохраняем в поле `lot_category` таблицы `qt_pricelist_items`
### В модели:
```go
type PricelistItem struct {
LotCategory *string `gorm:"column:lot_category;size:50" json:"category,omitempty"`
// JSON поле называется "category" для фронтенда
}
```
### ВАЖНО:
- Категория НЕ виртуальное поле - она сохраняется в БД при создании прайслиста
- JOIN с таблицей `lot` нужен только для `lot_description`, категория уже есть в `qt_pricelist_items`
- Никогда не получать категорию из имени элемента (lot name/названия). Всегда использовать категорию из `PricelistItem` (поле `lot_category` / JSON `category`).
## Notes
- Главная страница должна вести на `/admin/pricing`.
- В UI не должно быть ссылок на конфигуратор, проекты и экспорт.
- **НЕ использовать SSE** - только фоновые задачи через Task Manager.
## Vendor Mapping Rules (2026-02-18)
- `lot_partnumbers` is the canonical mapping contract for external configurator.
- Do not reintroduce multi-row mapping for one key: one `(vendor, partnumber)` -> one `lot_name`.
- Resolver behavior is fixed:
- first exact `vendor + partnumber`,
- then fallback `vendor='' + partnumber`.
- Composite vendor partnumbers must be implemented via internal bundle tables:
- `qt_lot_bundles`
- `qt_lot_bundle_items`
- Bundle LOT is internal and hidden in regular LOT UI by default.
- Ignore behavior:
- do not use `stock_ignore_rules` for new logic,
- use `qt_vendor_partnumber_seen.is_ignored` (cross-source).
- Client compatibility requirement:
- client still consumes LOT-based pricelists,
- bundle expansion/allocation happens only inside PriceForge warehouse calculations.
### Related modules
- Migration: `migrations/023_vendor_partnumber_global_mapping.sql`
- Service/API: `internal/services/vendor_mapping.go`, `internal/handlers/pricing.go`
- Resolver: `internal/lotmatch/matcher.go`
- Warehouse calc: `internal/warehouse/snapshot.go`
- Stock import seen+ignore: `internal/services/stock_import.go`
Read [`bible/BIBLE.md`](bible/BIBLE.md) and all files it references before making any changes. The bible is the source of truth. Follow its rules without exception.