package repository import ( "fmt" "testing" "time" "git.mchus.pro/mchus/quoteforge/internal/models" "github.com/glebarez/sqlite" "gorm.io/gorm" ) func TestGenerateVersion_FirstOfDay(t *testing.T) { repo := newTestPricelistRepository(t) version, err := repo.GenerateVersionBySource(string(models.PricelistSourceEstimate)) if err != nil { t.Fatalf("GenerateVersionBySource returned error: %v", err) } today := time.Now().Format("2006-01-02") want := fmt.Sprintf("E-%s-001", today) if version != want { t.Fatalf("expected %s, got %s", want, version) } } func TestGenerateVersion_UsesMaxSuffixNotCount(t *testing.T) { repo := newTestPricelistRepository(t) today := time.Now().Format("2006-01-02") seed := []models.Pricelist{ {Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("E-%s-001", today), CreatedBy: "test", IsActive: true}, {Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("E-%s-003", today), CreatedBy: "test", IsActive: true}, } for _, pl := range seed { if err := repo.Create(&pl); err != nil { t.Fatalf("seed insert failed: %v", err) } } version, err := repo.GenerateVersionBySource(string(models.PricelistSourceEstimate)) if err != nil { t.Fatalf("GenerateVersionBySource returned error: %v", err) } want := fmt.Sprintf("E-%s-004", today) if version != want { t.Fatalf("expected %s, got %s", want, version) } } func TestGenerateVersion_IsolatedBySource(t *testing.T) { repo := newTestPricelistRepository(t) today := time.Now().Format("2006-01-02") seed := []models.Pricelist{ {Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("E-%s-009", today), CreatedBy: "test", IsActive: true}, {Source: string(models.PricelistSourceWarehouse), Version: fmt.Sprintf("S-%s-002", today), CreatedBy: "test", IsActive: true}, } for _, pl := range seed { if err := repo.Create(&pl); err != nil { t.Fatalf("seed insert failed: %v", err) } } version, err := repo.GenerateVersionBySource(string(models.PricelistSourceWarehouse)) if err != nil { t.Fatalf("GenerateVersionBySource returned error: %v", err) } want := fmt.Sprintf("S-%s-003", today) if version != want { t.Fatalf("expected %s, got %s", want, version) } } func TestGetItems_WarehouseAvailableQtyUsesPrefixResolver(t *testing.T) { repo := newTestPricelistRepository(t) db := repo.db warehouse := models.Pricelist{ Source: string(models.PricelistSourceWarehouse), Version: "S-2026-02-07-001", CreatedBy: "test", IsActive: true, } if err := db.Create(&warehouse).Error; err != nil { t.Fatalf("create pricelist: %v", err) } if err := db.Create(&models.PricelistItem{ PricelistID: warehouse.ID, LotName: "SSD_NVME_03.2T", Price: 100, }).Error; err != nil { t.Fatalf("create pricelist item: %v", err) } if err := db.Create(&models.Lot{LotName: "SSD_NVME_03.2T"}).Error; err != nil { t.Fatalf("create lot: %v", err) } qty := 5.0 if err := db.Create(&models.StockLog{ Partnumber: "SSD_NVME_03.2T_GEN3_P4610", Date: time.Now(), Price: 200, Qty: &qty, }).Error; err != nil { t.Fatalf("create stock log: %v", err) } items, total, err := repo.GetItems(warehouse.ID, 0, 20, "") if err != nil { t.Fatalf("GetItems: %v", err) } if total != 1 { t.Fatalf("expected total=1, got %d", total) } if len(items) != 1 { t.Fatalf("expected 1 item, got %d", len(items)) } if items[0].AvailableQty == nil { t.Fatalf("expected available qty to be set") } if *items[0].AvailableQty != 5 { t.Fatalf("expected available qty=5, got %v", *items[0].AvailableQty) } } func TestGetLatestActiveBySource_SkipsPricelistsWithoutItems(t *testing.T) { repo := newTestPricelistRepository(t) db := repo.db ts := time.Now().Add(-time.Minute) source := "test-estimate-skip-empty" emptyLatest := models.Pricelist{ Source: source, Version: "E-empty", CreatedBy: "test", IsActive: true, CreatedAt: ts.Add(2 * time.Second), } if err := db.Create(&emptyLatest).Error; err != nil { t.Fatalf("create empty pricelist: %v", err) } withItems := models.Pricelist{ Source: source, Version: "E-with-items", CreatedBy: "test", IsActive: true, CreatedAt: ts, } if err := db.Create(&withItems).Error; err != nil { t.Fatalf("create pricelist with items: %v", err) } if err := db.Create(&models.PricelistItem{ PricelistID: withItems.ID, LotName: "CPU_A", Price: 100, }).Error; err != nil { t.Fatalf("create pricelist item: %v", err) } got, err := repo.GetLatestActiveBySource(source) if err != nil { t.Fatalf("GetLatestActiveBySource: %v", err) } if got.ID != withItems.ID { t.Fatalf("expected pricelist with items id=%d, got id=%d", withItems.ID, got.ID) } } func TestGetLatestActiveBySource_TieBreaksByID(t *testing.T) { repo := newTestPricelistRepository(t) db := repo.db ts := time.Now().Add(-time.Minute) source := "test-warehouse-tie-break" first := models.Pricelist{ Source: source, Version: "S-1", CreatedBy: "test", IsActive: true, CreatedAt: ts, } if err := db.Create(&first).Error; err != nil { t.Fatalf("create first pricelist: %v", err) } if err := db.Create(&models.PricelistItem{ PricelistID: first.ID, LotName: "CPU_A", Price: 101, }).Error; err != nil { t.Fatalf("create first item: %v", err) } second := models.Pricelist{ Source: source, Version: "S-2", CreatedBy: "test", IsActive: true, CreatedAt: ts, } if err := db.Create(&second).Error; err != nil { t.Fatalf("create second pricelist: %v", err) } if err := db.Create(&models.PricelistItem{ PricelistID: second.ID, LotName: "CPU_A", Price: 102, }).Error; err != nil { t.Fatalf("create second item: %v", err) } got, err := repo.GetLatestActiveBySource(source) if err != nil { t.Fatalf("GetLatestActiveBySource: %v", err) } if got.ID != second.ID { t.Fatalf("expected later inserted pricelist id=%d, got id=%d", second.ID, got.ID) } } func newTestPricelistRepository(t *testing.T) *PricelistRepository { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&models.Pricelist{}, &models.PricelistItem{}, &models.Lot{}, &models.LotPartnumber{}, &models.StockLog{}); err != nil { t.Fatalf("migrate: %v", err) } return NewPricelistRepository(db) }