package handler import ( "net/http" "time" "bj_power_wms/internal/svc" ) // 可生产数量(问题记录 L470-474 + L256): // - 可生产套数 = min(各物料可用量 ÷ 单台用量)。BOM 单台用量在 MES、库存可用量在 WMS, // 由 MES 实时计算,WMS 打开本页时按需拉一次(两系统之间不做定时/批量同步)。 // - 未来5天需求量 = 近5天日排产 × 物料清单单台用量,是备料四态「预警」判定的数据源。 // // 拉取语义:每次查询先实时取 MES 结果,再全量覆盖本地快照(单事务),保证展示与 MES 当前口径一致。 // queryProducibleHandler GET /api/stock/producible 可生产数量(实时拉取 MES 后落快照再返回) func queryProducibleHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if ctx.Mes == nil { fail(w, http.StatusBadRequest, "未配置 MES 地址,无法获取可生产数量") return } res, err := ctx.Mes.FetchProducible(ctx0()) if err != nil { fail(w, http.StatusBadGateway, "拉取 MES 可生产数量失败: "+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 res.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 } } if _, err = tx.MaterialDemand.Delete().Exec(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "清理旧需求失败: "+err.Error()) return } for _, d := range res.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 list, err := ctx.EntClient.ProducibleSnapshot.Query().All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, map[string]any{"list": list, "syncedAt": now}) } }