- New unified append-only quote log table parts_log replaces three separate log tables (stock_log, partnumber_log_competitors, lot_log) - Migrations 042-049: extend supplier, create parts_log/import_formats/ ignore_rules, rework qt_lot_metadata composite PK, add lead_time_weeks to pricelist_items, backfill data, migrate ignore rules - New services: PartsLogBackfillService, ImportFormatService, UnifiedImportService; new world pricelist type (all supplier types) - qt_lot_metadata PK changed to (lot_name, pricelist_type); all queries now filter WHERE pricelist_type='estimate' - Fix pre-existing bug: qt_component_usage_stats column names quotes_last30d/quotes_last7d (no underscore) — added explicit gorm tags - Bible: full table inventory, baseline schema snapshot, updated pricelist/ data-rules/api/history/architecture docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
4.3 KiB
Go
103 lines
4.3 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
type PricelistSource string
|
|
|
|
const (
|
|
PricelistSourceEstimate PricelistSource = "estimate"
|
|
PricelistSourceWarehouse PricelistSource = "warehouse"
|
|
PricelistSourceCompetitor PricelistSource = "competitor"
|
|
PricelistSourceWorld PricelistSource = "world"
|
|
)
|
|
|
|
func (s PricelistSource) IsValid() bool {
|
|
switch s {
|
|
case PricelistSourceEstimate, PricelistSourceWarehouse, PricelistSourceCompetitor, PricelistSourceWorld:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func NormalizePricelistSource(source string) PricelistSource {
|
|
switch PricelistSource(source) {
|
|
case PricelistSourceWarehouse:
|
|
return PricelistSourceWarehouse
|
|
case PricelistSourceCompetitor:
|
|
return PricelistSourceCompetitor
|
|
case PricelistSourceWorld:
|
|
return PricelistSourceWorld
|
|
default:
|
|
return PricelistSourceEstimate
|
|
}
|
|
}
|
|
|
|
// Pricelist represents a versioned snapshot of prices
|
|
type Pricelist struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Source string `gorm:"size:20;not null;default:'estimate';uniqueIndex:idx_qt_pricelists_source_version,priority:1;index:idx_qt_pricelists_source_created_at,priority:1" json:"source"`
|
|
Version string `gorm:"size:20;not null;uniqueIndex:idx_qt_pricelists_source_version,priority:2" json:"version"` // Format: YYYY-MM-DD-NNN
|
|
Notification string `gorm:"size:500" json:"notification"` // Notification shown in configurator
|
|
CreatedAt time.Time `gorm:"index:idx_qt_pricelists_source_created_at,priority:2,sort:desc" json:"created_at"`
|
|
CreatedBy string `gorm:"size:100" json:"created_by"`
|
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
|
UsageCount int `gorm:"default:0" json:"usage_count"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
ItemCount int `gorm:"-" json:"item_count,omitempty"` // Virtual field for display
|
|
}
|
|
|
|
func (Pricelist) TableName() string {
|
|
return "qt_pricelists"
|
|
}
|
|
|
|
// PricelistItem represents a single item in a pricelist
|
|
type PricelistItem struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
PricelistID uint `gorm:"not null;index:idx_pricelist_lot" json:"pricelist_id"`
|
|
LotName string `gorm:"size:255;not null;index:idx_pricelist_lot" json:"lot_name"`
|
|
LotCategory *string `gorm:"column:lot_category;size:50" json:"category,omitempty"`
|
|
Price float64 `gorm:"type:decimal(12,2);not null" json:"price"`
|
|
PriceMethod string `gorm:"size:20" json:"price_method"`
|
|
|
|
// Price calculation settings (snapshot from qt_lot_metadata)
|
|
PricePeriodDays int `gorm:"default:90" json:"price_period_days"`
|
|
PriceCoefficient float64 `gorm:"type:decimal(5,2);default:0" json:"price_coefficient"`
|
|
ManualPrice *float64 `gorm:"type:decimal(12,2)" json:"manual_price,omitempty"`
|
|
MetaPrices string `gorm:"size:1000" json:"meta_prices,omitempty"`
|
|
|
|
// lead_time_weeks: 99 if no data available, NULL if not applicable.
|
|
LeadTimeWeeks *int `gorm:"column:lead_time_weeks" json:"lead_time_weeks,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/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 {
|
|
return "qt_pricelist_items"
|
|
}
|
|
|
|
// PricelistSummary is used for list views
|
|
type PricelistSummary struct {
|
|
ID uint `json:"id"`
|
|
Source string `json:"source"`
|
|
Version string `json:"version"`
|
|
Notification string `json:"notification"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
CreatedBy string `json:"created_by"`
|
|
IsActive bool `json:"is_active"`
|
|
UsageCount int `json:"usage_count"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
ItemCount int64 `json:"item_count"`
|
|
}
|