refactor: 重构工艺工序相关命名与数据模型

1.  全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2.  重构工单与工件工艺数据模型:
    - 新增工艺组合表flow_material作为备料/齐套唯一源头
    - 新增工位状态表station_state支持自主停单/恢复接单
    - 移除station_process派工表,改用工单工艺组合作为路线唯一源头
    - 替换processCode为flowId作为工艺关联标识
    - 重构工件当前进度字段为currentStationNo
3.  删除冗余的静态备份资源文件
4.  调整物料选择组件默认启用仅显示激活物料
5.  优化工单创建/编辑逻辑,新增工艺组合校验与保存
This commit is contained in:
SunYF
2026-09-22 11:32:29 +08:00
parent 932700ca65
commit d609ff8a9e
185 changed files with 8623 additions and 4501 deletions
+90 -29
View File
@@ -5,9 +5,11 @@ import (
"errors"
"fmt"
"sort"
"strings"
"time"
"bj_power_mes/ent"
"bj_power_mes/ent/flowmaterial"
"bj_power_mes/ent/processflow"
"bj_power_mes/ent/processstep"
"bj_power_mes/ent/station"
@@ -245,7 +247,6 @@ type StationReq struct {
Name string `json:"name"`
StationType *string `json:"stationType"`
FlowId *int `json:"flowId"`
DockCode *string `json:"dockCode"` // 绑定的接驳台编码(与 WMS dock.dock_code 对应,1:1
}
type StationVO struct {
@@ -290,7 +291,7 @@ func (s *Service) ListStations(ctx context.Context) ([]*StationVO, error) {
// 内置工位(种子/初始化数据,is_builtin=true)需连 PLC 并必须按工位号顺序流转,
// 不允许手动更改工位类型;页面新增的工位一律为非内置,可改类型、可删除。
// 「是否有接驳台」对应实体位置,由内置数据给定,不接收页面入参。
// 接驳台工位 1:1绑定前校验该接驳台未被其他工位占用(数据库另有部分唯一索引兜底)
// 接驳台工位 1:1 绑定为系统预置只读主数据,由种子初始化,页面不可更改、也不接收页面入参
func (s *Service) SaveStation(ctx context.Context, req StationReq, operator string) error {
if req.StationNo < 0 {
return errors.New("工位号非法(需 ≥ 0")
@@ -311,18 +312,11 @@ func (s *Service) SaveStation(ctx context.Context, req StationReq, operator stri
return errors.New("工位类型仅支持 LINE/OFFLINE")
}
}
// 页面新增的是非内置工位:无实体接驳台,不可绑定接驳台
if req.DockCode != nil && *req.DockCode != "" {
return errors.New("自定义工位(非内置)无实体接驳台,不可绑定接驳台")
}
cr := s.ctx.EntClient.Station.Create().
SetStationNo(req.StationNo).SetName(req.Name).SetStationType(stype).SetStatus("ENABLED").
// 页面「新增工位」创建的是非内置工位(可改类型、可删除);内置工位只能由种子/初始化数据产生。
// 是否有接驳台对应实体位置,由内置数据给定,页面新增时一律为 false 且不可手改。
SetIsBuiltin(false).SetHasDock(false)
if req.DockCode != nil {
cr.SetDockCode(*req.DockCode)
}
if req.FlowId != nil && *req.FlowId > 0 {
flow, fErr := s.ctx.EntClient.ProcessFlow.Get(ctx, *req.FlowId)
if fErr != nil {
@@ -371,22 +365,6 @@ func (s *Service) SaveStation(ctx context.Context, req StationReq, operator stri
upd.SetFlowId(0)
}
}
if req.DockCode != nil {
dc := *req.DockCode
if dc != "" {
// 只有内置工位(实体产线位置)才有接驳台
if !st.IsBuiltin {
return errors.New("自定义工位(非内置)无实体接驳台,不可绑定接驳台")
}
// 接驳台与工位 1:1:已被其他工位占用则拒绝
other, oErr := s.ctx.EntClient.Station.Query().
Where(station.DockCode(dc), station.StationNoNEQ(req.StationNo)).First(ctx)
if oErr == nil && other != nil {
return fmt.Errorf("接驳台 %s 已绑定工位%d,一个接驳台只能绑定一个工位", dc, other.StationNo)
}
}
upd.SetDockCode(dc)
}
if _, err := upd.Save(ctx); err != nil {
return err
}
@@ -488,7 +466,7 @@ type WorkloadRow struct {
Operator string `json:"operator"`
StationNo string `json:"stationNo"`
Date string `json:"date"`
ProcessCode int `json:"processCode"`
FlowId int `json:"flowId"`
ProcessName string `json:"processName"`
DoneCount int `json:"doneCount"`
OkCount int `json:"okCount"`
@@ -543,10 +521,10 @@ func (s *Service) Workload(ctx context.Context, operator, stationNo, orderNo, fr
}
agg := map[key]*WorkloadRow{}
for _, r := range rows {
k := key{r.Operator, r.StationNo, r.CreatedAt.Format("2006-01-02"), r.ProcessCode}
k := key{r.Operator, r.StationNo, r.CreatedAt.Format("2006-01-02"), r.FlowId}
row, ok := agg[k]
if !ok {
row = &WorkloadRow{Operator: r.Operator, StationNo: r.StationNo, Date: k.date, ProcessCode: r.ProcessCode, ProcessName: r.ProcessName}
row = &WorkloadRow{Operator: r.Operator, StationNo: r.StationNo, Date: k.date, FlowId: r.FlowId, ProcessName: r.ProcessName}
agg[k] = row
}
row.DoneCount++
@@ -571,7 +549,7 @@ func (s *Service) Workload(ctx context.Context, operator, stationNo, orderNo, fr
if list[i].Date != list[j].Date {
return list[i].Date < list[j].Date
}
return list[i].ProcessCode < list[j].ProcessCode
return list[i].FlowId < list[j].FlowId
})
// 三个视图分别聚合(2026-09-08 绩效报表改造:按人/按工位/明细,各自真分页)
@@ -637,3 +615,86 @@ func (s *Service) Workload(ctx context.Context, operator, stationNo, orderNo, fr
"detail": map[string]any{"total": detTotal, "list": detList},
}, nil
}
// ---------- 工艺物料清单(flow_material ----------
// FlowMaterialReq 工艺物料清单条目(保存为整表覆盖)
type FlowMaterialReq struct {
MaterialCode string `json:"materialCode"`
MaterialName string `json:"materialName"`
Spec string `json:"spec"`
Unit string `json:"unit"`
ManageMode string `json:"manageMode"` // 1 结构件(批次) / 2 电气件(SN)
UnitQty float64 `json:"unitQty"` // 单台用量
LossRate float64 `json:"lossRate"` // 损耗率(%)
}
// ListFlowMaterials 查某工艺的物料清单(按 ID 升序)
func (s *Service) ListFlowMaterials(ctx context.Context, flowId int) ([]*ent.FlowMaterial, error) {
return s.ctx.EntClient.FlowMaterial.Query().
Where(flowmaterial.FlowId(flowId)).
Order(ent.Asc(flowmaterial.FieldID)).All(ctx)
}
// SaveFlowMaterials 保存工艺物料清单(整表覆盖:先删后插)。
// 物料编码须在 WMS 物料档案存在;WMS 不可达/异常时降级放行并记日志,避免阻塞工艺配置。
func (s *Service) SaveFlowMaterials(ctx context.Context, flowId int, items []FlowMaterialReq, operator string) error {
if flowId <= 0 {
return errors.New("flowId 必填")
}
if _, err := s.ctx.EntClient.ProcessFlow.Get(ctx, flowId); err != nil {
return errors.New("工艺不存在")
}
// WMS 物料存在性校验(缺失拒绝;不可达降级放行)
codes := make([]string, 0, len(items))
seen := map[string]bool{}
for _, it := range items {
if it.MaterialCode == "" {
return errors.New("物料编码不能为空")
}
if seen[it.MaterialCode] {
return errors.New("物料 " + it.MaterialCode + " 重复")
}
seen[it.MaterialCode] = true
codes = append(codes, it.MaterialCode)
}
if len(codes) > 0 && s.ctx.Wms != nil {
if missing, err := s.ctx.Wms.MaterialExists(ctx, codes); err != nil {
s.ctx.EventLog.Write(ctx, "flow.material.wms_skip", "", operator, "process_flow", itoa(flowId),
"WMS 物料校验不可达,已降级放行", map[string]any{"error": err.Error()})
} else if len(missing) > 0 {
return errors.New("以下物料在 WMS 物料档案中不存在:" + strings.Join(missing, "、"))
}
}
tx, err := s.ctx.EntClient.Tx(ctx)
if err != nil {
return err
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback()
}
}()
client := tx.Client()
if _, err := client.FlowMaterial.Delete().Where(flowmaterial.FlowId(flowId)).Exec(ctx); err != nil {
return err
}
for _, it := range items {
if err := client.FlowMaterial.Create().
SetFlowId(flowId).
SetMaterialCode(it.MaterialCode).SetMaterialName(it.MaterialName).
SetSpec(it.Spec).SetUnit(it.Unit).SetManageMode(it.ManageMode).
SetUnitQty(it.UnitQty).SetLossRate(it.LossRate).
Exec(ctx); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
committed = true
s.ctx.EventLog.Write(ctx, "flow.material.save", "", operator, "process_flow", itoa(flowId),
"保存工艺物料清单(整表覆盖)", map[string]any{"flowId": flowId, "count": len(items)})
return nil
}