feat(backend): 出入库定价字段消歧 + 总利润落库 + 成本仅管理员可见
- 字段重命名(旧列弃用保留,启动幂等回填 BackfillPricingColumns): 入库 unit_price/total_price/total_amount → cost_price/cost_amount/cost_total; 出库同前缀 + 新增 sale_amount(售价小计)/ sale_total(应收)/ profit_total(总利润) - 建单落三值;确认售价联动重算 sale_amount/sale_total/profit_total; 确认进价回填成本后联动重算受影响出库单利润(recalcStockOutProfit) - 出库 List/Get 对 operator/readonly 抹零成本与利润(stripStockOutCost 服务端兜底) - 兼容一版:Create/Update 接受旧 key unit_price 回落(v1.0.87 及之前客户端) - SUM 聚合/价格趋势/导入/种子工具同步切新列;测试全量改名 + 6 个新回归 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -193,7 +193,7 @@ func (h *StockInHandler) Summary(c *gin.Context) {
|
||||
}
|
||||
h.db.Model(&model.StockInOrder{}).
|
||||
Where("shop_id = ? AND deleted_at IS NULL AND order_date >= ? AND order_date < ?", shopID, from, to).
|
||||
Select("COUNT(*) AS cnt, COALESCE(SUM(total_amount),0) AS amt").Scan(&r)
|
||||
Select("COUNT(*) AS cnt, COALESCE(SUM(cost_total),0) AS amt").Scan(&r)
|
||||
return r.Cnt, r.Amt
|
||||
}
|
||||
var s stockSummary
|
||||
@@ -258,19 +258,23 @@ func (h *StockInHandler) Create(c *gin.Context) {
|
||||
for i := range req.Items {
|
||||
it := &req.Items[i]
|
||||
it.ShopID = shopID
|
||||
// 兼容旧客户端(≤1.0.87):unit_price 回落为 cost_price(新 key 优先)
|
||||
if it.CostPrice == 0 && it.LegacyUnitPrice != nil {
|
||||
it.CostPrice = *it.LegacyUnitPrice
|
||||
}
|
||||
if it.BatchNo == "" {
|
||||
it.BatchNo = fmt.Sprintf("%s-%02d", req.OrderNo, i+1)
|
||||
}
|
||||
prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice, it.SalePrice)
|
||||
prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.CostPrice, it.SalePrice)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
it.ProductID = prod.ID
|
||||
it.ProductCode = prod.Code // 保留快照,兼容现有查询(瘦身阶段再去)
|
||||
it.TotalPrice = it.Quantity * it.UnitPrice
|
||||
total += it.TotalPrice
|
||||
it.CostAmount = it.Quantity * it.CostPrice
|
||||
total += it.CostAmount
|
||||
}
|
||||
req.TotalAmount = total
|
||||
req.CostTotal = total
|
||||
return tx.Create(&req).Error
|
||||
})
|
||||
if err != nil {
|
||||
@@ -316,25 +320,28 @@ func (h *StockInHandler) Update(c *gin.Context) {
|
||||
it := &req.Items[i]
|
||||
it.ShopID = shopID
|
||||
it.OrderID = order.ID
|
||||
if it.CostPrice == 0 && it.LegacyUnitPrice != nil {
|
||||
it.CostPrice = *it.LegacyUnitPrice
|
||||
}
|
||||
if it.BatchNo == "" {
|
||||
it.BatchNo = fmt.Sprintf("%s-%02d", order.OrderNo, i+1)
|
||||
}
|
||||
// 编辑草稿:旧明细已删,每条按新模型重建独立产品(旧草稿 product 无库存,暂留待后续清理)
|
||||
prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice, it.SalePrice)
|
||||
prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.CostPrice, it.SalePrice)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
it.ProductID = prod.ID
|
||||
it.ProductCode = prod.Code
|
||||
it.TotalPrice = it.Quantity * it.UnitPrice
|
||||
total += it.TotalPrice
|
||||
it.CostAmount = it.Quantity * it.CostPrice
|
||||
total += it.CostAmount
|
||||
}
|
||||
updates := map[string]interface{}{
|
||||
"warehouse_id": req.WarehouseID,
|
||||
"partner_id": req.PartnerID,
|
||||
"order_date": req.OrderDate,
|
||||
"remark": req.Remark,
|
||||
"total_amount": total,
|
||||
"cost_total": total,
|
||||
}
|
||||
if err := tx.Model(&order).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user