package handler import ( "net/http" "time" "bj_power_wms/internal/svc" ) // producibleSyncHandler 接收 MES 推送的可生产数量快照 + 未来5天物料需求(X-API-TOKEN 内部接口) // // 业务背景(问题记录 L470-474 + L256): // - 可生产套数 = min(各物料可用量 ÷ 单台用量),BOM 只在 MES、库存只在 WMS, // 由 MES 计算后推送快照,WMS 库存页卡片只读展示(两系统独立运行约束,WMS 不反向依赖 MES)。 // - 未来5天需求量 = 近5天日排产 × 物料清单单台用量,是备料四态「预警」判定的数据源。 // // 推送语义:全量覆盖(先清后写),单事务提交;MES 侧每次重算后整体推送一次。 func producibleSyncHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { Items []struct { ProductCode string `json:"productCode"` ProductName string `json:"productName"` ProducibleQty int `json:"producibleQty"` ShortText string `json:"shortText"` // 短板物料描述,如 "SCR-M4-12缺20; XX缺5" ComputedAt int64 `json:"computedAt"` } `json:"items"` Demands []struct { MaterialCode string `json:"materialCode"` Future5Qty int `json:"future5Qty"` } `json:"demands"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误: "+err.Error()) return } now := time.Now().Unix() tx, err := ctx.EntClient.Tx(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error()) return } committed := false defer func() { if !committed { _ = tx.Rollback() } }() // 可生产快照:全量覆盖 if _, err = tx.ProducibleSnapshot.Delete().Exec(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "清理旧快照失败: "+err.Error()) return } for _, it := range req.Items { if it.ProductCode == "" { continue } if _, err = tx.ProducibleSnapshot.Create(). SetProductCode(it.ProductCode). SetNillableProductName(strPtr(it.ProductName)). SetProducibleQty(it.ProducibleQty). SetNillableShortText(strPtr(it.ShortText)). SetComputedAt(it.ComputedAt). SetSyncedAt(now). Save(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "写入快照失败: "+err.Error()) return } } // 未来5天需求:全量覆盖(demands 缺省时不动,允许只推快照不推需求) if req.Demands != nil { if _, err = tx.MaterialDemand.Delete().Exec(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "清理旧需求失败: "+err.Error()) return } for _, d := range req.Demands { if d.MaterialCode == "" { continue } if _, err = tx.MaterialDemand.Create(). SetMaterialCode(d.MaterialCode). SetFuture5Qty(d.Future5Qty). SetUpdatedAt(now). Save(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "写入需求失败: "+err.Error()) return } } } if err = tx.Commit(); err != nil { fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error()) return } committed = true ok(w, map[string]any{"items": len(req.Items), "demands": len(req.Demands)}) } } // queryProducibleHandler 可生产数量快照查询(WMS 库存页卡片数据源) // 返回 MES 最近推送的快照列表 + 同步时间;从未推送过则返回空列表,前端提示"暂无数据(等待 MES 同步)"。 func queryProducibleHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { list, err := ctx.EntClient.ProducibleSnapshot.Query().All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } syncedAt := int64(0) for _, s := range list { if s.SyncedAt > syncedAt { syncedAt = s.SyncedAt } } ok(w, map[string]any{"list": list, "syncedAt": syncedAt}) } }