Files
bj_power/bj_power_workstation/internal/handler/report.go
T
SunYF d609ff8a9e refactor: 重构工艺工序相关命名与数据模型
1.  全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2.  重构工单与工件工艺数据模型:
    - 新增工艺组合表flow_material作为备料/齐套唯一源头
    - 新增工位状态表station_state支持自主停单/恢复接单
    - 移除station_process派工表,改用工单工艺组合作为路线唯一源头
    - 替换processCode为flowId作为工艺关联标识
    - 重构工件当前进度字段为currentStationNo
3.  删除冗余的静态备份资源文件
4.  调整物料选择组件默认启用仅显示激活物料
5.  优化工单创建/编辑逻辑,新增工艺组合校验与保存
2026-09-22 11:32:29 +08:00

177 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"encoding/json"
"fmt"
"net/http"
"bj_power_workstation/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
// mesRespCode 解析 MES 统一响应 {code,message}。
// MES 业务失败约定为 HTTP 200 + code!=0httpx.Fail),HTTP 层无法区分,必须显式解析业务码。
// code==0 返回空串;code!=0 返回 message(供调用方直接透传给前端)。
func mesRespCode(respBody []byte) string {
if len(respBody) == 0 {
return ""
}
var mr struct {
Code int `json:"code"`
Message string `json:"message"`
}
if err := json.Unmarshal(respBody, &mr); err != nil {
return ""
}
if mr.Code == 0 {
return ""
}
if mr.Message != "" {
return mr.Message
}
return fmt.Sprintf("MES 拒绝本次操作(业务码 %d)", mr.Code)
}
// processDoneHandler POST /api/report/process-done
// {sn, orderNo, flowId, stationNo, steps:[{stepId,name,value,text}], binds:[{materialCode,bindValue}], operator}
// 流程:先冲刷一轮积压 → 实时报 MES /api/internal/station/report
// MES 不可达则写入 report_queue(kind=process_done) 并返回 data.queued=true(离线缓存稍后自动重传);
// MES 业务拒绝(HTTP200+code!=0,如装配物料未绑齐/电气件SN重复)直接失败回前端,绝不入离线队。
// binds 为本次工艺的装机绑定(结构件批次/电气件SN),MES 端落库 + 齐套强校验;离线重传时原样携带。
func processDoneHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
FlowId int `json:"flowId"`
StationNo int `json:"stationNo"`
Steps []struct {
StepId int `json:"stepId"`
Name string `json:"name"`
Value float64 `json:"value"`
Text string `json:"text"`
// CheckedneedCheck 步骤的「检测确认」结果,原样转发给 MES 做强校验(勿删,否则服务端拿不到确认)
Checked bool `json:"checked"`
} `json:"steps"`
Binds []struct {
MaterialCode string `json:"materialCode"`
BindValue string `json:"bindValue"`
} `json:"binds"`
Operator string `json:"operator"`
// 作业时长采集(P0-3):上料时刻/报工时刻(ISO8601),原样转发 MES 算 durationSec;离线重传时同 payload 保留
StartedAt string `json:"startedAt"`
EndedAt string `json:"endedAt"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Sn == "" || req.FlowId <= 0 || req.StationNo <= 0 {
fail(w, http.StatusBadRequest, "sn/flowId/stationNo 必填")
return
}
// 先尽力冲刷历史积压,失败不影响本次上报
ctx.Syncer.RunOnce(r.Context())
body := map[string]any{
"sn": req.Sn,
"orderNo": req.OrderNo,
"flowId": req.FlowId,
"stationNo": req.StationNo,
"steps": req.Steps,
"operator": req.Operator,
}
if len(req.Binds) > 0 {
body["binds"] = req.Binds
}
if req.StartedAt != "" {
body["startedAt"] = req.StartedAt
}
if req.EndedAt != "" {
body["endedAt"] = req.EndedAt
}
payloadBytes, _ := json.Marshal(body)
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "process_done",
"工艺完成上报 sn="+req.Sn+" stationNo="+itoa(req.StationNo)+" flowId="+itoa(req.FlowId)+" binds="+itoa(len(req.Binds)))
respBody, _, err := ctx.Mes.Post("/api/internal/station/report", json.RawMessage(payloadBytes))
if err != nil {
if qerr := ctx.Store.InsertQueueItem("process_done", string(payloadBytes)); qerr != nil {
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
return
}
logx.Errorf("工序完成上报失败已离线排队: %v", err)
ok(w, map[string]any{"queued": true})
return
}
// MES 业务拒绝(HTTP200+code!=0):缺料/SN冲突等,直接失败,不入队
if msg := mesRespCode(respBody); msg != "" {
logx.Errorf("工序完成上报被 MES 拒绝: %s", msg)
fail(w, http.StatusConflict, msg)
return
}
ok(w, map[string]any{"queued": false})
}
}
// tempStoreHandler POST /api/report/temp-store
// {sn, orderNo, stationNo, flowId, operator} → 调 MES /api/internal/station/checkin(半成品入库)。
// 不可达时以同样 payload 写入 report_queue(kind=temp_store)。
func tempStoreHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
StationNo int `json:"stationNo"`
FlowId int `json:"flowId"`
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
}
// 先尽力冲刷历史积压,失败不影响本次上报
ctx.Syncer.RunOnce(r.Context())
body := map[string]any{
"sn": req.Sn,
"orderNo": req.OrderNo,
"stationNo": req.StationNo,
"flowId": req.FlowId,
"operator": req.Operator,
}
payloadBytes, _ := json.Marshal(body)
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "temp_store",
"暂存/退回库房 sn="+req.Sn+" stationNo="+itoa(req.StationNo))
respBody, _, err := ctx.Mes.Post("/api/internal/station/checkin", json.RawMessage(payloadBytes))
if err != nil {
if qerr := ctx.Store.InsertQueueItem("temp_store", string(payloadBytes)); qerr != nil {
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
return
}
logx.Errorf("暂存退库上报失败已离线排队: %v", err)
ok(w, map[string]any{"queued": true})
return
}
// MES 业务拒绝(HTTP200+code!=0)→ 直接失败,不入队
if msg := mesRespCode(respBody); msg != "" {
logx.Errorf("暂存退库被 MES 拒绝: %s", msg)
fail(w, http.StatusConflict, msg)
return
}
ok(w, map[string]any{"queued": false})
}
}