feat(backend): 入库「确认进价」前向补偿(暂估价→真实价)

调货 0 价入库并已审核/出库后,价格确定时补填真实进价:一事务内
回填入库明细/库存批次/已出库成本快照,重算入库单总额,按差额补
应付流水,同步参考进价。按 product_id 精确命中(序列号 1:1),
不动售价/应收。仅 approved 单、管理员/超管,差额幂等可重复确认。

新增 POST /stock-in/orders/:id/confirm-cost 及 4 个测试用例。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-23 20:07:49 +08:00
parent 7969bb41e3
commit 3625240839
5 changed files with 337 additions and 0 deletions
+38
View File
@@ -315,3 +315,41 @@ func (h *StockInHandler) Return(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"message": "returned"})
}
// ConfirmCost POST /api/v1/stock-in/orders/:id/confirm-cost
// 确认进价:调货等以 0 价(暂估)入库并已审核后,价格确定时补填真实进价。
// 前向补偿(更新库存成本、回填已出库成本快照、补应付差额),不反审核。
// 仅 approved 单、管理员/超管(service 内判权)。
func (h *StockInHandler) ConfirmCost(c *gin.Context) {
shopID := middleware.GetShopID(c)
userID := middleware.GetUserID(c)
role := middleware.GetRole(c)
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
var req struct {
Items []service.CostConfirmItem `json:"items"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if len(req.Items) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "未提供要确认的明细"})
return
}
for _, it := range req.Items {
if it.UnitPrice <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "进价必须大于 0"})
return
}
}
if err := h.stockSvc.ConfirmStockInCost(shopID, id, userID, role, req.Items); err != nil {
if errors.Is(err, service.ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权确认进价"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "cost confirmed"})
}