2026-08-28 15:06:01 +08:00
|
|
|
|
package logic
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"bj_power_mes/ent"
|
|
|
|
|
|
"bj_power_mes/ent/associationtrace"
|
|
|
|
|
|
"bj_power_mes/ent/stepcriterion"
|
|
|
|
|
|
"bj_power_mes/ent/stepdata"
|
|
|
|
|
|
"bj_power_mes/ent/torquerecord"
|
|
|
|
|
|
"bj_power_mes/ent/workorder"
|
|
|
|
|
|
"bj_power_mes/ent/workpiece"
|
|
|
|
|
|
"bj_power_mes/ent/workpieceprocess"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func itoa(n int) string { return strconv.Itoa(n) }
|
|
|
|
|
|
func processName(code int) string {
|
|
|
|
|
|
names := map[int]string{1: "装配一", 2: "装配二", 3: "装配三", 4: "装配四", 5: "装配五", 6: "装配六", 7: "装配七", 8: "装配八", 9: "装配九", 10: "装配十", 11: "装配十一", 12: "装配十二"}
|
|
|
|
|
|
if n, ok := names[code]; ok {
|
|
|
|
|
|
return n
|
|
|
|
|
|
}
|
2026-08-31 12:58:28 +08:00
|
|
|
|
return "工位" + itoa(code)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// evalLogic 依据考核逻辑判定
|
|
|
|
|
|
func evalLogic(c *ent.StepCriterion, v float64) bool {
|
|
|
|
|
|
switch c.Logic {
|
|
|
|
|
|
case "GE":
|
|
|
|
|
|
return v >= c.Target
|
|
|
|
|
|
case "LE":
|
|
|
|
|
|
return v <= c.Target
|
|
|
|
|
|
case "GT":
|
|
|
|
|
|
return v > c.Target
|
|
|
|
|
|
case "LT":
|
|
|
|
|
|
return v < c.Target
|
|
|
|
|
|
case "RANGE":
|
|
|
|
|
|
l, r := true, true
|
|
|
|
|
|
if c.Min != nil {
|
|
|
|
|
|
l = v >= *c.Min
|
|
|
|
|
|
}
|
|
|
|
|
|
if c.Max != nil {
|
|
|
|
|
|
r = v <= *c.Max
|
|
|
|
|
|
}
|
|
|
|
|
|
return l && r
|
|
|
|
|
|
case "EQUAL":
|
|
|
|
|
|
return v == c.Target
|
|
|
|
|
|
default:
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type StepDataReq struct {
|
|
|
|
|
|
StepId int `json:"stepId"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Value float64 `json:"value"`
|
|
|
|
|
|
Text string `json:"text"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type OnlineReq struct {
|
|
|
|
|
|
Sn string `json:"sn"`
|
|
|
|
|
|
OrderNo string `json:"orderNo"`
|
|
|
|
|
|
WorkOrderId int `json:"workOrderId"`
|
|
|
|
|
|
ProcessSeq string `json:"processSeq"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OnlineWorkpiece 工件进线登记
|
|
|
|
|
|
func (s *Service) OnlineWorkpiece(ctx context.Context, req OnlineReq, operator string) error {
|
|
|
|
|
|
if req.Sn == "" {
|
|
|
|
|
|
return errors.New("sn 不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
seq := req.ProcessSeq
|
|
|
|
|
|
if seq == "" {
|
2026-08-31 12:58:28 +08:00
|
|
|
|
seq = FullProcessSeq
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
if req.WorkOrderId == 0 {
|
|
|
|
|
|
if wo, err := s.ctx.EntClient.WorkOrder.Query().Where(workorder.WorkOrderNo(req.OrderNo)).First(ctx); err == nil {
|
|
|
|
|
|
req.WorkOrderId = wo.ID
|
|
|
|
|
|
if req.ProcessSeq == "" && wo.ProcessSeq != "" {
|
|
|
|
|
|
seq = wo.ProcessSeq
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-31 12:58:28 +08:00
|
|
|
|
seq = NormalizeProcessSeq(seq)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
err := s.ctx.EntClient.Workpiece.Create().
|
|
|
|
|
|
SetSn(req.Sn).SetOrderNo(req.OrderNo).SetWorkOrderId(req.WorkOrderId).
|
|
|
|
|
|
SetProcessSeq(seq).SetStatus("ONLINE").SetOnlineAt(time.Now()).Exec(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
s.ctx.EventLog.Write(ctx, "workpiece.online", req.OrderNo, operator, "workpiece", req.Sn, "工件进线登记", nil)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ReportProcessReq struct {
|
|
|
|
|
|
Sn string `json:"sn"`
|
|
|
|
|
|
ProcessCode int `json:"processCode"`
|
|
|
|
|
|
StationNo int `json:"stationNo"`
|
|
|
|
|
|
Steps []StepDataReq `json:"steps"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 12:58:28 +08:00
|
|
|
|
// ReportProcess 工位报工:写报工实绩 + 步骤考核(step_data,扭矩在RANGE自动判定 OK/NG)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, operator string) error {
|
|
|
|
|
|
if req.Sn == "" || req.ProcessCode == 0 {
|
|
|
|
|
|
return errors.New("sn 与 processCode 必填")
|
|
|
|
|
|
}
|
|
|
|
|
|
wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(req.Sn)).First(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("工件未进线登记")
|
|
|
|
|
|
}
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
proc, err := s.ctx.EntClient.WorkpieceProcess.Create().
|
|
|
|
|
|
SetSn(req.Sn).SetProcessCode(req.ProcessCode).
|
|
|
|
|
|
SetStationNo(itoa(req.StationNo)).SetOrderNo(wp.OrderNo).
|
|
|
|
|
|
SetProcessName(processName(req.ProcessCode)).
|
|
|
|
|
|
SetStatus("DONE").SetOperator(operator).SetResult("OK").
|
|
|
|
|
|
SetStartedAt(now).SetEndedAt(now).Save(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
allOK := true
|
|
|
|
|
|
for _, st := range req.Steps {
|
|
|
|
|
|
if ok, e := s.writeStepData(ctx, req.Sn, proc.ID, st, operator); e == nil && !ok {
|
|
|
|
|
|
allOK = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if bad, _ := s.ctx.EntClient.StepData.Query().
|
|
|
|
|
|
Where(stepdata.ProcessId(proc.ID), stepdata.IsOK(false)).Exist(ctx); bad {
|
|
|
|
|
|
allOK = false
|
|
|
|
|
|
}
|
|
|
|
|
|
result := "OK"
|
|
|
|
|
|
if !allOK {
|
|
|
|
|
|
result = "NG"
|
|
|
|
|
|
}
|
|
|
|
|
|
_, _ = s.ctx.EntClient.WorkpieceProcess.UpdateOneID(proc.ID).SetResult(result).Save(ctx)
|
|
|
|
|
|
_, _ = s.ctx.EntClient.Workpiece.UpdateOneID(wp.ID).
|
|
|
|
|
|
SetCurrentProcess(req.ProcessCode).SetStatus("PROCESSING").Save(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
s.ctx.EventLog.Write(ctx, "workpiece.process.report", wp.OrderNo, operator, "workpiece", req.Sn,
|
2026-08-31 12:58:28 +08:00
|
|
|
|
"工位报工", map[string]any{"processCode": req.ProcessCode, "result": result})
|
2026-08-28 15:06:01 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// writeStepData 写一条步骤考核数据,按标准自动判定是否合格
|
|
|
|
|
|
func (s *Service) writeStepData(ctx context.Context, sn string, processId int, st StepDataReq, operator string) (bool, error) {
|
|
|
|
|
|
ok := true
|
|
|
|
|
|
crits, _ := s.ctx.EntClient.StepCriterion.Query().Where(stepcriterion.StepId(st.StepId)).All(ctx)
|
|
|
|
|
|
for _, c := range crits {
|
|
|
|
|
|
if !evalLogic(c, st.Value) {
|
|
|
|
|
|
ok = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var cname, clogic string
|
|
|
|
|
|
cid := 0
|
|
|
|
|
|
if len(crits) > 0 {
|
|
|
|
|
|
cname = crits[0].Name
|
|
|
|
|
|
clogic = crits[0].Logic
|
|
|
|
|
|
cid = crits[0].ID
|
|
|
|
|
|
}
|
|
|
|
|
|
_, err := s.ctx.EntClient.StepData.Create().
|
|
|
|
|
|
SetSn(sn).SetProcessId(processId).SetStepId(st.StepId).
|
|
|
|
|
|
SetStepName(st.Name).SetCriterionId(cid).SetCriterionName(cname).
|
|
|
|
|
|
SetCriterionLogic(clogic).SetValue(st.Value).SetValueText(st.Text).
|
|
|
|
|
|
SetIsOK(ok).SetOperator(operator).Save(ctx)
|
|
|
|
|
|
return ok, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DoneReq 完工 + 成品关联追溯
|
|
|
|
|
|
type DoneReq struct {
|
|
|
|
|
|
Sn string `json:"sn"`
|
|
|
|
|
|
BatchItems []string `json:"batchItems"` // 结构件批次号
|
|
|
|
|
|
SerialItems []string `json:"serialItems"` // 精密件SN
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DoneWorkpiece 完工:登记成品并生成关联追溯
|
|
|
|
|
|
func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator string) error {
|
|
|
|
|
|
wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(req.Sn)).First(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("工件不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
_, _ = s.ctx.EntClient.Workpiece.UpdateOneID(wp.ID).
|
|
|
|
|
|
SetStatus("DONE").SetDoneAt(now).Save(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
existID, _ := s.ctx.EntClient.AssociationTrace.Query().
|
|
|
|
|
|
Where(associationtrace.FinishedSn(req.Sn)).FirstID(ctx)
|
|
|
|
|
|
if existID > 0 {
|
|
|
|
|
|
_, _ = s.ctx.EntClient.AssociationTrace.UpdateOneID(existID).
|
|
|
|
|
|
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).SetOperator(operator).Save(ctx)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_, err = s.ctx.EntClient.AssociationTrace.Create().
|
|
|
|
|
|
SetFinishedSn(req.Sn).SetOrderNo(wp.OrderNo).SetProcessCode(wp.ProcessSeq).
|
|
|
|
|
|
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).
|
|
|
|
|
|
SetOperator(operator).Save(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
s.bumpWorkOrderProgress(ctx, wp.OrderNo, req.Sn)
|
2026-09-04 14:18:53 +08:00
|
|
|
|
s.bumpDailyPlanCompleted(ctx, wp.OrderNo, now)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
s.ctx.EventLog.Write(ctx, "workpiece.done", wp.OrderNo, operator, "workpiece", req.Sn, "完工+关联追溯", map[string]any{"batchItems": req.BatchItems, "serialItems": req.SerialItems})
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Trace 追溯查询(SN 维度):工序时间线/操作人/步骤数据/物料批次SN/拧紧数据
|
|
|
|
|
|
func (s *Service) Trace(ctx context.Context, sn string) (map[string]any, error) {
|
|
|
|
|
|
wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(sn)).First(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, errors.New("该SN无追溯数据")
|
|
|
|
|
|
}
|
|
|
|
|
|
procList, _ := s.ctx.EntClient.WorkpieceProcess.Query().
|
|
|
|
|
|
Where(workpieceprocess.Sn(sn)).Order(ent.Asc(workpieceprocess.FieldProcessCode)).All(ctx)
|
|
|
|
|
|
steps, _ := s.ctx.EntClient.StepData.Query().
|
|
|
|
|
|
Where(stepdata.Sn(sn)).Order(ent.Desc(stepdata.FieldID)).All(ctx)
|
|
|
|
|
|
assoc, _ := s.ctx.EntClient.AssociationTrace.Query().
|
|
|
|
|
|
Where(associationtrace.FinishedSn(sn)).First(ctx)
|
|
|
|
|
|
torque, _ := s.ctx.EntClient.TorqueRecord.Query().
|
|
|
|
|
|
Where(torquerecord.Sn(sn)).Order(ent.Desc(torquerecord.FieldTime)).Limit(200).All(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
return map[string]any{
|
|
|
|
|
|
"workpiece": wp,
|
|
|
|
|
|
"processTimeline": procList,
|
|
|
|
|
|
"stepData": steps,
|
|
|
|
|
|
"associationTrace": assoc,
|
|
|
|
|
|
"torqueRecords": torque,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) ListWorkpieces(ctx context.Context, sn, orderNo string) ([]*ent.Workpiece, error) {
|
|
|
|
|
|
q := s.ctx.EntClient.Workpiece.Query()
|
|
|
|
|
|
if sn != "" {
|
|
|
|
|
|
q = q.Where(workpiece.SnContains(sn))
|
|
|
|
|
|
}
|
|
|
|
|
|
if orderNo != "" {
|
|
|
|
|
|
q = q.Where(workpiece.OrderNo(orderNo))
|
|
|
|
|
|
}
|
|
|
|
|
|
return q.Order(ent.Desc(workpiece.FieldID)).Limit(500).All(ctx)
|
|
|
|
|
|
}
|