Files
bj_power/bj_power_wms/internal/handler/producible.go
T
SunYF 1cc93795ce feat(mes): 添加定时同步可生产数量到WMS及BOM项相关标准字段
- 在main.go中添加定时任务每10分钟同步可生产数量到WMS系统
- 为BomItem实体添加relatedStandard字段及相关CRUD方法
- 为InspectionRecord实体添加reportNo、materialCode、materialName等字段
- 更新ent schema确保新字段的验证和默认值设置
- 添加必要的数据库迁移和字段映射逻辑
2026-09-17 12:49:07 +08:00

121 lines
3.9 KiB
Go

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})
}
}