feat(db): 补全测试种子数据所有字段

入库单补全供应商/仓库,出库单补全客户/仓库,商品补全价格/条码/规格,
往来单位补全地址/银行账户/信用额度,财务记录关联已审核出库单。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-07 22:43:00 +08:00
parent 754275cbd6
commit 902c3c3046
+528 -293
View File
@@ -1,10 +1,10 @@
// seed — 初始化/重置测试数据
//
// 用法(在 backend/ 目录下执行):
// go run cmd/seed/main.go # 写入数据(已存在则跳过)
// go run cmd/seed/main.go --reset # 删表重建 + 写入数据
// go run cmd/seed/main.go --clear # 清空业务数据 + 重新写入(保留表结构)
//
// go run cmd/seed/main.go # 写入数据(已存在则跳过)
// go run cmd/seed/main.go --reset # 删表重建 + 写入数据
// go run cmd/seed/main.go --clear # 清空业务数据 + 重新写入(保留表结构)
package main
import (
@@ -42,7 +42,7 @@ var allModels = []any{
&model.NumberRule{},
}
// 清空业务数据的表(有外键依赖的先删子表
// 清空顺序(子表先清
var truncateOrder = []string{
"inventory_check_items", "inventory_checks",
"inventory_logs", "inventories",
@@ -65,10 +65,7 @@ func main() {
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,
},
logger.Config{LogLevel: logger.Warn, IgnoreRecordNotFoundError: true},
),
})
if err != nil {
@@ -107,192 +104,581 @@ func main() {
fmt.Println("✅ 表结构已同步")
fmt.Println()
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
d := func(daysAgo int) model.Date { return model.Date{Time: today.AddDate(0, 0, -daysAgo)} }
ptrT := func(t time.Time) *time.Time { return &t }
// ═══════════════════════════════════════════════════
// 门店
// ═══════════════════════════════════════════════════
shop := upsertShop(db)
shop := upsert(db, &model.Shop{}, "code = ?", "H001", func() any {
return &model.Shop{
Name: "测试酒库门店",
Code: "H001",
Address: "北京市朝阳区酒仙桥路甲1号",
Phone: "010-12345678",
ManagerName: "张三",
BusinessLicense: "91110105MA0000000X",
}
}).(model.Shop)
// ═══════════════════════════════════════════════════
// 用户
// ═══════════════════════════════════════════════════
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)
admin := upsert(db, &model.User{}, "shop_id = ? AND username = ?", shop.ID, "admin", func() any {
return &model.User{
TenantBase: model.TenantBase{ShopID: shop.ID},
Username: "admin", PasswordHash: hash, RealName: "张三(管理员)",
Phone: "13800000001", Role: "admin", IsActive: true,
}
}).(model.User)
operator := upsert(db, &model.User{}, "shop_id = ? AND username = ?", shop.ID, "operator", func() any {
return &model.User{
TenantBase: model.TenantBase{ShopID: shop.ID},
Username: "operator", PasswordHash: hash, RealName: "李四(操作员)",
Phone: "13800000002", Role: "operator", IsActive: true,
}
}).(model.User)
upsert(db, &model.User{}, "shop_id = ? AND username = ?", shop.ID, "test", func() any {
return &model.User{
TenantBase: model.TenantBase{ShopID: shop.ID},
Username: "test", PasswordHash: hash, RealName: "王五(只读)",
Phone: "13800000003", Role: "readonly", IsActive: true,
}
})
// ═══════════════════════════════════════════════════
// 仓库
// ═══════════════════════════════════════════════════
wh1 := upsertWarehouse(db, shop.ID, "主仓库", "A栋1层", true)
wh2 := upsertWarehouse(db, shop.ID, "备用仓库", "B栋2层", false)
wh1 := upsert(db, &model.Warehouse{}, "shop_id = ? AND name = ?", shop.ID, "主仓库", func() any {
return &model.Warehouse{
TenantBase: model.TenantBase{ShopID: shop.ID},
Name: "主仓库", Location: "A栋1层东侧", IsDefault: true,
}
}).(model.Warehouse)
wh2 := upsert(db, &model.Warehouse{}, "shop_id = ? AND name = ?", shop.ID, "进口酒专库", func() any {
return &model.Warehouse{
TenantBase: model.TenantBase{ShopID: shop.ID},
Name: "进口酒专库", Location: "B栋2层恒温区", IsDefault: false,
}
}).(model.Warehouse)
// ═══════════════════════════════════════════════════
// 商品分类
// ═══════════════════════════════════════════════════
catBaijiu := upsert(db, &model.ProductCategory{}, "shop_id = ? AND name = ?", shop.ID, "白酒", func() any {
return &model.ProductCategory{TenantBase: model.TenantBase{ShopID: shop.ID}, Name: "白酒", SortOrder: 1}
}).(model.ProductCategory)
catImport := upsert(db, &model.ProductCategory{}, "shop_id = ? AND name = ?", shop.ID, "进口烈酒", func() any {
return &model.ProductCategory{TenantBase: model.TenantBase{ShopID: shop.ID}, Name: "进口烈酒", SortOrder: 2}
}).(model.ProductCategory)
// ═══════════════════════════════════════════════════
// 商品
// ═══════════════════════════════════════════════════
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"},
type prodSeed struct {
code, barcode, name, series, spec, unit, brand string
catID *uint64
purchase, sale float64
minStock int
remark string
}
prodSeeds := []prodSeed{
{"MT-001", "6901234567890", "飞天茅台 53度 500ml", "茅台", "500ml/瓶", "瓶", "贵州茅台", &catBaijiu.ID, 2350, 2800, 10, "酱香型白酒,53度,飞天系列"},
{"WLY-001", "6902345678901", "五粮液 52度 500ml", "五粮液", "500ml/瓶", "瓶", "宜宾五粮液", &catBaijiu.ID, 950, 1200, 6, "浓香型白酒,52度,普五系列"},
{"YH-001", "6903456789012", "洋河梦之蓝 M6 500ml", "洋河", "500ml/瓶", "瓶", "江苏洋河", &catBaijiu.ID, 560, 680, 6, "浓香型,绵柔苏酒代表"},
{"LZ-001", "6904567890123", "泸州老窖 特曲 500ml", "泸州老窖", "500ml/瓶", "瓶", "泸州老窖", &catBaijiu.ID, 420, 520, 6, "浓香鼻祖,特曲系列"},
{"JNC-001", "6905678901234", "剑南春 水晶剑 500ml", "剑南春", "500ml/瓶", "瓶", "剑南春", &catBaijiu.ID, 390, 480, 6, "浓香型,绵竹名酒"},
{"LJ-001", "6906789012345", "郎酒 红花郎10 500ml", "郎酒", "500ml/瓶", "瓶", "古蔺郎酒", &catBaijiu.ID, 320, 420, 6, "酱香型,赤水河畔酿造"},
{"LF-001", "3760093550058", "拉菲古堡 2018 750ml", "波尔多", "750ml/瓶", "瓶", "Château Lafite", &catImport.ID, 3800, 5200, 3, "波尔多一级名庄,2018年份"},
{"RTM-001", "3021691010008", "人头马 VSOP 700ml", "人头马", "700ml/瓶", "瓶", "Rémy Martin", &catImport.ID, 480, 680, 3, "法国干邑,VSOP级别"},
}
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)
for _, s := range prodSeeds {
p := upsert(db, &model.Product{}, "shop_id = ? AND code = ?", shop.ID, s.code, func() any {
return &model.Product{
TenantBase: model.TenantBase{ShopID: shop.ID},
Code: s.code, Barcode: s.barcode, Name: s.name,
Series: s.series, Spec: s.spec, Unit: s.unit, Brand: s.brand,
CategoryID: s.catID, PurchasePrice: s.purchase, SalePrice: s.sale,
MinStock: s.minStock, Remark: s.remark,
}
}).(model.Product)
prods = append(prods, p)
}
// ═══════════════════════════════════════════════════
// 往来单位
// ═══════════════════════════════════════════════════
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")
type partnerSeed struct {
code, name, ptype, contact, phone, address, bank string
creditLimit float64
remark string
}
partnerSeeds := []partnerSeed{
{"SUP001", "贵州茅台酒股份有限公司", "supplier", "张经理", "0851-22222001",
"贵州省仁怀市茅台镇", "工商银行仁怀支行 6222 0000 0001 0001",
5000000, "茅台系列直供,账期30天"},
{"SUP002", "四川五粮液股份有限公司", "supplier", "王总监", "0831-33333001",
"四川省宜宾市翠屏区", "建设银行宜宾支行 6227 0000 0001 0002",
3000000, "五粮液系列授权经销"},
{"SUP003", "江苏洋河酒厂股份有限公司", "supplier", "赵主任", "0527-44444001",
"江苏省宿迁市洋河新区", "农业银行宿迁支行 6228 0000 0001 0003",
2000000, "洋河梦之蓝系列直供"},
{"SUP004", "四川郎酒股份有限公司", "supplier", "刘经理", "0830-55555001",
"四川省古蔺县二郎镇", "中国银行古蔺支行 6013 0000 0001 0004",
1500000, "郎酒红花郎系列"},
{"CUS001", "北京君悦大酒店", "customer", "李采购", "010-65888001",
"北京市朝阳区建国门外大街2号", "中信银行北京朝阳支行 6217 0000 0002 0001",
500000, "五星级酒店,月结"},
{"CUS002", "上海外滩华尔道夫酒店", "customer", "陈主任", "021-62308001",
"上海市黄浦区中山东一路2号", "浦发银行上海黄浦支行 6210 0000 0002 0002",
800000, "豪华酒店,按季结算"},
{"CUS003", "广州白天鹅宾馆", "customer", "吴采购", "020-81886001",
"广州市荔湾区沙面南街1号", "招商银行广州荔湾支行 6225 0000 0002 0003",
300000, "高端宾馆,月结30天"},
}
partners := make(map[string]model.Partner)
for _, s := range partnerSeeds {
p := upsert(db, &model.Partner{}, "shop_id = ? AND code = ?", shop.ID, s.code, func() any {
return &model.Partner{
TenantBase: model.TenantBase{ShopID: shop.ID},
Code: s.code,
Name: s.name,
Type: s.ptype,
Contact: s.contact,
Phone: s.phone,
Address: s.address,
BankAccount: s.bank,
CreditLimit: s.creditLimit,
Balance: 0,
Remark: s.remark,
}
}).(model.Partner)
partners[s.code] = p
}
// ═══════════════════════════════════════════════════
// 编号规则
// ═══════════════════════════════════════════════════
upsertNumberRule(db, shop.ID, "stock_in", "RK", 5)
upsertNumberRule(db, shop.ID, "stock_out", "CK", 2)
upsertNumberRule(db, shop.ID, "stock_out", "CK", 3)
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())
// #1 已审核:茅台+五粮液,主仓库,茅台供应商
in1 := createStockInOrder(db, shop.ID, wh1.ID, admin.ID, partners["SUP001"].ID,
"RK20260401001", d(6), "approved", "purchase",
"茅台系列首批入库,含飞天茅台及五粮液",
[]inItemSeed{
{prods[0].ID, 120, 2350, "BATCH-MT-2024001", "飞天茅台 2024年第一批次"},
{prods[1].ID, 60, 950, "BATCH-WLY-2024001", "五粮液普五 2024年批次"},
}, admin.ID, ptrT(today.AddDate(0, 0, -5)))
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瓶
},
},
}
// #2 已审核:洋河+泸州+剑南春,主仓库,洋河供应商
in2 := createStockInOrder(db, shop.ID, wh1.ID, operator.ID, partners["SUP002"].ID,
"RK20260403001", d(4), "approved", "purchase",
"浓香型白酒补货入库",
[]inItemSeed{
{prods[2].ID, 48, 560, "BATCH-YH-2024003", "洋河梦之蓝 M6 3月批次"},
{prods[3].ID, 36, 420, "BATCH-LZ-2024002", "泸州老窖特曲 春季批次"},
{prods[4].ID, 24, 390, "BATCH-JNC-2024001", "剑南春水晶剑 首批"},
}, admin.ID, ptrT(today.AddDate(0, 0, -3)))
for _, o := range inOrders {
order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, model.Date{Time: 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
// #3 已审核:进口酒,进口酒专库,茅台供应商(代理进口)
in3 := createStockInOrder(db, shop.ID, wh2.ID, operator.ID, partners["SUP004"].ID,
"RK20260404001", d(3), "approved", "purchase",
"进口烈酒专库入库,含拉菲及人头马",
[]inItemSeed{
{prods[6].ID, 24, 3800, "BATCH-LF-2018001", "拉菲古堡2018,原箱"},
{prods[7].ID, 36, 480, "BATCH-RTM-2024001", "人头马VSOP2024年进口"},
}, admin.ID, ptrT(today.AddDate(0, 0, -2)))
// 更新库存
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,
})
}
// #4 待审核:郎酒补货
in4 := createStockInOrder(db, shop.ID, wh1.ID, operator.ID, partners["SUP004"].ID,
"RK20260406001", d(1), "pending", "purchase",
"郎酒红花郎补货,待仓库管理员审核",
[]inItemSeed{
{prods[5].ID, 60, 320, "BATCH-LJ-2024002", "郎酒红花郎10 第二批次"},
}, 0, nil)
// #5 草稿:追加茅台
in5 := createStockInOrder(db, shop.ID, wh1.ID, operator.ID, partners["SUP001"].ID,
"RK20260407001", d(0), "draft", "purchase",
"茅台追加订货,草稿中",
[]inItemSeed{
{prods[0].ID, 60, 2380, "BATCH-MT-2024002", "飞天茅台 第二批次"},
}, 0, nil)
_ = in4
_ = in5
// 根据已审核入库单更新库存
updateInventoryBatch(db, shop.ID, wh1.ID, admin.ID, in1)
updateInventoryBatch(db, shop.ID, wh1.ID, admin.ID, in2)
updateInventoryBatch(db, shop.ID, wh2.ID, admin.ID, in3)
// ═══════════════════════════════════════════════════
// 出库单(已审核)→ 减库存
// 出库单
// ═══════════════════════════════════════════════════
outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", model.Date{Time: 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,
})
// #1 已审核:北京君悦大酒店,主仓库
out1 := createStockOutOrder(db, shop.ID, wh1.ID, operator.ID, partners["CUS001"].ID,
"CK20260404001", d(3), "approved", "sale",
"北京君悦大酒店 4月份定期供货",
[]outItemSeed{
{prods[0].ID, 12, 2800, "飞天茅台 12瓶,整箱出库"},
{prods[1].ID, 6, 1200, "五粮液普五 6瓶"},
{prods[2].ID, 12, 680, "洋河梦之蓝 M6 12瓶"},
}, admin.ID, ptrT(today.AddDate(0, 0, -2)))
// 备用仓库放一些商品(直接写库存)
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)
}
// #2 已审核:上海外滩华尔道夫,进口酒专库
out2 := createStockOutOrder(db, shop.ID, wh2.ID, operator.ID, partners["CUS002"].ID,
"CK20260405001", d(2), "approved", "sale",
"上海外滩华尔道夫 进口酒专属采购",
[]outItemSeed{
{prods[6].ID, 6, 5200, "拉菲古堡2018 6瓶"},
{prods[7].ID, 12, 680, "人头马VSOP 12瓶"},
}, admin.ID, ptrT(today.AddDate(0, 0, -1)))
// #3 待审核:广州白天鹅宾馆
out3 := createStockOutOrder(db, shop.ID, wh1.ID, operator.ID, partners["CUS003"].ID,
"CK20260406001", d(1), "pending", "sale",
"广州白天鹅宾馆 月度供货申请",
[]outItemSeed{
{prods[3].ID, 24, 520, "泸州老窖特曲 24瓶"},
{prods[4].ID, 12, 480, "剑南春水晶剑 12瓶"},
{prods[5].ID, 12, 420, "郎酒红花郎10 12瓶"},
}, 0, nil)
// #4 草稿:君悦追加
out4 := createStockOutOrder(db, shop.ID, wh1.ID, operator.ID, partners["CUS001"].ID,
"CK20260407001", d(0), "draft", "sale",
"北京君悦追加订单,草稿中",
[]outItemSeed{
{prods[0].ID, 6, 2800, "飞天茅台 追加 6瓶"},
}, 0, nil)
_ = out3
_ = out4
// 根据已审核出库单减库存
deductInventoryBatch(db, shop.ID, wh1.ID, admin.ID, out1)
deductInventoryBatch(db, shop.ID, wh2.ID, admin.ID, out2)
// ═══════════════════════════════════════════════════
// 财务记录(与已审核出库单对应的应收款)
// ═══════════════════════════════════════════════════
createFinanceRecord(db, shop.ID, partners["CUS001"].ID, admin.ID,
"receivable", out1.order.TotalAmount, out1.order.TotalAmount, "stock_out", out1.order.ID,
d(3), "君悦大酒店4月供货应收款")
createFinanceRecord(db, shop.ID, partners["CUS002"].ID, admin.ID,
"receivable", out2.order.TotalAmount, out2.order.TotalAmount, "stock_out", out2.order.ID,
d(2), "外滩华尔道夫进口酒应收款")
// 模拟一笔已收款
createFinanceRecord(db, shop.ID, partners["CUS001"].ID, admin.ID,
"receipt", out1.order.TotalAmount, 0, "stock_out", out1.order.ID,
d(1), "君悦大酒店回款,结清")
// ═══════════════════════════════════════════════════
// 打印汇总
// ═══════════════════════════════════════════════════
fmt.Println("═══════════════════════════════════════")
fmt.Println("═══════════════════════════════════════════")
fmt.Println(" 测试数据写入完成")
fmt.Println("═══════════════════════════════════════")
fmt.Println("═══════════════════════════════════════════")
fmt.Println()
fmt.Println(" 登录信息:")
fmt.Println(" 门店编号:H001")
fmt.Printf(" 门店编号:%s\n", shop.Code)
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("═══════════════════════════════════════")
fmt.Printf(" 仓库:%s / %s\n", wh1.Name, wh2.Name)
fmt.Printf(" 商品分类:白酒 / 进口烈酒,共 %d 种商品\n", len(prods))
fmt.Println(" 往来单位:4 供应商 + 3 客户")
fmt.Println(" 入库单:5 张(2已审核 / 1待审核 / 1草稿 / 1进口专库已审核)")
fmt.Println(" 出库单:4 张(2已审核 / 1待审核 / 1草稿")
fmt.Println(" 财务记录:2 应收 + 1 收款")
fmt.Println("═══════════════════════════════════════════")
}
// ── 辅助函数 ────────────────────────────────────────────
// ── 辅助类型 ────────────────────────────────────────────
type inItemSeed struct {
productID uint64
qty float64
price float64
batchNo string
remark string
}
type outItemSeed struct {
productID uint64
qty float64
price float64
remark string
}
type stockInResult struct {
order model.StockInOrder
items []inItemSeed
}
type stockOutResult struct {
order model.StockOutOrder
items []outItemSeed
}
// ── 核心 upsert ────────────────────────────────────────────
// upsert 通用:查不到就调 factory 建,返回具体值(interface{}
func upsert(db *gorm.DB, dest any, cond string, args ...any) any {
// 最后一个 arg 是 factory func
factoryIdx := len(args) - 1
factory := args[factoryIdx].(func() any)
queryArgs := args[:factoryIdx]
found := false
switch v := dest.(type) {
case *model.Shop:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.Shop)
db.Create(obj)
fmt.Printf("✅ 门店:%s (%s)\n", obj.Name, obj.Code)
return *obj
}
fmt.Printf("⏭ 门店:%s (%s)\n", v.Name, v.Code)
return *v
case *model.User:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.User)
db.Create(obj)
fmt.Printf("✅ 用户:%s%s\n", obj.Username, obj.Role)
return *obj
}
fmt.Printf("⏭ 用户:%s\n", v.Username)
return *v
case *model.Warehouse:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.Warehouse)
db.Create(obj)
fmt.Printf("✅ 仓库:%s%s\n", obj.Name, obj.Location)
return *obj
}
fmt.Printf("⏭ 仓库:%s\n", v.Name)
return *v
case *model.ProductCategory:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.ProductCategory)
db.Create(obj)
fmt.Printf("✅ 分类:%s\n", obj.Name)
return *obj
}
fmt.Printf("⏭ 分类:%s\n", v.Name)
return *v
case *model.Product:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.Product)
db.Create(obj)
fmt.Printf("✅ 商品:%s%s\n", obj.Name, obj.Code)
return *obj
}
fmt.Printf("⏭ 商品:%s\n", v.Name)
return *v
case *model.Partner:
found = db.Where(cond, queryArgs...).First(v).Error == nil
if !found {
obj := factory().(*model.Partner)
db.Create(obj)
fmt.Printf("✅ 往来单位:%s%s\n", obj.Name, obj.Type)
return *obj
}
fmt.Printf("⏭ 往来单位:%s\n", v.Name)
return *v
}
return nil
}
// ── 入库单 ────────────────────────────────────────────
func createStockInOrder(
db *gorm.DB, shopID, whID, opID, partnerID uint64,
orderNo string, date model.Date, status, orderType, remark string,
items []inItemSeed,
reviewerID uint64, reviewedAt *time.Time,
) stockInResult {
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: orderType,
WarehouseID: whID,
PartnerID: &partnerID,
OperatorID: opID,
Status: status,
OrderDate: date,
Remark: remark,
}
if reviewerID > 0 {
o.ReviewerID = &reviewerID
o.ReviewedAt = reviewedAt
}
db.Create(&o)
var total float64
for _, it := range items {
item := model.StockInItem{
OrderID: o.ID,
ShopID: shopID,
ProductID: it.productID,
Quantity: it.qty,
UnitPrice: it.price,
TotalPrice: it.qty * it.price,
BatchNo: it.batchNo,
Remark: it.remark,
}
db.Create(&item)
total += item.TotalPrice
}
db.Model(&o).Update("total_amount", total)
o.TotalAmount = total
fmt.Printf("✅ 入库单:%s [%s] 供应商ID=%d 仓库ID=%d 金额=%.0f\n",
orderNo, status, partnerID, whID, total)
} else {
fmt.Printf("⏭ 入库单:%s\n", orderNo)
}
return stockInResult{order: o, items: items}
}
// ── 出库单 ────────────────────────────────────────────
func createStockOutOrder(
db *gorm.DB, shopID, whID, opID, partnerID uint64,
orderNo string, date model.Date, status, orderType, remark string,
items []outItemSeed,
reviewerID uint64, reviewedAt *time.Time,
) stockOutResult {
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: orderType,
WarehouseID: whID,
PartnerID: &partnerID,
OperatorID: opID,
Status: status,
OrderDate: date,
Remark: remark,
}
if reviewerID > 0 {
o.ReviewerID = &reviewerID
o.ReviewedAt = reviewedAt
}
db.Create(&o)
var total float64
for _, it := range items {
item := model.StockOutItem{
OrderID: o.ID,
ShopID: shopID,
ProductID: it.productID,
Quantity: it.qty,
UnitPrice: it.price,
TotalPrice: it.qty * it.price,
Remark: it.remark,
}
db.Create(&item)
total += item.TotalPrice
}
db.Model(&o).Update("total_amount", total)
o.TotalAmount = total
fmt.Printf("✅ 出库单:%s [%s] 客户ID=%d 仓库ID=%d 金额=%.0f\n",
orderNo, status, partnerID, whID, total)
} else {
fmt.Printf("⏭ 出库单:%s\n", orderNo)
}
return stockOutResult{order: o, items: items}
}
// ── 库存 ────────────────────────────────────────────
func updateInventoryBatch(db *gorm.DB, shopID, whID, opID uint64, r stockInResult) {
for _, it := range r.items {
var inv model.Inventory
db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?", shopID, whID, it.productID).First(&inv)
before := inv.Quantity
after := before + it.qty
if inv.ID == 0 {
inv = model.Inventory{ShopID: shopID, WarehouseID: whID, ProductID: it.productID, Quantity: after}
db.Create(&inv)
} else {
db.Model(&inv).Update("quantity", after)
}
db.Create(&model.InventoryLog{
ShopID: shopID, WarehouseID: whID, ProductID: it.productID,
Direction: "in", Quantity: it.qty, QtyBefore: before, QtyAfter: after,
RefType: "stock_in", RefID: r.order.ID, OperatorID: &opID,
})
}
}
func deductInventoryBatch(db *gorm.DB, shopID, whID, opID uint64, r stockOutResult) {
for _, it := range r.items {
var inv model.Inventory
db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?", shopID, whID, it.productID).First(&inv)
before := inv.Quantity
after := before - it.qty
if after < 0 {
after = 0
}
db.Model(&inv).Update("quantity", after)
db.Create(&model.InventoryLog{
ShopID: shopID, WarehouseID: whID, ProductID: it.productID,
Direction: "out", Quantity: it.qty, QtyBefore: before, QtyAfter: after,
RefType: "stock_out", RefID: r.order.ID, OperatorID: &opID,
})
}
}
// ── 财务记录 ────────────────────────────────────────────
func createFinanceRecord(db *gorm.DB, shopID, partnerID, opID uint64,
ftype string, amount, balance float64, refType string, refID uint64,
date model.Date, remark string,
) {
var f model.FinanceRecord
if db.Where("shop_id = ? AND ref_type = ? AND ref_id = ? AND type = ?", shopID, refType, refID, ftype).First(&f).Error != nil {
f = model.FinanceRecord{
ShopID: shopID,
PartnerID: &partnerID,
Type: ftype,
Amount: amount,
Balance: balance,
RefType: refType,
RefID: &refID,
OperatorID: opID,
RecordDate: date.Time,
Remark: remark,
}
db.Create(&f)
fmt.Printf("✅ 财务记录:%s %.0f 元(%s\n", ftype, amount, remark)
}
}
// ── 工具函数 ────────────────────────────────────────────
func mustHash(plain string) string {
b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost)
@@ -302,66 +688,6 @@ func mustHash(plain string) string {
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 {
@@ -370,94 +696,3 @@ func upsertNumberRule(db *gorm.DB, shopID uint64, ruleType, prefix string, curre
fmt.Printf("✅ 编号规则:%s → %s\n", ruleType, prefix)
}
}
func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date model.Date) 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 model.Date) 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)
}