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

186 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/materialqtyreport"
"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)。
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
}
content := fmt.Sprintf("工位 %d 数量不符:物料 %s 应发 %d / 实收 %d / 差异 %d。原因:%s",
req.StationNo, req.MaterialCode, req.PlanQty, req.ActualQty, diff, req.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
}
// ListQtyReports 数量不符上报列表
func (s *Service) ListQtyReports(ctx context.Context, status, orderNo, materialCode string) ([]*ent.MaterialQtyReport, error) {
q := s.ctx.EntClient.MaterialQtyReport.Query()
if status != "" {
q = q.Where(materialqtyreport.Status(status))
}
if orderNo != "" {
q = q.Where(materialqtyreport.OrderNoContainsFold(orderNo))
}
if materialCode != "" {
q = q.Where(materialqtyreport.MaterialCodeContainsFold(materialCode))
}
return q.Order(ent.Desc(materialqtyreport.FieldCreatedAt), ent.Desc(materialqtyreport.FieldID)).All(ctx)
}
// 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
}