Files
bj_power/bj_power_mes/internal/handler/station_ops.go
T
SunYF 4a5e2d7bac feat: 完成多模块迭代更新
- 新增过程巡检按步骤上报、数量不符上报、工位退料功能
- 增加工单工程编号三级归属字段
- 新增物料安全库存与缺货预警
- 新增附件管理、退库单管理模块
- 优化生产看板展示逻辑与前端页面文案
- 清理冗余的工艺路线相关代码与备份文件
2026-09-15 16:37:39 +08:00

222 lines
7.8 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/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)
}
}