package handler import ( "encoding/base64" "net/http" "os" "path/filepath" "strings" "time" "bj_power_mes/common/httpx" "bj_power_mes/internal/logic" "bj_power_mes/internal/svc" ) // ============ 工位终端 → MES(X-API-TOKEN 保护内部接口) ============ // StationEmergencyInternalHandler POST /api/internal/station/emergency // 工位终端应急呼叫:落预警中心消息(type=emergency_call),pad 顶部轮询提示。 func StationEmergencyInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { StationNo int `json:"stationNo"` OrderNo string `json:"orderNo"` Remark string `json:"remark"` Operator string `json:"operator"` } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if err := logic.New(svcCtx).EmergencyCall(r.Context(), req.StationNo, req.OrderNo, req.Remark, req.Operator); err != nil { httpx.Fail(w, 2401, err.Error()) return } httpx.OkMessage(w, "应急呼叫已发送", nil) } } // StationQtyReportInternalHandler POST /api/internal/station/qty-report // 工位数量不符上报:落 material_qty_report + 预警中心消息(type=qty_diff)。 func StationQtyReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req logic.QtyReportReq if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if err := logic.New(svcCtx).CreateQtyReport(r.Context(), req); err != nil { httpx.Fail(w, 2402, err.Error()) return } httpx.OkMessage(w, "数量不符已上报", nil) } } // StationReturnMaterialInternalHandler POST /api/internal/station/return-material // 工位退料回库房:落事件日志 + 调 WMS 预建待确认退库单(WMS 确认收货后库存加回)。 func StationReturnMaterialInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { OrderNo string `json:"orderNo"` StationNo int `json:"stationNo"` Sn string `json:"sn"` MaterialCode string `json:"materialCode"` MaterialName string `json:"materialName"` Spec string `json:"spec"` Qty int `json:"qty"` Unit string `json:"unit"` Reason string `json:"reason"` Operator string `json:"operator"` } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if err := logic.New(svcCtx).StationReturnMaterial(r.Context(), req.OrderNo, req.StationNo, req.Sn, req.MaterialCode, req.MaterialName, req.Spec, req.Reason, req.Operator, req.Unit, req.Qty); err != nil { httpx.Fail(w, 2403, err.Error()) return } httpx.OkMessage(w, "退库单已提交", nil) } } // StationInspectionInternalHandler POST /api/internal/station/inspection // 工位终端提交巡检记录(签到/点检/过程巡检/完工确认/异常),复用与 PAD 巡检终端相同的落库逻辑。 // 工位终端无按钮级权限概念,仅按 category 合法性校验(CreateInspection 内部已做)。 func StationInspectionInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req logic.InspectionReq if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if err := logic.New(svcCtx).CreateInspection(r.Context(), req, req.Operator); err != nil { httpx.Fail(w, 2301, err.Error()) return } httpx.OkMessage(w, "巡检记录已提交", nil) } } // StationInspectionUploadInternalHandler POST /api/internal/station/inspection-upload // 工位终端巡检拍照上传(手机设备拍照)。前端把图片读成 base64 传 {filename, data}, // 服务端解码落盘到 Upload.Dir/日期目录,返回系统文件名(落库到 inspection_record.photo)。 func StationInspectionUploadInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { Filename string `json:"filename"` Data string `json:"data"` // base64(可带 data:image/xxx;base64, 前缀) } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if req.Data == "" { httpx.BadRequest(w, "图片数据为空") return } raw := req.Data if i := strings.Index(raw, ","); i >= 0 && strings.Contains(raw[:i], "base64") { raw = raw[i+1:] } b, err := base64.StdEncoding.DecodeString(raw) if err != nil { httpx.Fail(w, 2306, "图片解码失败:"+err.Error()) return } sub := time.Now().Format("2006-01-02") dir := filepath.Join(svcCtx.Config.Upload.Dir, sub) if err := os.MkdirAll(dir, 0o755); err != nil { httpx.Fail(w, 2306, "目录创建失败:"+err.Error()) return } name := req.Filename if name == "" || strings.ContainsAny(name, "/\\") { name = "insp_" + time.Now().Format("150405") + ".png" } // 文件名加日期前缀防重复 saveName := time.Now().Format("20060102") + "_" + filepath.Base(name) full := filepath.Join(dir, saveName) if err := os.WriteFile(full, b, 0o644); err != nil { httpx.Fail(w, 2306, "照片保存失败:"+err.Error()) return } httpx.Ok(w, map[string]any{"filename": sub + "/" + saveName, "url": "/api/v1/files/" + sub + "/" + saveName}) } } // ============ MES 前端(JWT 保护) ============ // EmergencyCallHandler POST /api/v1/alert/emergency(MES 前端也可发起应急呼叫) func EmergencyCallHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { StationNo int `json:"stationNo"` OrderNo string `json:"orderNo"` Remark string `json:"remark"` } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } if err := logic.New(svcCtx).EmergencyCall(r.Context(), req.StationNo, req.OrderNo, req.Remark, operator(r, "")); err != nil { httpx.Fail(w, 2401, err.Error()) return } httpx.OkMessage(w, "应急呼叫已发送", nil) } } // CreateQtyReportHandler POST /api/v1/qty-reports func CreateQtyReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req logic.QtyReportReq if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } req.Operator = operator(r, req.Operator) if err := logic.New(svcCtx).CreateQtyReport(r.Context(), req); err != nil { httpx.Fail(w, 2402, err.Error()) return } httpx.OkMessage(w, "数量不符已上报", nil) } } // ListQtyReportsHandler GET /api/v1/qty-reports?status= func ListQtyReportsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data, err := logic.New(svcCtx).ListQtyReports(r.Context(), r.URL.Query().Get("status"), r.URL.Query().Get("orderNo"), r.URL.Query().Get("materialCode")) if err != nil { httpx.Fail(w, 2404, err.Error()) return } httpx.Ok(w, data) } } // GenerateInterProcessInspectionHandler POST /api/v1/inspections/generate-inter-process // 一键生成《工序间检验记录》:汇总该工单 PROCESS 类巡检记录(按步骤维度)。 func GenerateInterProcessInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { OrderNo string `json:"orderNo"` } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") return } data, err := logic.New(svcCtx).GenerateInterProcessInspection(r.Context(), req.OrderNo) if err != nil { httpx.Fail(w, 2405, err.Error()) return } httpx.Ok(w, data) } }