Implement global vendor mappings with bundle support and seen-based ignore

This commit is contained in:
Mikhail Chusavitin
2026-02-18 19:54:07 +03:00
parent b94dd3d015
commit c22328bf03
21 changed files with 2048 additions and 342 deletions

View File

@@ -93,6 +93,86 @@ func TestLoadLotMetricsLatestOnlyIncludesPartnumbers(t *testing.T) {
}
}
func TestComputePricelistItemsFromStockLog_BundleAllocation(t *testing.T) {
db := openTestDB(t)
if err := db.AutoMigrate(
&models.Lot{},
&models.LotPartnumber{},
&models.LotBundle{},
&models.LotBundleItem{},
&models.StockLog{},
); err != nil {
t.Fatalf("automigrate: %v", err)
}
if err := db.Exec(`CREATE TABLE qt_lot_metadata (lot_name TEXT PRIMARY KEY, current_price REAL NULL)`).Error; err != nil {
t.Fatalf("create qt_lot_metadata: %v", err)
}
cat := "BUNDLE"
if err := db.Create(&models.Lot{LotName: "BND_SERVER", LotCategory: &cat}).Error; err != nil {
t.Fatalf("seed bundle lot: %v", err)
}
catComp := "COMP"
if err := db.Create(&models.Lot{LotName: "CPU_X", LotCategory: &catComp}).Error; err != nil {
t.Fatalf("seed cpu lot: %v", err)
}
if err := db.Create(&models.Lot{LotName: "RAM_X", LotCategory: &catComp}).Error; err != nil {
t.Fatalf("seed ram lot: %v", err)
}
if err := db.Create(&models.LotPartnumber{Vendor: "VendorA", Partnumber: "SERVER-A", LotName: "BND_SERVER"}).Error; err != nil {
t.Fatalf("seed mapping: %v", err)
}
if err := db.Create(&models.LotBundle{BundleLotName: "BND_SERVER", IsActive: true}).Error; err != nil {
t.Fatalf("seed bundle: %v", err)
}
if err := db.Create(&[]models.LotBundleItem{
{BundleLotName: "BND_SERVER", LotName: "CPU_X", Qty: 2},
{BundleLotName: "BND_SERVER", LotName: "RAM_X", Qty: 1},
}).Error; err != nil {
t.Fatalf("seed bundle items: %v", err)
}
cpuPrice := 100.0
ramPrice := 50.0
if err := db.Exec(`INSERT INTO qt_lot_metadata (lot_name, current_price) VALUES (?, ?)`, "CPU_X", cpuPrice).Error; err != nil {
t.Fatalf("seed cpu meta: %v", err)
}
if err := db.Exec(`INSERT INTO qt_lot_metadata (lot_name, current_price) VALUES (?, ?)`, "RAM_X", ramPrice).Error; err != nil {
t.Fatalf("seed ram meta: %v", err)
}
qty := 3.0
vendor := "VendorA"
if err := db.Create(&models.StockLog{
Partnumber: "SERVER-A",
Vendor: &vendor,
Date: time.Now(),
Price: 300,
Qty: &qty,
}).Error; err != nil {
t.Fatalf("seed stock: %v", err)
}
items, err := ComputePricelistItemsFromStockLog(db)
if err != nil {
t.Fatalf("ComputePricelistItemsFromStockLog: %v", err)
}
if len(items) != 2 {
t.Fatalf("expected 2 items, got %d", len(items))
}
priceByLot := map[string]float64{}
for _, item := range items {
priceByLot[item.LotName] = item.Price
}
// Weights: CPU=2*100=200, RAM=1*50=50 => total=250; row price=300
// CPU=300*200/250=240, RAM=300*50/250=60
if math.Abs(priceByLot["CPU_X"]-240) > 0.001 {
t.Fatalf("expected CPU_X 240, got %f", priceByLot["CPU_X"])
}
if math.Abs(priceByLot["RAM_X"]-60) > 0.001 {
t.Fatalf("expected RAM_X 60, got %f", priceByLot["RAM_X"])
}
}
func openTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})