Files
PriceForge/internal/models/competitor.go
Mikhail Chusavitin c53c484bde 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>
2026-03-13 12:58:41 +03:00

66 lines
3.3 KiB
Go

package models
import (
"time"
)
// Competitor represents a competitor profile
type Competitor struct {
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"size:255;not null" json:"name"`
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
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"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (Competitor) TableName() string {
return "qt_competitors"
}
// CompetitorQuote stores a historical price quote from a competitor.
// Mirrors stock_log structure: partnumber as-is from file, date = quote date entered by user.
// LOT resolution happens at pricelist build time via current p/n mappings.
type CompetitorQuote struct {
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
CompetitorID uint64 `gorm:"not null;index" json:"competitor_id"`
Partnumber string `gorm:"size:255;not null;index" json:"partnumber"`
Description *string `gorm:"size:500" json:"description,omitempty"`
Vendor *string `gorm:"size:255" json:"vendor,omitempty"`
Price float64 `gorm:"type:decimal(12,2);not null" json:"price"` // price in USD (always)
PriceLoccur *float64 `gorm:"type:decimal(12,2)" json:"price_loccur,omitempty"` // original price in local currency
Currency *string `gorm:"size:10" json:"currency,omitempty"` // local currency code
Qty float64 `gorm:"type:decimal(12,4);not null;default:1" json:"qty"`
Date time.Time `gorm:"type:date;not null" json:"date"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
Competitor *Competitor `gorm:"foreignKey:CompetitorID" json:"competitor,omitempty"`
}
func (CompetitorQuote) TableName() string {
return "partnumber_log_competitors"
}
// CompetitorColumnMapping defines how to read Excel columns for a competitor.
// Competitor files always contain partnumbers (never lot names directly).
// Partnumbers are resolved to lots via qt_partnumber_book_items.
type CompetitorColumnMapping struct {
Sheet int `json:"sheet"`
HeaderRow int `json:"header_row"`
PartnumberCol string `json:"partnumber_col"`
DescriptionCol string `json:"description_col,omitempty"`
VendorCol string `json:"vendor_col,omitempty"`
// PriceCol is the legacy single price column (treated as local currency, converted via rateToUSD).
PriceCol string `json:"price_col,omitempty"`
// PriceUSDCol is an explicit USD price column (no conversion needed).
PriceUSDCol string `json:"price_usd_col,omitempty"`
// PriceLocCurCol is an explicit local-currency price column (requires conversion via rateToUSD).
PriceLocCurCol string `json:"price_loccur_col,omitempty"`
QtyCol string `json:"qty_col,omitempty"`
SkipRows []int `json:"skip_rows,omitempty"`
}