Go refactoring: - Split handlers/pricing.go (2446→291 lines) into 5 focused files - Split services/stock_import.go (1334→~400 lines) into stock_mappings.go + stock_parse.go - Split services/sync/service.go (1290→~250 lines) into 3 files JS extraction: - Move all inline <script> blocks to web/static/js/ (6 files) - Templates reduced: admin_pricing 2873→521, lot 1531→304, vendor_mappings 1063→169, etc. Competitor pricelists (migrations 033-039): - qt_competitors + partnumber_log_competitors tables - Excel import with column mapping, dedup, bulk insert - p/n→lot resolution via weighted_median, discount applied - Unmapped p/ns written to qt_vendor_partnumber_seen - Quote counts (unique/total) shown on /admin/competitors - price_method="weighted_median", price_period_days=0 stored explicitly Fix price_method/price_period_days for warehouse items: - warehouse: weighted_avg, period=0 - competitor: weighted_median, period=0 - Removes misleading DB defaults (was: median/90) Update bible: architecture.md, pricelist.md, history.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
3.3 KiB
Go
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
|
|
ExpectedDiscountPct float64 `gorm:"type:decimal(5,2);not null;default:0" json:"expected_discount_pct"`
|
|
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"`
|
|
}
|