Replace competitor discount with price_uplift; stock pricelist detail UI

- Drop `expected_discount_pct`, add `price_uplift DECIMAL(8,4) DEFAULT 1.3`
  to `qt_competitors` (migration 040); formula: effective_price = price / uplift
- Extend `LoadLotMetrics` to return per-PN qty map (`pnQtysByLot`)
- Add virtual fields `CompetitorNames`, `PriceSpreadPct`, `PartnumberQtys`
  to `PricelistItem`; populate via `enrichWarehouseItems` / `enrichCompetitorItems`
- Competitor quotes filtered to qty > 0 before lot resolution
- New "stock layout" on pricelist detail page for warehouse/competitor:
  Partnumbers column (PN + qty, only qty>0), Поставщик column, no Настройки/Доступно
- Spread badge ±N% shown next to price for competitor rows
- Bible updated: pricelist.md, history.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-03-13 12:58:41 +03:00
parent 9b9b343f0c
commit c53c484bde
15 changed files with 456 additions and 86 deletions

View File

@@ -545,6 +545,63 @@ Warehouse and competitor pricelist items now store explicit, meaningful values i
---
## 2026-03-13: Price Uplift Replaces Competitor Discount; Stock Pricelist Detail UI
### Decision
1. Replaced `expected_discount_pct` with `price_uplift` in competitor model.
2. Completely reworked pricelist detail page for warehouse and competitor sources into a compact "stock layout".
### What changed
**Competitor uplift**:
- `qt_competitors.expected_discount_pct` dropped; `price_uplift DECIMAL(8,4) DEFAULT 1.3` added (migration `040_competitor_uplift.sql`).
- Formula at pricelist build time: `effective_price = quote_price / price_uplift`.
- `internal/models/competitor.go`: `PriceUplift float64`.
- `internal/handlers/competitor.go`: Create/Update endpoints use `price_uplift`; default 1.3 when ≤0.
- `internal/services/competitor_import.go`: `RebuildPricelist` and `buildCompetitorPricelistItems` use uplift divisor.
- `web/templates/competitors.html`, `web/static/js/competitors.js`: UI field renamed "Аплифт", uses `price_uplift`.
**Per-lot enrichment**:
- `internal/warehouse/snapshot.go` `LoadLotMetrics` returns 3 maps: `qtyByLot`, `partnumbersByLot`, `pnQtysByLot` (PN → qty).
- `internal/models/pricelist.go` `PricelistItem` added virtual fields: `CompetitorNames []string`, `PriceSpreadPct *float64`, `PartnumberQtys map[string]float64`.
- `internal/repository/pricelist.go` added `enrichWarehouseItems` (fills PartnumberQtys from stock_log) and `enrichCompetitorItems` (fills CompetitorNames, Partnumbers from quotes with qty>0, PriceSpreadPct).
- `internal/repository/competitor.go` `GetLatestQuotesByPN`: added `AND qty > 0` filter.
- `internal/handlers/pricing_lots.go`: updated to 4-return `LoadLotMetrics` call.
**Pricelist detail UI**:
- Two layouts: `estimate` (with Настройки) and stock (`warehouse`/`competitor`, with Partnumbers + Поставщик columns, no Настройки/Доступно).
- Partnumbers shown only if `partnumber_qtys[pn] > 0`; format `PN (qty шт.)`, max 4 + overflow badge.
- Поставщик: blue competitor name badges (competitor) or grey "склад" text (warehouse).
- Price spread `±N%` badge in amber next to price for competitor rows.
- `web/static/js/pricelist_detail.js`: full rendering overhaul.
- `web/templates/pricelist_detail.html`: column ids for JS toggle.
### Rationale
- "Discount %" was ambiguous and error-prone (mixed % vs ratio). Uplift as a divisor is explicit.
- Default uplift 1.3 ≈ 23% margin, which matches the actual operational baseline.
- Stock layout needed because warehouse/competitor pricelists have fundamentally different metadata (suppliers, per-PN stock) than estimate pricelists.
### Constraints
- `price_uplift` must be > 0; if omitted in API it defaults to 1.3.
- Only PNs with `qty > 0` appear in Partnumbers column (no phantom entries from catalog).
- `LoadLotMetrics` always returns 4 values; callers that don't need pnQtysByLot must use `_`.
### Files
- Migration: `migrations/040_competitor_uplift.sql`
- Models: `internal/models/competitor.go`, `internal/models/pricelist.go`
- Repository: `internal/repository/pricelist.go`, `internal/repository/competitor.go`
- Warehouse: `internal/warehouse/snapshot.go`
- Handlers: `internal/handlers/competitor.go`, `internal/handlers/pricing_lots.go`
- Services: `internal/services/competitor_import.go`
- UI: `web/templates/pricelist_detail.html`, `web/templates/competitors.html`, `web/static/js/pricelist_detail.js`, `web/static/js/competitors.js`
- Docs: `bible-local/pricelist.md`
---
## Architecture Conventions
> All future architectural decisions must be documented here with: