refactor: 重构工艺工序相关命名与数据模型
1. 全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2. 重构工单与工件工艺数据模型:
- 新增工艺组合表flow_material作为备料/齐套唯一源头
- 新增工位状态表station_state支持自主停单/恢复接单
- 移除station_process派工表,改用工单工艺组合作为路线唯一源头
- 替换processCode为flowId作为工艺关联标识
- 重构工件当前进度字段为currentStationNo
3. 删除冗余的静态备份资源文件
4. 调整物料选择组件默认启用仅显示激活物料
5. 优化工单创建/编辑逻辑,新增工艺组合校验与保存
This commit is contained in:
@@ -3,6 +3,7 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -22,13 +23,6 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
return "工位" + itoa(code)
|
||||
}
|
||||
|
||||
// evalLogic 依据考核逻辑判定
|
||||
func evalLogic(c *ent.StepCriterion, v float64) bool {
|
||||
@@ -73,7 +67,7 @@ type OnlineReq struct {
|
||||
}
|
||||
|
||||
// OnlineWorkpiece 工件进线登记
|
||||
// 路线由「工位派工(station_process)」按日派生,工件不再冗余存储路线串
|
||||
// 执行路线 = 工单工艺组合(唯一路线源头),工件只需按组合逐工位报工推进
|
||||
func (s *Service) OnlineWorkpiece(ctx context.Context, req OnlineReq, operator string) error {
|
||||
if req.Sn == "" {
|
||||
return errors.New("sn 不能为空")
|
||||
@@ -95,11 +89,11 @@ func (s *Service) OnlineWorkpiece(ctx context.Context, req OnlineReq, operator s
|
||||
}
|
||||
|
||||
type ReportProcessReq struct {
|
||||
Sn string `json:"sn"`
|
||||
ProcessCode int `json:"processCode"`
|
||||
StationNo int `json:"stationNo"`
|
||||
Steps []StepDataReq `json:"steps"`
|
||||
Binds []BindItemReq `json:"binds"` // 装机绑定(本工序装配物料:结构件批次/电气件SN),随报工原子提交
|
||||
Sn string `json:"sn"`
|
||||
FlowId int `json:"flowId"` // 报工工艺(缺省回退工位当前绑定的工艺)
|
||||
StationNo int `json:"stationNo"`
|
||||
Steps []StepDataReq `json:"steps"`
|
||||
Binds []BindItemReq `json:"binds"` // 装机绑定(本工序装配物料:结构件批次/电气件SN),随报工原子提交
|
||||
// 作业时长采集(方案 M / P0-3):工位终端在「上料到本工位」时采 startedAt、点「报工」时采 endedAt,
|
||||
// 两端时间戳同源于终端时钟(ISO8601),离线重传也不失真;缺失则回退服务端当前时刻(时长=0)。
|
||||
StartedAt string `json:"startedAt"`
|
||||
@@ -122,25 +116,53 @@ func parseClientTime(s string) (time.Time, error) {
|
||||
}
|
||||
|
||||
// ReportProcess 工位报工:先应用装机绑定并做强校验(齐套才放行),再写报工实绩 + 步骤考核。
|
||||
// 绑料是"落到工艺流程"的强约束:产品 BOM 中标记为本工序(processCode)的物料必须绑齐,
|
||||
// 绑料是"落到工艺"的强约束:工艺物料清单(flow_material)中要求装配的物料必须绑齐,
|
||||
// 电气件按单台用量逐颗SN、结构件至少一个批次号;缺料直接拒绝报工并返回缺料清单。
|
||||
// 顺序校验(工单工艺组合为唯一路线源头):报工工位必须在组合中、工艺匹配,且不可倒序(可跳过未选工位)。
|
||||
func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, operator string) error {
|
||||
if req.Sn == "" || req.ProcessCode == 0 {
|
||||
return errors.New("sn 与 processCode 必填")
|
||||
if req.Sn == "" || req.StationNo <= 0 {
|
||||
return errors.New("sn 与 stationNo 必填")
|
||||
}
|
||||
wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(req.Sn)).First(ctx)
|
||||
if err != nil {
|
||||
return errors.New("工件未进线登记")
|
||||
}
|
||||
// 严格校验(业务口径已确认):仅“在制”工件可报工,且必须按工序顺序流转,禁止跳站/漏站/重复报同一站。
|
||||
// 在制 = ONLINE(已进线) / PROCESSING(在工位);DONE/REPAIR/SCRAPPED 一律拒绝。
|
||||
// 严格校验(业务口径已确认):仅“在制”工件可报工;DONE/REPAIR/SCRAPPED 一律拒绝。
|
||||
if wp.Status != "ONLINE" && wp.Status != "PROCESSING" {
|
||||
return errors.New("工件当前状态为「" + wp.Status + "」,非在制,不允许报工")
|
||||
}
|
||||
// 顺序流转:currentProcess 语义为“已报工完成的工序”(进线时=0),本工位必须是其下一道,即 processCode == currentProcess+1。
|
||||
if req.ProcessCode != wp.CurrentProcess+1 {
|
||||
return errors.New("工序顺序不符:工件已完成工序" + itoa(wp.CurrentProcess) +
|
||||
",应在工位" + itoa(wp.CurrentProcess+1) + "报工,当前请求工位" + itoa(req.ProcessCode))
|
||||
// 工艺解析:优先取报工传入 flowId;缺省回退工位当前绑定的工艺
|
||||
flowId := req.FlowId
|
||||
if flowId <= 0 {
|
||||
if st, e := s.ctx.EntClient.Station.Query().Where(station.StationNo(req.StationNo)).First(ctx); e == nil {
|
||||
flowId = st.FlowId
|
||||
}
|
||||
}
|
||||
if flowId <= 0 {
|
||||
return errors.New("无法确定报工工艺:请传 flowId 或确认工位已绑定工艺")
|
||||
}
|
||||
// 组合校验:报工条目必须在工单工艺组合中且工艺匹配;不可倒序(工位号即顺序号)
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId)
|
||||
if err != nil || wo == nil {
|
||||
return errors.New("工件所属工单不存在")
|
||||
}
|
||||
items, _ := RouteStationsFromWO(ctx, s.ctx.EntClient, wo)
|
||||
matched := false
|
||||
for _, it := range items {
|
||||
if it.StationNo == req.StationNo {
|
||||
matched = true
|
||||
if it.FlowId != flowId {
|
||||
return fmt.Errorf("工位%d当前工艺与工单工艺组合不匹配", req.StationNo)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return fmt.Errorf("工位%d不在该工单的工艺组合中,不可报工", req.StationNo)
|
||||
}
|
||||
if req.StationNo <= wp.CurrentStationNo {
|
||||
return errors.New("不可倒序报工:工件已报工到工位" + itoa(wp.CurrentStationNo) +
|
||||
",不能在工位" + itoa(req.StationNo) + "重复/回退报工")
|
||||
}
|
||||
// 报工是"装机绑定→校验齐套→写实绩→写步骤考核→回写结果/工件状态"的多步写,必须整体事务化(方案 U3):
|
||||
// 任一步失败全部回滚,杜绝"实绩已建但工件状态没推进/结果没落"的半成品脏数据;
|
||||
@@ -161,10 +183,10 @@ func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, opera
|
||||
if req.StationNo > 0 {
|
||||
stationNo = itoa(req.StationNo)
|
||||
}
|
||||
if _, err := s.applyBinds(ctx, client, req.Sn, wp.OrderNo, req.ProcessCode, stationNo, operator, req.Binds); err != nil {
|
||||
if _, err := s.applyBinds(ctx, client, req.Sn, wp.OrderNo, flowId, stationNo, operator, req.Binds); err != nil {
|
||||
return err
|
||||
}
|
||||
if miss, err := s.validateStationBinds(ctx, client, req.Sn, req.ProcessCode); err != nil {
|
||||
if miss, err := s.validateStationBinds(ctx, client, req.Sn, flowId); err != nil {
|
||||
return err
|
||||
} else if miss != "" {
|
||||
return errors.New(miss)
|
||||
@@ -190,9 +212,9 @@ func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, opera
|
||||
endedAt = startedAt
|
||||
}
|
||||
proc, err := client.WorkpieceProcess.Create().
|
||||
SetSn(req.Sn).SetProcessCode(req.ProcessCode).
|
||||
SetSn(req.Sn).SetFlowId(flowId).
|
||||
SetStationNo(itoa(req.StationNo)).SetOrderNo(wp.OrderNo).
|
||||
SetProcessName(processName(req.ProcessCode)).
|
||||
SetProcessName(s.flowNameById(ctx, flowId)).
|
||||
SetStatus("DONE").SetOperator(operator).SetResult("OK").
|
||||
SetStartedAt(startedAt).SetEndedAt(endedAt).SetDurationSec(durationSec).Save(ctx)
|
||||
if err != nil {
|
||||
@@ -223,7 +245,7 @@ func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, opera
|
||||
return err
|
||||
}
|
||||
if _, err := client.Workpiece.UpdateOneID(wp.ID).
|
||||
SetCurrentProcess(req.ProcessCode).SetStatus("PROCESSING").Save(ctx); err != nil {
|
||||
SetCurrentStationNo(req.StationNo).SetStatus("PROCESSING").Save(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
@@ -232,7 +254,7 @@ func (s *Service) ReportProcess(ctx context.Context, req ReportProcessReq, opera
|
||||
committed = true
|
||||
|
||||
s.ctx.EventLog.Write(ctx, "workpiece.process.report", wp.OrderNo, operator, "workpiece", req.Sn,
|
||||
"工位报工", map[string]any{"processCode": req.ProcessCode, "result": result})
|
||||
"工位报工", map[string]any{"flowId": flowId, "stationNo": req.StationNo, "result": result})
|
||||
s.notifyDashboard()
|
||||
return nil
|
||||
}
|
||||
@@ -316,8 +338,12 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin
|
||||
if err != nil {
|
||||
return errors.New("工件不存在")
|
||||
}
|
||||
// 兜底强校验:工艺路线中每个工序的装配物料都须绑齐(BOM 未配置工序时自动放行)
|
||||
if miss, err := s.validateAllProcessBinds(ctx, s.ctx.EntClient, req.Sn); err != nil {
|
||||
// 兜底强校验:工单工艺组合中每个工艺的装配物料都须绑齐(工艺未配置物料清单时自动放行)
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId)
|
||||
if err != nil || wo == nil {
|
||||
return errors.New("工件所属工单不存在")
|
||||
}
|
||||
if miss, err := s.validateAllFlows(ctx, s.ctx.EntClient, req.Sn, wo); err != nil {
|
||||
return err
|
||||
} else if miss != "" {
|
||||
return errors.New("完工前必须绑齐全部装配物料:" + miss)
|
||||
@@ -332,7 +358,10 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin
|
||||
_, _ = s.ctx.EntClient.AssociationTrace.UpdateOneID(existID).
|
||||
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).SetOperator(operator).Save(ctx)
|
||||
} else {
|
||||
routeStr, _ := RouteStationsStr(ctx, s.ctx.EntClient, TodayStr(), "")
|
||||
routeStr := ""
|
||||
if items, _ := RouteStationsFromWO(ctx, s.ctx.EntClient, wo); len(items) > 0 {
|
||||
routeStr = RouteStationsStrOfWO(items)
|
||||
}
|
||||
_, err = s.ctx.EntClient.AssociationTrace.Create().
|
||||
SetFinishedSn(req.Sn).SetOrderNo(wp.OrderNo).SetProcessCode(routeStr).
|
||||
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).
|
||||
@@ -420,7 +449,7 @@ func (s *Service) Trace(ctx context.Context, sn string) (map[string]any, error)
|
||||
return nil, errors.New("该SN无追溯数据")
|
||||
}
|
||||
procList, _ := s.ctx.EntClient.WorkpieceProcess.Query().
|
||||
Where(workpieceprocess.Sn(sn)).Order(ent.Asc(workpieceprocess.FieldProcessCode)).All(ctx)
|
||||
Where(workpieceprocess.Sn(sn)).Order(ent.Asc(workpieceprocess.FieldFlowId), ent.Asc(workpieceprocess.FieldID)).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().
|
||||
@@ -484,9 +513,25 @@ func (s *Service) WorkpiecesInProcess(ctx context.Context, stationNo int, orderN
|
||||
return nil, err
|
||||
}
|
||||
out := make([]InProcessItem, 0, len(list))
|
||||
woCache := map[int]*ent.WorkOrder{}
|
||||
getWO := func(wp *ent.Workpiece) *ent.WorkOrder {
|
||||
if wp.WorkOrderId <= 0 {
|
||||
return nil
|
||||
}
|
||||
if wo, ok := woCache[wp.WorkOrderId]; ok {
|
||||
return wo
|
||||
}
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId)
|
||||
if err != nil {
|
||||
wo = nil
|
||||
}
|
||||
woCache[wp.WorkOrderId] = wo
|
||||
return wo
|
||||
}
|
||||
for _, wp := range list {
|
||||
// 路线由今日工位派工(station_process)派生;未派工则无路线(maxStation=0)
|
||||
seq, _ := RouteStations(ctx, s.ctx.EntClient, TodayStr(), "")
|
||||
// 路线 = 工单工艺组合(唯一路线源头);未配置组合则无路线(maxStation=0)
|
||||
items, _ := RouteStationsFromWO(ctx, s.ctx.EntClient, getWO(wp))
|
||||
seq := RouteStationsOfWO(items)
|
||||
maxStation := 0
|
||||
for _, c := range seq {
|
||||
if c > maxStation {
|
||||
@@ -496,7 +541,7 @@ func (s *Service) WorkpiecesInProcess(ctx context.Context, stationNo int, orderN
|
||||
// 推算当前物理工位:ONLINE 在 上线位(0);PROCESSING 且已到末端 → 下线位(13)
|
||||
cur := 0
|
||||
if wp.Status == "PROCESSING" || wp.Status == "REPAIR" {
|
||||
cp := wp.CurrentProcess
|
||||
cp := wp.CurrentStationNo
|
||||
if cp <= 0 {
|
||||
cp = maxStation
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user