// seed — 初始化/重置测试数据 // // 用法(在 backend/ 目录下执行): // go run cmd/seed/main.go # 写入数据(已存在则跳过) // go run cmd/seed/main.go --reset # 删表重建 + 写入数据 // go run cmd/seed/main.go --clear # 清空业务数据 + 重新写入(保留表结构) package main import ( "fmt" stdlog "log" "os" "time" "golang.org/x/crypto/bcrypt" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/wangjia/jiu/backend/config" "github.com/wangjia/jiu/backend/internal/model" ) var allModels = []any{ &model.Shop{}, &model.User{}, &model.License{}, &model.ProductCategory{}, &model.Product{}, &model.Warehouse{}, &model.Partner{}, &model.StockInOrder{}, &model.StockInItem{}, &model.StockOutOrder{}, &model.StockOutItem{}, &model.Inventory{}, &model.InventoryLog{}, &model.InventoryCheck{}, &model.InventoryCheckItem{}, &model.FinanceRecord{}, &model.NumberRule{}, } // 清空业务数据的表(有外键依赖的先删子表) var truncateOrder = []string{ "inventory_check_items", "inventory_checks", "inventory_logs", "inventories", "stock_out_items", "stock_out_orders", "stock_in_items", "stock_in_orders", "finance_records", "number_rules", "partners", "warehouses", "products", "product_categories", "licenses", "users", "shops", } func main() { mode := "" if len(os.Args) > 1 { mode = os.Args[1] } config.Load() db, err := gorm.Open(mysql.Open(config.C.Database.DSN), &gorm.Config{ Logger: logger.New( stdlog.New(os.Stdout, "\r\n", stdlog.LstdFlags), logger.Config{ LogLevel: logger.Warn, IgnoreRecordNotFoundError: true, }, ), }) if err != nil { stdlog.Fatalf("连接数据库失败: %v", err) } switch mode { case "--reset": fmt.Println("⚠️ --reset:删除所有表并重建...") db.Exec("SET FOREIGN_KEY_CHECKS = 0") if err := db.Migrator().DropTable(allModels...); err != nil { stdlog.Fatalf("删表失败: %v", err) } db.Exec("SET FOREIGN_KEY_CHECKS = 1") fmt.Println("✅ 所有表已删除") case "--clear": fmt.Println("🧹 --clear:清空所有业务数据...") db.Exec("SET FOREIGN_KEY_CHECKS = 0") for _, t := range truncateOrder { if err := db.Exec("TRUNCATE TABLE `" + t + "`").Error; err != nil { fmt.Printf(" 跳过 %s(%v)\n", t, err) } else { fmt.Printf(" 清空 %s\n", t) } } db.Exec("SET FOREIGN_KEY_CHECKS = 1") fmt.Println("✅ 数据已清空") return } // 同步表结构 if err := db.AutoMigrate(allModels...); err != nil { stdlog.Fatalf("AutoMigrate 失败: %v", err) } fmt.Println("✅ 表结构已同步") fmt.Println() // ═══════════════════════════════════════════════════ // 门店 // ═══════════════════════════════════════════════════ shop := upsertShop(db) // ═══════════════════════════════════════════════════ // 用户 // ═══════════════════════════════════════════════════ hash := mustHash("password123") admin := upsertUser(db, shop.ID, "admin", "超级管理员", "admin", hash) upsertUser(db, shop.ID, "operator", "操作员小李", "operator", hash) upsertUser(db, shop.ID, "test", "测试账号", "readonly", hash) // ═══════════════════════════════════════════════════ // 仓库 // ═══════════════════════════════════════════════════ wh1 := upsertWarehouse(db, shop.ID, "主仓库", "A栋1层", true) wh2 := upsertWarehouse(db, shop.ID, "备用仓库", "B栋2层", false) // ═══════════════════════════════════════════════════ // 商品 // ═══════════════════════════════════════════════════ products := []struct{ name, series, unit, sku string }{ {"飞天茅台 53度 500ml", "茅台", "瓶", "MT-001"}, {"五粮液 52度 500ml", "五粮液", "瓶", "WLY-001"}, {"洋河梦之蓝 M6 500ml", "洋河", "瓶", "YH-001"}, {"泸州老窖 特曲 500ml", "泸州老窖", "瓶", "LZ-001"}, {"剑南春 水晶剑 500ml", "剑南春", "瓶", "JNC-001"}, {"郎酒 红花郎 500ml", "郎酒", "瓶", "LJ-001"}, {"拉菲古堡 2018 750ml", "波尔多", "瓶", "LF-001"}, {"人头马 VSOP 700ml", "人头马", "瓶", "RTM-001"}, } var prods []model.Product for _, p := range products { prod := upsertProduct(db, shop.ID, p.name, p.series, p.unit, p.sku) prods = append(prods, prod) } // ═══════════════════════════════════════════════════ // 往来单位 // ═══════════════════════════════════════════════════ sup1 := upsertPartner(db, shop.ID, "贵州茅台酒股份有限公司", "supplier", "张经理", "0851-12345678") sup2 := upsertPartner(db, shop.ID, "四川五粮液股份有限公司", "supplier", "王经理", "0831-87654321") cus1 := upsertPartner(db, shop.ID, "北京君悦大酒店", "customer", "李采购", "010-65888888") upsertPartner(db, shop.ID, "上海外滩华尔道夫", "customer", "陈主任", "021-62308888") // ═══════════════════════════════════════════════════ // 编号规则 // ═══════════════════════════════════════════════════ upsertNumberRule(db, shop.ID, "stock_in", "RK", 5) upsertNumberRule(db, shop.ID, "stock_out", "CK", 2) upsertNumberRule(db, shop.ID, "inventory_check", "PD", 1) // ═══════════════════════════════════════════════════ // 入库单(已审核)→ 生成库存 // ═══════════════════════════════════════════════════ now := time.Now() today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) inOrders := []struct { orderNo string partnerID uint64 items []struct { prodIdx int qty float64 price float64 } }{ { orderNo: "RK20260401001", partnerID: sup1.ID, items: []struct { prodIdx int qty float64 price float64 }{ {0, 120, 2800}, // 茅台 120瓶 {1, 60, 1200}, // 五粮液 60瓶 }, }, { orderNo: "RK20260403001", partnerID: sup2.ID, items: []struct { prodIdx int qty float64 price float64 }{ {2, 48, 680}, // 洋河 48瓶 {3, 36, 520}, // 泸州老窖 36瓶 {4, 24, 480}, // 剑南春 24瓶 }, }, } for _, o := range inOrders { order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, today.AddDate(0, 0, -3)) total := 0.0 for _, it := range o.items { item := model.StockInItem{ OrderID: order.ID, ShopID: shop.ID, ProductID: prods[it.prodIdx].ID, Quantity: it.qty, UnitPrice: it.price, TotalPrice: it.qty * it.price, } db.Create(&item) total += item.TotalPrice // 更新库存 updateInventory(db, shop.ID, wh1.ID, prods[it.prodIdx].ID, it.qty, order.ID, admin.ID) } db.Model(&order).Updates(map[string]any{ "status": "approved", "total_amount": total, "reviewer_id": admin.ID, "reviewed_at": now, }) } // ═══════════════════════════════════════════════════ // 出库单(已审核)→ 减库存 // ═══════════════════════════════════════════════════ outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", today.AddDate(0, 0, -1)) outItems := []struct { prodIdx int qty float64 price float64 }{ {0, 12, 3200}, // 茅台 12瓶 {1, 6, 1380}, // 五粮液 6瓶 } outTotal := 0.0 for _, it := range outItems { item := model.StockOutItem{ OrderID: outOrder.ID, ShopID: shop.ID, ProductID: prods[it.prodIdx].ID, Quantity: it.qty, UnitPrice: it.price, TotalPrice: it.qty * it.price, } db.Create(&item) outTotal += item.TotalPrice updateInventoryOut(db, shop.ID, wh1.ID, prods[it.prodIdx].ID, it.qty, outOrder.ID, admin.ID) } db.Model(&outOrder).Updates(map[string]any{ "status": "approved", "total_amount": outTotal, "reviewer_id": admin.ID, "reviewed_at": now, }) // 备用仓库放一些商品(直接写库存) directInventory := []struct{ prodIdx int; qty float64 }{ {5, 24}, {6, 18}, {7, 12}, } for _, it := range directInventory { updateInventory(db, shop.ID, wh2.ID, prods[it.prodIdx].ID, it.qty, 0, admin.ID) } // ═══════════════════════════════════════════════════ // 打印汇总 // ═══════════════════════════════════════════════════ fmt.Println("═══════════════════════════════════════") fmt.Println(" 测试数据写入完成") fmt.Println("═══════════════════════════════════════") fmt.Println() fmt.Println(" 登录信息:") fmt.Println(" 门店编号:H001") fmt.Println(" admin / password123 (管理员)") fmt.Println(" operator / password123 (操作员)") fmt.Println(" test / password123 (只读)") fmt.Println() fmt.Println(" 数据概览:") fmt.Println(" 仓库:主仓库、备用仓库") fmt.Printf(" 商品:%d 种\n", len(prods)) fmt.Println(" 往来单位:2 供应商 + 2 客户") fmt.Println(" 入库单:2 张(已审核)") fmt.Println(" 出库单:1 张(已审核)") fmt.Println(" 库存:主仓库 5 种商品,备用仓库 3 种商品") fmt.Println("═══════════════════════════════════════") } // ── 辅助函数 ──────────────────────────────────────────── func mustHash(plain string) string { b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost) if err != nil { stdlog.Fatalf("bcrypt 失败: %v", err) } return string(b) } func upsertShop(db *gorm.DB) model.Shop { var s model.Shop if db.Where("code = ?", "H001").First(&s).Error != nil { s = model.Shop{Name: "测试门店", Code: "H001", Address: "北京市朝阳区测试街1号", Phone: "010-12345678", ManagerName: "张三"} db.Create(&s) fmt.Printf("✅ 门店:%s (%s)\n", s.Name, s.Code) } else { fmt.Printf("⏭ 门店:%s (%s)\n", s.Name, s.Code) } return s } func upsertUser(db *gorm.DB, shopID uint64, username, realName, role, hash string) model.User { var u model.User if db.Where("shop_id = ? AND username = ?", shopID, username).First(&u).Error != nil { u = model.User{TenantBase: model.TenantBase{ShopID: shopID}, Username: username, PasswordHash: hash, RealName: realName, Role: role, IsActive: true} db.Create(&u) fmt.Printf("✅ 用户:%s(%s)\n", username, role) } else { fmt.Printf("⏭ 用户:%s(%s)\n", username, role) } return u } func upsertWarehouse(db *gorm.DB, shopID uint64, name, location string, isDefault bool) model.Warehouse { var w model.Warehouse if db.Where("shop_id = ? AND name = ?", shopID, name).First(&w).Error != nil { w = model.Warehouse{TenantBase: model.TenantBase{ShopID: shopID}, Name: name, Location: location, IsDefault: isDefault} db.Create(&w) fmt.Printf("✅ 仓库:%s\n", name) } else { fmt.Printf("⏭ 仓库:%s\n", name) } return w } func upsertProduct(db *gorm.DB, shopID uint64, name, series, unit, code string) model.Product { var p model.Product if db.Where("shop_id = ? AND name = ?", shopID, name).First(&p).Error != nil { p = model.Product{TenantBase: model.TenantBase{ShopID: shopID}, Name: name, Series: series, Unit: unit, Code: code} db.Create(&p) fmt.Printf("✅ 商品:%s\n", name) } else { fmt.Printf("⏭ 商品:%s\n", name) } return p } func upsertPartner(db *gorm.DB, shopID uint64, name, ptype, contact, phone string) model.Partner { var p model.Partner if db.Where("shop_id = ? AND name = ?", shopID, name).First(&p).Error != nil { p = model.Partner{TenantBase: model.TenantBase{ShopID: shopID}, Name: name, Type: ptype, Contact: contact, Phone: phone} db.Create(&p) fmt.Printf("✅ 往来单位:%s(%s)\n", name, ptype) } else { fmt.Printf("⏭ 往来单位:%s\n", name) } return p } func upsertNumberRule(db *gorm.DB, shopID uint64, ruleType, prefix string, currentNo int) { var r model.NumberRule if db.Where("shop_id = ? AND type = ?", shopID, ruleType).First(&r).Error != nil { r = model.NumberRule{ShopID: shopID, Type: ruleType, Prefix: prefix, CurrentNo: currentNo, DateFormat: "YYYYMMDD"} db.Create(&r) fmt.Printf("✅ 编号规则:%s → %s\n", ruleType, prefix) } } func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockInOrder { var o model.StockInOrder if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil { o = model.StockInOrder{ TenantBase: model.TenantBase{ShopID: shopID}, OrderNo: orderNo, Type: "purchase", WarehouseID: whID, PartnerID: &partnerID, OperatorID: opID, Status: "draft", OrderDate: date, } db.Create(&o) fmt.Printf("✅ 入库单:%s\n", orderNo) } return o } func upsertStockOutOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockOutOrder { var o model.StockOutOrder if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil { o = model.StockOutOrder{ TenantBase: model.TenantBase{ShopID: shopID}, OrderNo: orderNo, Type: "sale", WarehouseID: whID, PartnerID: &partnerID, OperatorID: opID, Status: "draft", OrderDate: date, } db.Create(&o) fmt.Printf("✅ 出库单:%s\n", orderNo) } return o } func updateInventory(db *gorm.DB, shopID, whID, productID uint64, qty float64, refID uint64, opID uint64) { var inv model.Inventory db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?", shopID, whID, productID).First(&inv) before := inv.Quantity after := before + qty if inv.ID == 0 { inv = model.Inventory{ShopID: shopID, WarehouseID: whID, ProductID: productID, Quantity: after} db.Create(&inv) } else { db.Model(&inv).Update("quantity", after) } log := model.InventoryLog{ ShopID: shopID, WarehouseID: whID, ProductID: productID, Direction: "in", Quantity: qty, QtyBefore: before, QtyAfter: after, RefType: "stock_in", RefID: refID, OperatorID: &opID, } db.Create(&log) } func updateInventoryOut(db *gorm.DB, shopID, whID, productID uint64, qty float64, refID uint64, opID uint64) { var inv model.Inventory db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?", shopID, whID, productID).First(&inv) before := inv.Quantity after := before - qty if after < 0 { after = 0 } db.Model(&inv).Update("quantity", after) log := model.InventoryLog{ ShopID: shopID, WarehouseID: whID, ProductID: productID, Direction: "out", Quantity: qty, QtyBefore: before, QtyAfter: after, RefType: "stock_out", RefID: refID, OperatorID: &opID, } db.Create(&log) }