Renamed module path git.mchus.pro/mchus/quoteforge → git.mchus.pro/mchus/priceforge, renamed package quoteforge → priceforge, moved binary from cmd/qfs to cmd/pfs.
161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/priceforge/internal/config"
|
|
"git.mchus.pro/mchus/priceforge/internal/models"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
func main() {
|
|
configPath := flag.String("config", "config.yaml", "path to config file")
|
|
flag.Parse()
|
|
|
|
cfg, err := config.Load(*configPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
db, err := gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to database: %v", err)
|
|
}
|
|
|
|
log.Println("Connected to database")
|
|
|
|
// Ensure tables exist
|
|
if err := models.Migrate(db); err != nil {
|
|
log.Fatalf("Migration failed: %v", err)
|
|
}
|
|
if err := models.SeedCategories(db); err != nil {
|
|
log.Fatalf("Seeding categories failed: %v", err)
|
|
}
|
|
|
|
// Load categories for lookup
|
|
var categories []models.Category
|
|
db.Find(&categories)
|
|
categoryMap := make(map[string]uint)
|
|
for _, c := range categories {
|
|
categoryMap[c.Code] = c.ID
|
|
}
|
|
log.Printf("Loaded %d categories", len(categories))
|
|
|
|
// Get all lots
|
|
var lots []models.Lot
|
|
if err := db.Find(&lots).Error; err != nil {
|
|
log.Fatalf("Failed to load lots: %v", err)
|
|
}
|
|
log.Printf("Found %d lots to import", len(lots))
|
|
|
|
// Import each lot
|
|
var imported, skipped, updated int
|
|
for _, lot := range lots {
|
|
category, model := ParsePartNumber(lot.LotName)
|
|
|
|
var categoryID *uint
|
|
if id, ok := categoryMap[category]; ok && id > 0 {
|
|
categoryID = &id
|
|
} else {
|
|
// Try to find by prefix match
|
|
for code, id := range categoryMap {
|
|
if strings.HasPrefix(category, code) {
|
|
categoryID = &id
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if already exists
|
|
var existing models.LotMetadata
|
|
result := db.Where("lot_name = ?", lot.LotName).First(&existing)
|
|
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
// Check if there are prices in the last 90 days
|
|
var recentPriceCount int64
|
|
db.Model(&models.LotLog{}).
|
|
Where("lot = ? AND date >= DATE_SUB(NOW(), INTERVAL 90 DAY)", lot.LotName).
|
|
Count(&recentPriceCount)
|
|
|
|
// Default to 90 days, but use "all time" (0) if no recent prices
|
|
periodDays := 90
|
|
if recentPriceCount == 0 {
|
|
periodDays = 0
|
|
}
|
|
|
|
// Create new
|
|
metadata := models.LotMetadata{
|
|
LotName: lot.LotName,
|
|
CategoryID: categoryID,
|
|
Model: model,
|
|
PricePeriodDays: periodDays,
|
|
}
|
|
if err := db.Create(&metadata).Error; err != nil {
|
|
log.Printf("Failed to create metadata for %s: %v", lot.LotName, err)
|
|
continue
|
|
}
|
|
imported++
|
|
} else if result.Error == nil {
|
|
// Update if needed
|
|
needsUpdate := false
|
|
|
|
if existing.Model == "" {
|
|
existing.Model = model
|
|
needsUpdate = true
|
|
}
|
|
if existing.CategoryID == nil {
|
|
existing.CategoryID = categoryID
|
|
needsUpdate = true
|
|
}
|
|
|
|
// Check if using default period (90 days) but no recent prices
|
|
if existing.PricePeriodDays == 90 {
|
|
var recentPriceCount int64
|
|
db.Model(&models.LotLog{}).
|
|
Where("lot = ? AND date >= DATE_SUB(NOW(), INTERVAL 90 DAY)", lot.LotName).
|
|
Count(&recentPriceCount)
|
|
|
|
if recentPriceCount == 0 {
|
|
existing.PricePeriodDays = 0
|
|
needsUpdate = true
|
|
}
|
|
}
|
|
|
|
if needsUpdate {
|
|
db.Save(&existing)
|
|
updated++
|
|
} else {
|
|
skipped++
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Import complete: %d imported, %d updated, %d skipped", imported, updated, skipped)
|
|
|
|
// Show final counts
|
|
var metadataCount int64
|
|
db.Model(&models.LotMetadata{}).Count(&metadataCount)
|
|
log.Printf("Total metadata records: %d", metadataCount)
|
|
}
|
|
|
|
// ParsePartNumber extracts category and model from lot_name
|
|
// Examples:
|
|
// "CPU_AMD_9654" → category="CPU", model="AMD_9654"
|
|
// "MB_INTEL_4.Sapphire_2S" → category="MB", model="INTEL_4.Sapphire_2S"
|
|
func ParsePartNumber(lotName string) (category, model string) {
|
|
parts := strings.SplitN(lotName, "_", 2)
|
|
if len(parts) >= 1 {
|
|
category = parts[0]
|
|
}
|
|
if len(parts) >= 2 {
|
|
model = parts[1]
|
|
}
|
|
return
|
|
}
|