Files
bj_power/bj_power_mes/internal/logic/station_ops.go
T
2026-09-18 18:54:54 +08:00

187 lines
6.6 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 logic
import (
"context"
"errors"
"fmt"
"time"
"bj_power_mes/ent"
"bj_power_mes/ent/inspectionrecord"
"bj_power_mes/ent/workorder"
)
// ============ 工位终端业务操作(P0-3 / 数量不符 / 退库 / 工序间检验记录) ============
// EmergencyCall 应急呼叫:落预警消息(type=emergency_call),预警中心实时展示 + pad 顶部提示。
// 纯软件方案(客户 L614 已定:发消息给班组长,不依赖安灯硬件)。
func (s *Service) EmergencyCall(ctx context.Context, stationNo int, orderNo, remark, operator string) error {
if stationNo <= 0 {
return errors.New("缺少工位号")
}
content := fmt.Sprintf("工位 %d 发起应急呼叫", stationNo)
if orderNo != "" {
content += "(工单 " + orderNo + ""
}
if remark != "" {
content += "" + remark
}
_, err := s.ctx.EntClient.Alert.Create().
SetRuleId(0).
SetType("emergency_call").
SetTitle("应急呼叫").
SetContent(content).
SetRefType("station").
SetRefId(fmt.Sprintf("%d", stationNo)).
SetReceiver("").
SetStatus("UNREAD").
Save(ctx)
if err != nil {
return err
}
s.ctx.EventLog.Write(ctx, "station.emergency", orderNo, operator, "alert",
fmt.Sprintf("%d", stationNo), "工位应急呼叫", map[string]any{"stationNo": stationNo, "remark": remark})
s.notifyDashboard()
return nil
}
// QtyReportReq 数量不符上报请求(工位收到料数量与下发数量不一致)
type QtyReportReq struct {
StationNo int `json:"stationNo"`
OrderNo string `json:"orderNo"`
Sn string `json:"sn"`
MaterialCode string `json:"materialCode"`
MaterialName string `json:"materialName"`
PlanQty int `json:"planQty"`
ActualQty int `json:"actualQty"`
Cause string `json:"cause"`
Operator string `json:"operator"`
}
// CreateQtyReport 数量不符上报:落 material_qty_report + 预警中心消息(type=qty_diff)。
// MES 侧仅预警,不承载明细处理流程:相关内容融合为一段文字描述写入预警内容,
// 由预警中心与右上角铃铛统一呈现;明细与闭环由 WMS 负责。
func (s *Service) CreateQtyReport(ctx context.Context, req QtyReportReq) error {
if req.StationNo <= 0 || req.MaterialCode == "" {
return errors.New("工位号与物料编码必填")
}
diff := req.PlanQty - req.ActualQty
_, err := s.ctx.EntClient.MaterialQtyReport.Create().
SetStationNo(req.StationNo).
SetOrderNo(req.OrderNo).
SetSn(req.Sn).
SetMaterialCode(req.MaterialCode).
SetMaterialName(req.MaterialName).
SetPlanQty(req.PlanQty).
SetActualQty(req.ActualQty).
SetDiffQty(diff).
SetCause(req.Cause).
SetStatus("PENDING").
SetOperator(req.Operator).
Save(ctx)
if err != nil {
return err
}
// 融合成单段文字描述,直接写入预警「内容」(即预警原因)。
cause := req.Cause
if cause == "" {
cause = "未填写"
}
content := fmt.Sprintf("工位%d 数量不符:物料 %s", req.StationNo, req.MaterialCode)
if req.MaterialName != "" {
content += "" + req.MaterialName + ""
}
content += fmt.Sprintf(";应发 %d,实收 %d,差异 %d", req.PlanQty, req.ActualQty, diff)
if req.OrderNo != "" {
content += ";工单 " + req.OrderNo
}
if req.Sn != "" {
content += ";序列号 " + req.Sn
}
content += ";上报原因:" + cause
_, _ = s.ctx.EntClient.Alert.Create().
SetRuleId(0).
SetType("qty_diff").
SetTitle("数量不符预警").
SetContent(content).
SetRefType("material_qty_report").
SetRefId(req.MaterialCode).
SetReceiver("").
SetStatus("UNREAD").
Save(ctx)
s.ctx.EventLog.Write(ctx, "station.qty_report", req.OrderNo, req.Operator, "material_qty_report",
req.MaterialCode, "数量不符上报", map[string]any{"stationNo": req.StationNo, "diff": diff})
s.notifyDashboard()
return nil
}
// StationReturnMaterial 工位退料回库房:落事件日志 + 调 WMS 预建待确认退库单。
// 退库单在 WMS 侧由库管确认收货后库存加回(链路封闭,问题记录 L491)。
func (s *Service) StationReturnMaterial(ctx context.Context, orderNo string, stationNo int, sn, materialCode, materialName, spec, reason, operator, unit string, qty int) error {
if stationNo <= 0 || materialCode == "" || qty <= 0 {
return errors.New("工位号、物料编码、退库数量必填")
}
s.ctx.EventLog.Write(ctx, "station.return_material", orderNo, operator, "return_order",
materialCode, "工位退料回库房", map[string]any{"stationNo": stationNo, "qty": qty, "reason": reason})
// 调 WMS 内部 API 预建退库单(失败降级:记录日志但不阻断工位操作)
if s.ctx.Wms != nil {
if err := s.ctx.Wms.CreateReturnOrder(ctx, orderNo, sn, materialCode, materialName, spec, reason, operator, stationNo, qty, unit); err != nil {
return fmt.Errorf("通知 WMS 建退库单失败:%w", err)
}
}
return nil
}
// ============ 一键生成工序间检验记录(P0-4 ============
// InterProcessInspection 工序间检验记录汇总项
type InterProcessInspection struct {
OrderNo string `json:"orderNo"`
StationNo int `json:"stationNo"`
StepId int `json:"stepId"`
StepName string `json:"stepName"`
Sn string `json:"sn"`
Result string `json:"result"`
Measured string `json:"measuredValue"`
Photo string `json:"photo"`
Operator string `json:"operator"`
CreatedAt time.Time `json:"createdAt"`
}
// GenerateInterProcessInspection 汇总某工单 PROCESS 类巡检记录(按步骤维度)生成工序间检验记录。
// 返回结构化列表,前端/H5 据此渲染打印(PDF/Excel)。
func (s *Service) GenerateInterProcessInspection(ctx context.Context, orderNo string) ([]*InterProcessInspection, error) {
if orderNo == "" {
return nil, errors.New("工单号必填")
}
if _, err := s.ctx.EntClient.WorkOrder.Query().Where(workorder.WorkOrderNo(orderNo)).First(ctx); err != nil {
return nil, errors.New("工单不存在:" + orderNo)
}
rows, err := s.ctx.EntClient.InspectionRecord.Query().
Where(
inspectionrecord.Category("PROCESS"),
inspectionrecord.OrderNo(orderNo),
).
Order(ent.Desc(inspectionrecord.FieldCreatedAt), ent.Desc(inspectionrecord.FieldID)).
All(ctx)
if err != nil {
return nil, err
}
out := make([]*InterProcessInspection, 0, len(rows))
for _, r := range rows {
out = append(out, &InterProcessInspection{
OrderNo: r.OrderNo,
StationNo: atoiSafe(r.StationNo),
StepId: r.StepId,
StepName: r.StepName,
Sn: r.Sn,
Result: r.Result,
Measured: r.MeasuredValue,
Photo: r.Photo,
Operator: r.Operator,
CreatedAt: r.CreatedAt,
})
}
return out, nil
}