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

@@ -11,7 +11,7 @@ type Competitor struct {
Code string `gorm:"size:100;not null;uniqueIndex" json:"code"`
DeliveryBasis string `gorm:"size:50;not null;default:DDP" json:"delivery_basis"`
Currency string `gorm:"size:10;not null;default:USD" json:"currency"` // currency of prices in file
ExpectedDiscountPct float64 `gorm:"type:decimal(5,2);not null;default:0" json:"expected_discount_pct"`
PriceUplift float64 `gorm:"type:decimal(8,4);not null;default:1.3" json:"price_uplift"` // effective_price = price / price_uplift
ColumnMapping []byte `gorm:"type:json" json:"column_mapping,omitempty"`
IsActive bool `gorm:"not null;default:true" json:"is_active"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`

View File

@@ -66,11 +66,15 @@ type PricelistItem struct {
MetaPrices string `gorm:"size:1000" json:"meta_prices,omitempty"`
// Virtual fields for display (not stored in qt_pricelist_items table)
// LotDescription is populated via JOIN in SQL queries
// AvailableQty and Partnumbers are populated programmatically for warehouse pricelists
LotDescription string `gorm:"-" json:"lot_description,omitempty"`
AvailableQty *float64 `gorm:"-" json:"available_qty,omitempty"`
Partnumbers []string `gorm:"-" json:"partnumbers,omitempty"`
// LotDescription is populated via JOIN in SQL queries.
// AvailableQty and Partnumbers are populated programmatically for warehouse/competitor pricelists.
// CompetitorNames and PriceSpreadPct are populated for competitor pricelists.
LotDescription string `gorm:"-" json:"lot_description,omitempty"`
AvailableQty *float64 `gorm:"-" json:"available_qty,omitempty"`
Partnumbers []string `gorm:"-" json:"partnumbers,omitempty"`
CompetitorNames []string `gorm:"-" json:"competitor_names,omitempty"`
PriceSpreadPct *float64 `gorm:"-" json:"price_spread_pct,omitempty"`
PartnumberQtys map[string]float64 `gorm:"-" json:"partnumber_qtys,omitempty"` // pn → qty from competitor quotes
}
func (PricelistItem) TableName() string {