// confirm-backfilled-pdd-availability 把三个固定 Excel 的完整 PDD 规格确认为可采购候选。 // 默认只预览;只有显式传入 -apply 才写数据库。 package main import ( "encoding/json" "flag" "fmt" "log" "os" "time" "cmautobuy/admin/config" "cmautobuy/admin/repository" "cmautobuy/admin/service" ) type backupFile struct { Shop string `json:"shop"` GeneratedAt string `json:"generated_at"` Files []string `json:"files"` Items []backupItem `json:"items"` } type backupItem struct { PDDGoodsID string `json:"pdd_goods_id"` OriginalSKUsJSON string `json:"original_skus_json"` OriginalCollectStatus string `json:"original_collect_status"` OriginalCollectMsg *string `json:"original_collect_msg"` OriginalArtifactRef *string `json:"original_artifact_ref"` OriginalCollectedAt *string `json:"original_collected_at"` OriginalUpdatedAt string `json:"original_updated_at"` } func main() { dir := flag.String("dir", "../raw_data", "三个固定 Excel 所在目录") apply := flag.Bool("apply", false, "实际写入;不传时只做 dry-run") flag.Parse() if flag.NArg() != 0 { log.Fatal("不接受额外文件参数;只会读取 -dir 下三个固定文件名") } sources, err := service.LoadXLSXPriceSources(*dir) if err != nil { log.Fatalf("读取三个固定 Excel 失败: %v", err) } cfg, err := config.LoadDatabase() if err != nil { log.Fatalf("读取 MySQL 配置失败: %v", err) } db, err := repository.OpenMySQL(cfg) if err != nil { log.Fatalf("连接 MySQL 失败: %v", err) } defer db.Close() targets, err := repository.ListShopPDDPriceBackfillTargets(db, service.XLSXPriceBackfillShop) if err != nil { log.Fatal(err) } plan := service.BuildXLSXAvailabilityPlan(sources, targets, time.Now()) if !*apply { printReport(plan, 0, "dry-run") return } if len(plan.Candidates) == 0 { printReport(plan, 0, "applied-noop") return } backupPath, err := writeBackup(plan) if err != nil { log.Fatalf("创建本机回退备份失败,未写数据库: %v", err) } updated, err := service.ApplyXLSXAvailabilityPlan(db, plan, time.Now()) if err != nil { log.Fatalf("PDD 可售状态确认失败: %v;本机备份位于 %s", err, backupPath) } printReport(plan, updated, "applied") fmt.Printf("本机回退备份:%s\n", backupPath) } func printReport(plan service.XLSXAvailabilityPlan, updated int, mode string) { summary, _ := json.Marshal(plan.Summary) skuCount := 0 for _, candidate := range plan.Candidates { skuCount += candidate.SKUCount } fmt.Printf("模式:%s\n店铺:%s\nExcel 行:%d\n蝦皮商品:%d\nPDD 商品:%d\n候选 PDD:%d\n候选 SKU:%d\n本次更新 PDD:%d\n分类:%s\n", mode, plan.Shop, plan.Sources.RowCount, plan.Sources.ShopeeProductCount, plan.Sources.PDDProductCount, len(plan.Candidates), skuCount, updated, summary) } func writeBackup(plan service.XLSXAvailabilityPlan) (string, error) { file, err := os.CreateTemp("", "cmautobuy_xlsx_availability_backup_*.json") if err != nil { return "", err } path := file.Name() defer file.Close() if err := file.Chmod(0o600); err != nil { return "", err } backup := backupFile{ Shop: plan.Shop, GeneratedAt: time.Now().UTC().Format(time.RFC3339), Files: append([]string(nil), service.XLSXPriceBackfillFiles...), } for _, candidate := range plan.Candidates { original := candidate.Original backup.Items = append(backup.Items, backupItem{ PDDGoodsID: candidate.PDDGoodsID, OriginalSKUsJSON: original.SKUsJSON, OriginalCollectStatus: original.CollectStatus, OriginalCollectMsg: original.CollectMsg, OriginalArtifactRef: original.ArtifactRef, OriginalCollectedAt: original.CollectedAt, OriginalUpdatedAt: original.UpdatedAt, }) } encoder := json.NewEncoder(file) encoder.SetIndent("", " ") if err := encoder.Encode(backup); err != nil { return "", err } return path, nil }