fix(shipping): 使用资产价值快照价格确保发货与分解价格一致

修复改价后发货价格与分解价格不一致的问题:
- 发货时优先使用 user_inventory.value_cents 快照价格
- 后台发货列表使用 shipping_records.price 存储的快照价格
- 确保盈亏统计时价格数据准确一致
This commit is contained in:
Zuncle 2026-03-05 17:54:58 +08:00
parent a29669ccf6
commit 2aa7cdbd61
2 changed files with 114 additions and 27 deletions

View File

@ -216,11 +216,46 @@ func (h *handler) ListShippingOrders() core.HandlerFunc {
}
}
// 获取商品信息(去重并计数)
pidCounts := make(map[int64]int64)
for _, pid := range a.pid {
pidCounts[pid]++
// 获取商品信息(去重并计数,使用发货记录中的价格快照)
// 按商品ID聚合价格和数量
type productInfo struct {
Name string
Image string
Price int64 // 使用发货记录中的快照价格
Count int64
}
productMap := make(map[int64]*productInfo)
// 查询发货记录获取每个商品的快照价格
if len(a.recordIDs) > 0 {
records, _ := h.readDB.ShippingRecords.WithContext(ctx.RequestContext()).ReadDB().
Where(h.readDB.ShippingRecords.ID.In(a.recordIDs...)).
Find()
for _, r := range records {
if r.ProductID <= 0 {
continue
}
if info, ok := productMap[r.ProductID]; ok {
info.Count++
} else {
// 查询商品名称和图片
var prodName, prodImage string
if prod, _ := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().
Where(h.readDB.Products.ID.Eq(r.ProductID)).First(); prod != nil {
prodName = prod.Name
prodImage = prod.ImagesJSON
}
productMap[r.ProductID] = &productInfo{
Name: prodName,
Image: prodImage,
Price: r.Price, // 使用发货记录中的快照价格
Count: 1,
}
}
}
}
var products []struct {
ID int64 `json:"id"`
Name string `json:"name"`
@ -228,22 +263,20 @@ func (h *handler) ListShippingOrders() core.HandlerFunc {
Price int64 `json:"price"`
Count int64 `json:"count"`
}
for pid, count := range pidCounts {
if prod, _ := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.Eq(pid)).First(); prod != nil {
products = append(products, struct {
ID int64 `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Price int64 `json:"price"`
Count int64 `json:"count"`
}{
ID: prod.ID,
Name: prod.Name,
Image: prod.ImagesJSON, // 商品图片JSON
Price: prod.Price,
Count: count,
})
}
for pid, info := range productMap {
products = append(products, struct {
ID int64 `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Price int64 `json:"price"`
Count int64 `json:"count"`
}{
ID: pid,
Name: info.Name,
Image: info.Image,
Price: info.Price, // 使用快照价格
Count: info.Count,
})
}
items = append(items, &ShippingOrderGroup{

View File

@ -200,11 +200,19 @@ func (s *service) SubmitAddressShare(ctx context.Context, shareToken string, nam
}
// c. 创建发货记录 (归属于 targetUserID)
var price int64
if inv.ProductID > 0 {
// 使用资产价值快照,确保价格与分解时一致
price := inv.ValueCents
if price <= 0 && inv.ProductID > 0 {
// 如果没有快照价格,回退到商品当前价格并记录快照
var p model.Products
if err := tx.Table("products").Where("id = ?", inv.ProductID).First(&p).Error; err == nil {
price = p.Price
// 回写资产价值快照
tx.Table("user_inventory").Where("id = ?", inv.ID).Updates(map[string]interface{}{
"value_cents": price,
"value_source": 2,
"value_snapshot_at": time.Now(),
})
}
}
@ -265,10 +273,15 @@ func (s *service) RequestShippingWithBatch(ctx context.Context, userID int64, in
addrID = addr.ID
}
var price int64
if inv.ProductID > 0 {
// 使用资产价值快照,确保价格与分解时一致
price := inv.ValueCents
if price <= 0 && inv.ProductID > 0 {
// 如果没有快照价格,回退到商品当前价格并记录快照
if p, e := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(inv.ProductID)).First(); e == nil && p != nil {
price = p.Price
// 回写资产价值快照
s.repo.GetDbW().Exec("UPDATE user_inventory SET value_cents=?, value_source=?, value_snapshot_at=NOW(3) WHERE id=? AND user_id=?",
price, 2, inventoryID, userID)
}
}
@ -443,8 +456,26 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
return addrID, batchNo, success, skipped, failed, nil
}
// 7. 批量查询products获取价格一次查询替代N次
// 7. 批量查询products获取价格用于没有快照价格的资产
productMap := make(map[int64]int64) // productID -> price
// 收集需要回写快照的资产
type valueFix struct {
ID int64
ValueCents int64
ValueSource int32
}
valueFixes := make([]valueFix, 0)
// 先检查哪些资产没有快照价格
for _, inv := range validInvs {
if inv.ValueCents <= 0 && inv.ProductID > 0 {
if _, ok := productIDSet[inv.ProductID]; !ok {
productIDSet[inv.ProductID] = struct{}{}
productIDs = append(productIDs, inv.ProductID)
}
}
}
if len(productIDs) > 0 {
prods, _ := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.In(productIDs...)).Find()
for _, p := range prods {
@ -455,9 +486,22 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
// 8. 单事务批量处理
validIDs := make([]int64, 0, len(validInvs))
err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
// 批量插入shipping_records
// 批量插入shipping_records(使用资产价值快照)
for _, inv := range validInvs {
price := productMap[inv.ProductID]
// 优先使用资产价值快照,确保与分解价格一致
price := inv.ValueCents
valueSource := inv.ValueSource
if price <= 0 && inv.ProductID > 0 {
// 如果没有快照价格,回退到商品当前价格
price = productMap[inv.ProductID]
valueSource = 2
// 记录需要回写快照的资产
valueFixes = append(valueFixes, valueFix{
ID: inv.ID,
ValueCents: price,
ValueSource: valueSource,
})
}
if errExec := tx.Exec(
"INSERT INTO shipping_records (user_id, order_id, order_item_id, inventory_id, product_id, quantity, price, address_id, status, batch_no, remark) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
userID, inv.OrderID, 0, inv.ID, inv.ProductID, 1, price, addrID, 1, batchNo, "batch_request_shipping",
@ -467,6 +511,16 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
validIDs = append(validIDs, inv.ID)
}
// 回写资产价值快照(用于之前没有快照的资产)
for _, fix := range valueFixes {
if err := tx.Exec(
"UPDATE user_inventory SET value_cents=?, value_source=?, value_snapshot_at=NOW(3) WHERE id=? AND user_id=?",
fix.ValueCents, fix.ValueSource, fix.ID, userID,
).Error; err != nil {
return err
}
}
// 批量更新inventory状态一次UPDATE替代N次
if len(validIDs) > 0 {
if errExec := tx.Exec(