184 lines
5.5 KiB
Go
184 lines
5.5 KiB
Go
package warehouse
|
|
|
|
import (
|
|
"math"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.mchus.pro/mchus/priceforge/internal/models"
|
|
"github.com/glebarez/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestComputePricelistItemsFromStockLog(t *testing.T) {
|
|
db := openTestDB(t)
|
|
if err := db.AutoMigrate(&models.Lot{}, &models.LotPartnumber{}, &models.StockLog{}); err != nil {
|
|
t.Fatalf("automigrate: %v", err)
|
|
}
|
|
|
|
if err := db.Create(&models.Lot{LotName: "CPU_X"}).Error; err != nil {
|
|
t.Fatalf("seed lot: %v", err)
|
|
}
|
|
if err := db.Create(&models.LotPartnumber{Partnumber: "PN-CPU-X", LotName: "CPU_X"}).Error; err != nil {
|
|
t.Fatalf("seed mapping: %v", err)
|
|
}
|
|
|
|
qtySmall := 1.0
|
|
qtyBig := 9.0
|
|
now := time.Now()
|
|
rows := []models.StockLog{
|
|
{Partnumber: "PN CPU X", Date: now, Price: 100, Qty: &qtySmall},
|
|
{Partnumber: "CPU_X-EXTRA", Date: now, Price: 200, Qty: &qtyBig},
|
|
}
|
|
if err := db.Create(&rows).Error; err != nil {
|
|
t.Fatalf("seed stock rows: %v", err)
|
|
}
|
|
|
|
items, err := ComputePricelistItemsFromStockLog(db)
|
|
if err != nil {
|
|
t.Fatalf("ComputePricelistItemsFromStockLog: %v", err)
|
|
}
|
|
if len(items) != 1 {
|
|
t.Fatalf("expected 1 item, got %d", len(items))
|
|
}
|
|
if items[0].LotName != "CPU_X" {
|
|
t.Fatalf("expected lot CPU_X, got %s", items[0].LotName)
|
|
}
|
|
if math.Abs(items[0].Price-190) > 0.001 {
|
|
t.Fatalf("expected weighted average 190, got %f", items[0].Price)
|
|
}
|
|
}
|
|
|
|
func TestLoadLotMetricsLatestOnlyIncludesPartnumbers(t *testing.T) {
|
|
db := openTestDB(t)
|
|
if err := db.AutoMigrate(&models.Lot{}, &models.LotPartnumber{}, &models.StockLog{}); err != nil {
|
|
t.Fatalf("automigrate: %v", err)
|
|
}
|
|
|
|
if err := db.Create(&models.Lot{LotName: "CPU_X"}).Error; err != nil {
|
|
t.Fatalf("seed lot: %v", err)
|
|
}
|
|
if err := db.Create(&models.LotPartnumber{Partnumber: "PN-MAPPED", LotName: "CPU_X"}).Error; err != nil {
|
|
t.Fatalf("seed mapping: %v", err)
|
|
}
|
|
|
|
oldDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
newDate := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
|
|
oldQty := 10.0
|
|
newQty := 3.0
|
|
rows := []models.StockLog{
|
|
{Partnumber: "CPU_X-001", Date: oldDate, Price: 100, Qty: &oldQty},
|
|
{Partnumber: "CPU_X-001", Date: newDate, Price: 100, Qty: &newQty},
|
|
}
|
|
if err := db.Create(&rows).Error; err != nil {
|
|
t.Fatalf("seed stock rows: %v", err)
|
|
}
|
|
|
|
qtyByLot, pnsByLot, err := LoadLotMetrics(db, []string{"CPU_X"}, true)
|
|
if err != nil {
|
|
t.Fatalf("LoadLotMetrics: %v", err)
|
|
}
|
|
|
|
if got := qtyByLot["CPU_X"]; math.Abs(got-3.0) > 0.001 {
|
|
t.Fatalf("expected latest qty 3, got %f", got)
|
|
}
|
|
|
|
pns := pnsByLot["CPU_X"]
|
|
if !slices.Contains(pns, "PN-MAPPED") {
|
|
t.Fatalf("expected mapped PN-MAPPED in partnumbers, got %v", pns)
|
|
}
|
|
if !slices.Contains(pns, "CPU_X-001") {
|
|
t.Fatalf("expected stock CPU_X-001 in partnumbers, got %v", pns)
|
|
}
|
|
}
|
|
|
|
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{})
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
return db
|
|
}
|