refactor: 重构工艺工序相关命名与数据模型
1. 全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2. 重构工单与工件工艺数据模型:
- 新增工艺组合表flow_material作为备料/齐套唯一源头
- 新增工位状态表station_state支持自主停单/恢复接单
- 移除station_process派工表,改用工单工艺组合作为路线唯一源头
- 替换processCode为flowId作为工艺关联标识
- 重构工件当前进度字段为currentStationNo
3. 删除冗余的静态备份资源文件
4. 调整物料选择组件默认启用仅显示激活物料
5. 优化工单创建/编辑逻辑,新增工艺组合校验与保存
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user