Files
bj_power/bj_power_mes/internal/handler/station_ops.go
T

222 lines
7.8 KiB
Go
Raw Normal View History

2026-09-15 16:37:39 +08:00
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"
)
// ============ 工位终端 → MESX-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/emergencyMES 前端也可发起应急呼叫)
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)
}
}