feat(backend): 出入库汇总近30天滚动窗口 + 跨租户安全加固
- summaryBounds:本月/近30天滚动双口径(stock-in/out Summary ?window=rolling30) - security(SEC-001):新增 ownership.go ensureShopRef 写入侧防线(stock-in/out/finance/ 盘点建单的 warehouse/partner/product 外键归属校验);读取侧全部 Preload 补 shop_id 作用域,finance Summary JOIN 补租户条件;回归测试 CrossTenantRefs - security(SEC-002):release 模式 JWT 密钥为空/默认值时拒绝启动 - gofmt 对齐若干 model/cmd 文件 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -230,7 +230,8 @@ func (h *InventoryHandler) Logs(c *gin.Context) {
|
||||
query.Count(&total)
|
||||
|
||||
logs := make([]model.InventoryLog, 0)
|
||||
query.Preload("Product").Preload("Warehouse").Offset((page-1)*pageSize).Limit(pageSize).Order("id DESC").Find(&logs)
|
||||
query.Preload("Product", "shop_id = ?", shopID).
|
||||
Preload("Warehouse", "shop_id = ?", shopID).Offset((page - 1) * pageSize).Limit(pageSize).Order("id DESC").Find(&logs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": logs, "total": total, "page": page, "page_size": pageSize})
|
||||
}
|
||||
@@ -250,6 +251,18 @@ func (h *InventoryHandler) CreateCheck(c *gin.Context) {
|
||||
req.OperatorID = operatorID
|
||||
req.Status = "draft"
|
||||
|
||||
// SEC-P01:外键归属校验——仓库与明细商品必须属于本店,拒绝跨租户脏引用
|
||||
if err := ensureShopRef(h.db, "warehouses", req.WarehouseID, shopID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
for i := range req.Items {
|
||||
if err := ensureShopRef(h.db, "products", req.Items[i].ProductID, shopID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 自动填入系统库存数量(SUM 聚合)
|
||||
for i := range req.Items {
|
||||
req.Items[i].ShopID = shopID
|
||||
@@ -274,7 +287,7 @@ func (h *InventoryHandler) CreateCheck(c *gin.Context) {
|
||||
func (h *InventoryHandler) GetCheck(c *gin.Context) {
|
||||
shopID := middleware.GetShopID(c)
|
||||
var check model.InventoryCheck
|
||||
if err := h.db.Preload("Items.Product").
|
||||
if err := h.db.Preload("Items.Product", "shop_id = ?", shopID).
|
||||
Where("id = ? AND shop_id = ?", c.Param("id"), shopID).
|
||||
First(&check).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
@@ -289,7 +302,7 @@ func (h *InventoryHandler) CompleteCheck(c *gin.Context) {
|
||||
checkID, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
|
||||
var check model.InventoryCheck
|
||||
if err := h.db.Preload("Items.Product").
|
||||
if err := h.db.Preload("Items.Product", "shop_id = ?", shopID).
|
||||
Where("id = ? AND shop_id = ?", checkID, shopID).
|
||||
First(&check).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
|
||||
Reference in New Issue
Block a user