Implement warehouse/lot pricing updates and configurator performance fixes

This commit is contained in:
2026-02-07 05:20:35 +03:00
parent c1a31e5ee0
commit 7c741ff675
26 changed files with 1701 additions and 305 deletions

View File

@@ -47,6 +47,35 @@ func TestParseMXLRows(t *testing.T) {
}
}
func TestParseMXLRows_EmptyQtyMarkedInvalid(t *testing.T) {
content := strings.Join([]string{
`MOXCEL`,
`{16,2,{1,1,{"ru","Папка"}},0},1,`,
`{16,2,{1,1,{"ru","Артикул"}},0},2,`,
`{16,2,{1,1,{"ru","Описание"}},0},3,`,
`{16,2,{1,1,{"ru","Вендор"}},0},4,`,
`{16,2,{1,1,{"ru","Стоимость"}},0},5,`,
`{16,2,{1,1,{"ru","Свободно"}},0},6,`,
`{16,2,{1,1,{"ru","Серверы"}},0},1,`,
`{16,2,{1,1,{"ru","CPU_X"}},0},2,`,
`{16,2,{1,1,{"ru","Процессор"}},0},3,`,
`{16,2,{1,1,{"ru","AMD"}},0},4,`,
`{16,2,{1,1,{"ru","125,50"}},0},5,`,
`{16,2,{1,1,{"ru",""}},0},6,`,
}, "\n")
rows, err := parseMXLRows([]byte(content))
if err != nil {
t.Fatalf("parseMXLRows: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(rows))
}
if !rows[0].QtyInvalid {
t.Fatalf("expected QtyInvalid=true for empty qty")
}
}
func TestParseXLSXRows(t *testing.T) {
xlsx := buildMinimalXLSX(t, []string{
"Папка", "Артикул", "Описание", "Вендор", "Стоимость", "Свободно",
@@ -114,9 +143,9 @@ func TestImportNoValidRowsKeepsStockLog(t *testing.T) {
}
existing := models.StockLog{
Lot: "CPU_A",
Date: time.Now(),
Price: 10,
Partnumber: "CPU_A",
Date: time.Now(),
Price: 10,
}
if err := db.Create(&existing).Error; err != nil {
t.Fatalf("seed stock_log: %v", err)
@@ -152,14 +181,14 @@ func TestReplaceStockLogs(t *testing.T) {
t.Fatalf("automigrate stock_log: %v", err)
}
if err := db.Create(&models.StockLog{Lot: "OLD", Date: time.Now(), Price: 1}).Error; err != nil {
if err := db.Create(&models.StockLog{Partnumber: "OLD", Date: time.Now(), Price: 1}).Error; err != nil {
t.Fatalf("seed old row: %v", err)
}
svc := NewStockImportService(db, nil)
records := []models.StockLog{
{Lot: "NEW_1", Date: time.Now(), Price: 2},
{Lot: "NEW_2", Date: time.Now(), Price: 3},
{Partnumber: "NEW_1", Date: time.Now(), Price: 2},
{Partnumber: "NEW_2", Date: time.Now(), Price: 3},
}
deleted, inserted, err := svc.replaceStockLogs(records)
@@ -171,14 +200,73 @@ func TestReplaceStockLogs(t *testing.T) {
}
var rows []models.StockLog
if err := db.Order("lot").Find(&rows).Error; err != nil {
if err := db.Order("partnumber").Find(&rows).Error; err != nil {
t.Fatalf("read rows: %v", err)
}
if len(rows) != 2 || rows[0].Lot != "NEW_1" || rows[1].Lot != "NEW_2" {
if len(rows) != 2 || rows[0].Partnumber != "NEW_1" || rows[1].Partnumber != "NEW_2" {
t.Fatalf("unexpected rows after replace: %#v", rows)
}
}
func TestWeightedMedian(t *testing.T) {
got := weightedMedian([]weightedPricePoint{
{price: 10, weight: 1},
{price: 20, weight: 3},
{price: 50, weight: 1},
})
if got != 20 {
t.Fatalf("expected weighted median 20, got %v", got)
}
}
func TestWeightedMedianFallbackToMedianWhenNoWeights(t *testing.T) {
got := weightedMedian([]weightedPricePoint{
{price: 10, weight: 0},
{price: 20, weight: 0},
{price: 30, weight: 0},
})
if got != 20 {
t.Fatalf("expected fallback median 20, got %v", got)
}
}
func TestBuildWarehousePricelistItems_UsesPrefixResolver(t *testing.T) {
db := openTestDB(t)
if err := db.AutoMigrate(&models.StockLog{}, &models.Lot{}, &models.LotPartnumber{}); err != nil {
t.Fatalf("automigrate: %v", err)
}
if err := db.Create(&models.Lot{LotName: "CPU_A"}).Error; err != nil {
t.Fatalf("seed lot: %v", err)
}
qty1 := 3.0
qty2 := 1.0
now := time.Now()
rows := []models.StockLog{
{Partnumber: "CPU_A-001", Date: now, Price: 100, Qty: &qty1},
{Partnumber: "CPU_A-XYZ", Date: now, Price: 120, Qty: &qty2},
}
if err := db.Create(&rows).Error; err != nil {
t.Fatalf("seed stock_log: %v", err)
}
svc := NewStockImportService(db, nil)
items, err := svc.buildWarehousePricelistItems()
if err != nil {
t.Fatalf("buildWarehousePricelistItems: %v", err)
}
if len(items) != 1 {
t.Fatalf("expected 1 item, got %d", len(items))
}
if items[0].LotName != "CPU_A" {
t.Fatalf("expected lot CPU_A, got %s", items[0].LotName)
}
if items[0].Price != 100 {
t.Fatalf("expected weighted median 100, got %v", items[0].Price)
}
}
func openTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})