2026-09-15 16:37:39 +08:00
|
|
|
|
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"`
|
2026-09-18 18:54:54 +08:00
|
|
|
|
MaterialCode string `json:"materialCode"`
|
|
|
|
|
|
MaterialName string `json:"materialName"`
|
2026-09-15 16:37:39 +08:00
|
|
|
|
PlanQty int `json:"planQty"`
|
|
|
|
|
|
ActualQty int `json:"actualQty"`
|
|
|
|
|
|
Cause string `json:"cause"`
|
|
|
|
|
|
Operator string `json:"operator"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateQtyReport 数量不符上报:落 material_qty_report + 预警中心消息(type=qty_diff)。
|
2026-09-19 17:04:58 +08:00
|
|
|
|
//
|
|
|
|
|
|
// 归属(2026-09-19 定稿):MES 是唯一真源与处理侧 —— 上报、明细、处理(补发/退库/调整)
|
|
|
|
|
|
// 全在 MES(见 logic/qty_report.go:HandleQtyReport),WMS 只经内部接口被动建单。
|
|
|
|
|
|
// 预警 refId 存差异单业务号 QR{id},处理完成后可精确定位并闭环该预警。
|
2026-09-15 16:37:39 +08:00
|
|
|
|
func (s *Service) CreateQtyReport(ctx context.Context, req QtyReportReq) error {
|
|
|
|
|
|
if req.StationNo <= 0 || req.MaterialCode == "" {
|
|
|
|
|
|
return errors.New("工位号与物料编码必填")
|
|
|
|
|
|
}
|
|
|
|
|
|
diff := req.PlanQty - req.ActualQty
|
2026-09-19 17:04:58 +08:00
|
|
|
|
rec, err := s.ctx.EntClient.MaterialQtyReport.Create().
|
2026-09-15 16:37:39 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-09-18 18:54:54 +08:00
|
|
|
|
// 融合成单段文字描述,直接写入预警「内容」(即预警原因)。
|
|
|
|
|
|
cause := req.Cause
|
|
|
|
|
|
if cause == "" {
|
|
|
|
|
|
cause = "未填写"
|
|
|
|
|
|
}
|
2026-09-19 17:04:58 +08:00
|
|
|
|
content := fmt.Sprintf("差异单 %s|工位%d 数量不符:物料 %s", qtyReportNo(rec.ID), req.StationNo, req.MaterialCode)
|
2026-09-18 18:54:54 +08:00
|
|
|
|
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
|
2026-09-15 16:37:39 +08:00
|
|
|
|
_, _ = s.ctx.EntClient.Alert.Create().
|
|
|
|
|
|
SetRuleId(0).
|
|
|
|
|
|
SetType("qty_diff").
|
2026-09-18 18:54:54 +08:00
|
|
|
|
SetTitle("数量不符预警").
|
2026-09-15 16:37:39 +08:00
|
|
|
|
SetContent(content).
|
|
|
|
|
|
SetRefType("material_qty_report").
|
2026-09-19 17:04:58 +08:00
|
|
|
|
SetRefId(qtyReportNo(rec.ID)).
|
2026-09-15 16:37:39 +08:00
|
|
|
|
SetReceiver("").
|
|
|
|
|
|
SetStatus("UNREAD").
|
|
|
|
|
|
Save(ctx)
|
|
|
|
|
|
s.ctx.EventLog.Write(ctx, "station.qty_report", req.OrderNo, req.Operator, "material_qty_report",
|
2026-09-19 17:04:58 +08:00
|
|
|
|
req.MaterialCode, "数量不符上报", map[string]any{"stationNo": req.StationNo, "diff": diff, "reportNo": qtyReportNo(rec.ID)})
|
2026-09-15 16:37:39 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|