Files
PriceForge/internal/models/competitor.go
Mikhail Chusavitin f48615e8a9 Modularize Go files, extract JS to static, implement competitor pricelists
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>
2026-03-13 07:44:10 +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
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"`
}