package handler import ( "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/wangjia/jiu/backend/internal/middleware" ) type AdminHandler struct{ db *gorm.DB } func NewAdminHandler(db *gorm.DB) *AdminHandler { return &AdminHandler{db: db} } // ClearData 清空指定表数据(仅限当前 shop,不影响其他租户) func (h *AdminHandler) ClearData(c *gin.Context) { shopID := middleware.GetShopID(c) var body struct { Tables []string `json:"tables"` } if err := c.ShouldBindJSON(&body); err != nil || len(body.Tables) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "tables is required"}) return } // 允许清空的表名白名单(key=前端传入标识,value=DB表名列表) allowed := map[string][]string{ "stock_in": {"stock_in_items", "stock_in_orders"}, "stock_out": {"stock_out_items", "stock_out_orders"}, "inventory": {"inventory_logs", "inventories"}, "products": {"products"}, "partners": {"partners"}, } cleared := []string{} err := h.db.Transaction(func(tx *gorm.DB) error { for _, t := range body.Tables { tables, ok := allowed[t] if !ok { continue } for _, tbl := range tables { if err := tx.Exec("DELETE FROM "+tbl+" WHERE shop_id = ?", shopID).Error; err != nil { return err } } cleared = append(cleared, t) } return nil }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"cleared": cleared}) } // ReconcileInventory 对账:检查 inventories 表当前库存与 inventory_logs 流水之和是否吻合。 // 返回所有差异行,正常情况下 data 为空数组。 // 支持可选 ?shop_id=N 参数(superadmin 可指定任意门店)。 func (h *AdminHandler) ReconcileInventory(c *gin.Context) { // superadmin 路由,可通过参数指定 shop;不指定则对所有门店执行 var shopFilter *uint64 if s := c.Query("shop_id"); s != "" { v, err := strconv.ParseUint(s, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid shop_id"}) return } shopFilter = &v } _ = middleware.GetShopID // superadmin 不依赖 JWT shop_id type row struct { ShopID uint64 `json:"shop_id"` WarehouseID uint64 `json:"warehouse_id"` ProductID uint64 `json:"product_id"` InvQty float64 `json:"inv_qty"` LogQty float64 `json:"log_qty"` Diff float64 `json:"diff"` } // inventories 侧:当前各(shop,warehouse,product)库存总量 invQuery := h.db.Table("inventories"). Select("shop_id, warehouse_id, product_id, COALESCE(SUM(quantity),0) AS inv_qty"). Where("deleted_at IS NULL"). Group("shop_id, warehouse_id, product_id") if shopFilter != nil { invQuery = invQuery.Where("shop_id = ?", *shopFilter) } // inventory_logs 侧:同维度流水净量(in 为正,out 为负) logQuery := h.db.Table("inventory_logs"). Select("shop_id, warehouse_id, product_id, " + "COALESCE(SUM(CASE WHEN direction='in' THEN quantity ELSE -quantity END),0) AS log_qty"). Group("shop_id, warehouse_id, product_id") if shopFilter != nil { logQuery = logQuery.Where("shop_id = ?", *shopFilter) } // 全外连接找差异 type invRow struct { ShopID uint64 `gorm:"column:shop_id"` WarehouseID uint64 `gorm:"column:warehouse_id"` ProductID uint64 `gorm:"column:product_id"` InvQty float64 `gorm:"column:inv_qty"` } type logRow struct { ShopID uint64 `gorm:"column:shop_id"` WarehouseID uint64 `gorm:"column:warehouse_id"` ProductID uint64 `gorm:"column:product_id"` LogQty float64 `gorm:"column:log_qty"` } var invRows []invRow var logRows []logRow if err := invQuery.Scan(&invRows).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "inventory query failed: " + err.Error()}) return } if err := logQuery.Scan(&logRows).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "log query failed: " + err.Error()}) return } // 合并为 map 再求差 type key struct{ S, W, P uint64 } m := map[key]*row{} for _, r := range invRows { k := key{r.ShopID, r.WarehouseID, r.ProductID} m[k] = &row{ShopID: r.ShopID, WarehouseID: r.WarehouseID, ProductID: r.ProductID, InvQty: r.InvQty} } for _, r := range logRows { k := key{r.ShopID, r.WarehouseID, r.ProductID} if _, ok := m[k]; !ok { m[k] = &row{ShopID: r.ShopID, WarehouseID: r.WarehouseID, ProductID: r.ProductID} } m[k].LogQty = r.LogQty } diffs := make([]row, 0) for _, v := range m { v.Diff = v.InvQty - v.LogQty if v.Diff < -0.001 || v.Diff > 0.001 { diffs = append(diffs, *v) } } c.JSON(http.StatusOK, gin.H{"data": diffs, "total": len(diffs)}) }