package models import "time" // OfferType classifies whether a quote is public (market) or private (negotiated). type OfferType string const ( OfferTypePublic OfferType = "public" OfferTypePrivate OfferType = "private" ) // SupplierType classifies suppliers by their role in the supply chain. type SupplierType string const ( SupplierTypeTrader SupplierType = "trader" SupplierTypeSelf SupplierType = "self" SupplierTypeCompetitor SupplierType = "competitor" ) // PricelistTypeBySupplierType maps supplier_type to pricelist source. // world includes all supplier types. var PricelistTypeBySupplierType = map[SupplierType][]PricelistSource{ SupplierTypeTrader: {PricelistSourceEstimate}, SupplierTypeSelf: {PricelistSourceWarehouse}, SupplierTypeCompetitor: {PricelistSourceCompetitor}, } // SupplierTypesForPricelist returns the supplier_type values to include for a given pricelist source. func SupplierTypesForPricelist(src PricelistSource) []SupplierType { switch src { case PricelistSourceEstimate: return []SupplierType{SupplierTypeTrader} case PricelistSourceWarehouse: return []SupplierType{SupplierTypeSelf} case PricelistSourceCompetitor: return []SupplierType{SupplierTypeCompetitor} case PricelistSourceWorld: return []SupplierType{SupplierTypeTrader, SupplierTypeSelf, SupplierTypeCompetitor} default: return nil } } // PartsLog is the unified append-only quote journal. // All price quotes from all sources (warehouse, competitors, traders) are stored here. // The only permitted mutation after insert is filling lot_name/lot_category by the backfill job. type PartsLog struct { ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` SupplierCode string `gorm:"column:supplier_code;size:100;not null;default:''" json:"supplier_code"` Partnumber string `gorm:"column:partnumber;size:255;not null;default:''" json:"partnumber"` Vendor string `gorm:"column:vendor;size:255;not null;default:''" json:"vendor"` Description *string `gorm:"column:description;size:1000" json:"description,omitempty"` LotName *string `gorm:"column:lot_name;size:255" json:"lot_name,omitempty"` LotCategory *string `gorm:"column:lot_category;size:50" json:"lot_category,omitempty"` Price float64 `gorm:"column:price;type:decimal(12,4);not null" json:"price"` Qty *float64 `gorm:"column:qty;type:decimal(12,4)" json:"qty,omitempty"` OfferType OfferType `gorm:"column:offer_type;type:enum('private','public');not null;default:'public'" json:"offer_type"` LeadTimeWeeks *int `gorm:"column:lead_time_weeks" json:"lead_time_weeks,omitempty"` CreatedBy string `gorm:"column:created_by;size:100;not null;default:''" json:"created_by"` QuoteDate time.Time `gorm:"column:quote_date;type:date;not null" json:"quote_date"` CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` } func (PartsLog) TableName() string { return "parts_log" } // ImportFormat stores file format definitions used by the unified import pipeline. // column_mapping JSON uses the same structure as CompetitorColumnMapping. type ImportFormat struct { FormatCode string `gorm:"column:format_code;primaryKey;size:100" json:"format_code"` Name string `gorm:"column:name;size:255;not null" json:"name"` FileType string `gorm:"column:file_type;type:enum('csv','xlsx','mxl','json');not null" json:"file_type"` HeaderRow int `gorm:"column:header_row;not null;default:1" json:"header_row"` DataStartRow int `gorm:"column:data_start_row;not null;default:2" json:"data_start_row"` Delimiter *string `gorm:"column:delimiter;size:5" json:"delimiter,omitempty"` Encoding string `gorm:"column:encoding;size:20;not null;default:'utf-8'" json:"encoding"` ColumnMapping []byte `gorm:"column:column_mapping;type:json" json:"column_mapping,omitempty"` SampleFilename *string `gorm:"column:sample_filename;size:500" json:"sample_filename,omitempty"` Notes *string `gorm:"column:notes;type:text" json:"notes,omitempty"` IsActive bool `gorm:"column:is_active;not null;default:true" json:"is_active"` CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` } func (ImportFormat) TableName() string { return "qt_import_formats" } // IgnoreRule stores structured ignore patterns for the unified import pipeline. // Applied in order: ignore → mapping → insert. type IgnoreRule struct { ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` Field string `gorm:"column:field;type:enum('partnumber','vendor','description');not null" json:"field"` MatchType string `gorm:"column:match_type;type:enum('exact','glob','contains');not null;default:'exact'" json:"match_type"` Pattern string `gorm:"column:pattern;size:500;not null" json:"pattern"` Note *string `gorm:"column:note;size:500" json:"note,omitempty"` CreatedBy string `gorm:"column:created_by;size:100;not null;default:''" json:"created_by"` CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` } func (IgnoreRule) TableName() string { return "qt_ignore_rules" } // LotMetadataConfig represents per-(lot, pricelist_type) configuration. // This is the extended view of qt_lot_metadata with the new composite primary key. // Used for world/competitor/warehouse pricelist generation; estimate uses LotMetadata. type LotMetadataConfig struct { LotName string `gorm:"column:lot_name;primaryKey;size:255" json:"lot_name"` PricelistType string `gorm:"column:pricelist_type;primaryKey;size:20;not null;default:'estimate'" json:"pricelist_type"` PriceMethod string `gorm:"column:price_method;type:enum('manual','median','average','weighted_median');default:'median'" json:"price_method"` PeriodDays int `gorm:"column:period_days;not null;default:90" json:"period_days"` OnMissingQuotes string `gorm:"column:on_missing_quotes;type:enum('keep','drop');not null;default:'drop'" json:"on_missing_quotes"` IsHidden bool `gorm:"column:is_hidden;default:false" json:"is_hidden"` } func (LotMetadataConfig) TableName() string { return "qt_lot_metadata" }