package handler import ( "encoding/json" "net/http" "strconv" "strings" "bj_power_wms/ent" "bj_power_wms/ent/ordermaterialledger" "bj_power_wms/ent/packagebox" "bj_power_wms/ent/semifinished" "bj_power_wms/ent/serialnumber" "bj_power_wms/internal/svc" ) // 半成品 / 包装 / 台账 相关(盘点见 stocktake.go) // semiInboundHandler 半成品入库(携带已完成工序) // 与 MES 闭环:MES 工位报工暂存退库时调用本接口,把半成品 SN 入 WMS 库。 // 为保持两系统解耦,materialCode 不强制要求——优先用请求传入, // 其次按 SN 反查 WMS 序列号台账,都没有则留空(SN 本身即追溯锚点)。 func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { Sn string `json:"sn"` MaterialCode string `json:"materialCode"` DoneProcessCodes []int `json:"doneProcessCodes"` CompletedProcess string `json:"completedProcess"` ZoneCode string `json:"zoneCode"` OrderNo string `json:"orderNo"` Operator string `json:"operator"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.Sn == "" { fail(w, http.StatusBadRequest, "sn 必填") return } exists, err := ctx.EntClient.SemiFinished.Query(). Where(semifinished.SnEQ(req.Sn)). Exist(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } if exists { fail(w, http.StatusConflict, "半成品已存在: "+req.Sn) return } // 已完成工序:MES 传 doneProcessCodes([]int) 优先,否则用 completedProcess 字符串 completed := req.CompletedProcess if len(req.DoneProcessCodes) > 0 { parts := make([]string, 0, len(req.DoneProcessCodes)) for _, c := range req.DoneProcessCodes { parts = append(parts, strconv.Itoa(c)) } completed = strings.Join(parts, ",") } // 物料编码:优先请求传入,其次按 SN 反查序列号台账 materialCode := req.MaterialCode if materialCode == "" { if snRec, e := ctx.EntClient.SerialNumber.Query(). Where(serialnumber.SnCodeEQ(req.Sn)).Only(ctx0()); e == nil { materialCode = snRec.MaterialCode } } rec, err := ctx.EntClient.SemiFinished.Create(). SetSn(req.Sn). SetNillableMaterialCode(strPtr(materialCode)). SetNillableCompletedProcess(strPtr(completed)). SetNillableZoneCode(strPtr(req.ZoneCode)). SetStatus("在库"). SetNillableOrderNo(strPtr(req.OrderNo)). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } // 同时记入 SN 台账(半成品也是精密件,便于按 SN 追溯) ctx.EntClient.SerialNumber.Create(). SetSnCode(req.Sn). SetNillableMaterialCode(strPtr(materialCode)). SetStatus("在库"). SetNillableCurrentZone(strPtr(req.ZoneCode)). SaveX(ctx0()) ok(w, rec) } } // semiOutboundHandler 半成品出库(重上线) func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { Sn string `json:"sn"` ZoneCode string `json:"zoneCode"` Operator string `json:"operator"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.Sn == "" { fail(w, http.StatusBadRequest, "sn 必填") return } rec, err := ctx.EntClient.SemiFinished.Query(). Where(semifinished.SnEQ(req.Sn)). Only(ctx0()) if err != nil { fail(w, http.StatusNotFound, "半成品不存在: "+req.Sn) return } if rec.Status != "在库" { fail(w, http.StatusConflict, "半成品不在库: "+rec.Status) return } ctx.EntClient.SemiFinished.UpdateOneID(rec.ID). SetStatus("出库"). ExecX(ctx0()) ctx.EntClient.SerialNumber.Update(). Where(serialnumber.SnCodeEQ(req.Sn)). SetStatus("出库"). ExecX(ctx0()) ok(w, map[string]any{"sn": req.Sn, "status": "出库"}) } } // querySemiHandler 半成品查询 func querySemiHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { page := atoi(r.URL.Query().Get("page"), 1) pageSize := atoi(r.URL.Query().Get("pageSize"), 20) materialCode := r.URL.Query().Get("materialCode") status := r.URL.Query().Get("status") q := ctx.EntClient.SemiFinished.Query() if materialCode != "" { q = q.Where(semifinished.MaterialCodeEQ(materialCode)) } if status != "" { q = q.Where(semifinished.StatusEQ(status)) } total, err := q.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } list, err := q.Order(ent.Desc("id")). Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, map[string]any{ "total": total, "list": list, "page": page, "pageSize": pageSize, }) } } // packageBindHandler 装箱绑定(箱号 ↔ SN),支持合同号 func packageBindHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { BoxNo string `json:"boxNo"` SnList []string `json:"snList"` MaterialCode string `json:"materialCode"` ContractNo string `json:"contractNo"` Operator string `json:"operator"` Remark string `json:"remark"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.BoxNo == "" || len(req.SnList) == 0 { fail(w, http.StatusBadRequest, "箱号和 SN 清单必填") return } exists, err := ctx.EntClient.PackageBox.Query(). Where(packagebox.BoxNoEQ(req.BoxNo)). Exist(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } if exists { fail(w, http.StatusConflict, "箱号已存在: "+req.BoxNo) return } // SN 列表序列化 snJSON := toJSON(req.SnList) pkg, err := ctx.EntClient.PackageBox.Create(). SetBoxNo(req.BoxNo). SetSnList(snJSON). SetNillableMaterialCode(strPtr(req.MaterialCode)). SetQuantity(len(req.SnList)). SetNillableContractNo(strPtr(req.ContractNo)). SetNillableOperator(strPtr(req.Operator)). SetNillableRemark(strPtr(req.Remark)). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, pkg) } } // packageUpdateHandler 编辑装箱(重新装箱 SN / 修改合同号等) func packageUpdateHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { ID int `json:"id"` SnList []string `json:"snList"` MaterialCode string `json:"materialCode"` ContractNo string `json:"contractNo"` Operator string `json:"operator"` Remark string `json:"remark"` } if err := parseJSON(r, &req); err != nil || req.ID <= 0 { fail(w, http.StatusBadRequest, "参数错误") return } upd := ctx.EntClient.PackageBox.UpdateOneID(req.ID) if len(req.SnList) > 0 { upd.SetSnList(toJSON(req.SnList)).SetQuantity(len(req.SnList)) } upd.SetNillableMaterialCode(strPtr(req.MaterialCode)). SetNillableContractNo(strPtr(req.ContractNo)). SetNillableOperator(strPtr(req.Operator)). SetNillableRemark(strPtr(req.Remark)) pkg, err := upd.Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "更新装箱失败: "+err.Error()) return } ok(w, pkg) } } // toJSON 序列化任意值为 JSON 字符串 func toJSON(v any) string { b, err := json.Marshal(v) if err != nil { return "[]" } return string(b) } // queryPackageHandler 装箱查询(多条件:箱号/合同号/操作人/物料/时间范围) func queryPackageHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { boxNo := r.URL.Query().Get("boxNo") contractNo := r.URL.Query().Get("contractNo") operator := r.URL.Query().Get("operator") materialCode := r.URL.Query().Get("materialCode") startAt := r.URL.Query().Get("startAt") endAt := r.URL.Query().Get("endAt") page := atoi(r.URL.Query().Get("page"), 1) pageSize := atoi(r.URL.Query().Get("pageSize"), 20) q := ctx.EntClient.PackageBox.Query() if boxNo != "" { q = q.Where(packagebox.BoxNoContains(boxNo)) } if contractNo != "" { q = q.Where(packagebox.ContractNoContains(contractNo)) } if operator != "" { q = q.Where(packagebox.OperatorContains(operator)) } if materialCode != "" { q = q.Where(packagebox.MaterialCodeEQ(materialCode)) } if startAt != "" { start := atoi64(startAt, 0) if start > 0 { q = q.Where(packagebox.CreatedAtGTE(start)) } } if endAt != "" { end := atoi64(endAt, 0) if end > 0 { q = q.Where(packagebox.CreatedAtLTE(end)) } } total, err := q.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } list, err := q.Order(ent.Desc("id")). Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, map[string]any{ "total": total, "list": list, "page": page, "pageSize": pageSize, }) } } // ledger 相关:queryLedgerHandler 工单物料台账查询 func queryLedgerHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orderNo := r.URL.Query().Get("orderNo") materialCode := r.URL.Query().Get("materialCode") page := atoi(r.URL.Query().Get("page"), 1) pageSize := atoi(r.URL.Query().Get("pageSize"), 20) q := ctx.EntClient.OrderMaterialLedger.Query() if orderNo != "" { q = q.Where(ordermaterialledger.OrderNoEQ(orderNo)) } if materialCode != "" { q = q.Where(ordermaterialledger.MaterialCodeEQ(materialCode)) } total, err := q.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } list, err := q.Order(ent.Desc("id")). Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, map[string]any{ "total": total, "list": list, "page": page, "pageSize": pageSize, }) } }