1. 全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2. 重构工单与工件工艺数据模型:
- 新增工艺组合表flow_material作为备料/齐套唯一源头
- 新增工位状态表station_state支持自主停单/恢复接单
- 移除station_process派工表,改用工单工艺组合作为路线唯一源头
- 替换processCode为flowId作为工艺关联标识
- 重构工件当前进度字段为currentStationNo
3. 删除冗余的静态备份资源文件
4. 调整物料选择组件默认启用仅显示激活物料
5. 优化工单创建/编辑逻辑,新增工艺组合校验与保存
145 lines
4.7 KiB
Go
145 lines
4.7 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"bj_power_mes/ent"
|
||
"bj_power_mes/ent/station"
|
||
)
|
||
|
||
// ---------- 工单工艺组合(work_order.flow_combination)路线派生 ----------
|
||
|
||
// TodayStr 当天日期 yyyy-MM-dd(工位停单只认当天)
|
||
func TodayStr() string { return time.Now().Format("2006-01-02") }
|
||
|
||
// FlowItem 工单工艺组合条目:工艺 + 工位(工位号即顺序号,按工位号升序执行)
|
||
type FlowItem struct {
|
||
FlowId int `json:"flowId"`
|
||
StationNo int `json:"stationNo"`
|
||
}
|
||
|
||
// RouteStationsFromWO 解析工单 flow_combination → 组合条目(按工位号升序;空组合返回空切片)
|
||
func RouteStationsFromWO(ctx context.Context, client *ent.Client, wo *ent.WorkOrder) ([]FlowItem, error) {
|
||
if wo == nil {
|
||
return nil, nil
|
||
}
|
||
items := flowMapsToItems(wo.FlowCombination)
|
||
sort.SliceStable(items, func(i, j int) bool { return items[i].StationNo < items[j].StationNo })
|
||
return items, nil
|
||
}
|
||
|
||
// flowMapsToItems jsonb 原始值 → 组合条目(过滤 flowId/stationNo 非正的脏条目)
|
||
func flowMapsToItems(raw []map[string]int) []FlowItem {
|
||
items := make([]FlowItem, 0, len(raw))
|
||
for _, m := range raw {
|
||
flowId, no := m["flowId"], m["stationNo"]
|
||
if flowId > 0 && no > 0 {
|
||
items = append(items, FlowItem{FlowId: flowId, StationNo: no})
|
||
}
|
||
}
|
||
return items
|
||
}
|
||
|
||
// flowItemsToMaps 组合条目 → jsonb 存储结构
|
||
func flowItemsToMaps(items []FlowItem) []map[string]int {
|
||
out := make([]map[string]int, 0, len(items))
|
||
for _, it := range items {
|
||
out = append(out, map[string]int{"flowId": it.FlowId, "stationNo": it.StationNo})
|
||
}
|
||
return out
|
||
}
|
||
|
||
// RouteStationsOfWO 组合条目 → 工位号列表(升序;未选工位即跳过)
|
||
func RouteStationsOfWO(items []FlowItem) []int {
|
||
out := make([]int, 0, len(items))
|
||
for _, it := range items {
|
||
out = append(out, it.StationNo)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// RouteStationsStrOfWO 组合条目 → 路线串 "2,5,6,8"
|
||
func RouteStationsStrOfWO(items []FlowItem) string {
|
||
parts := make([]string, 0, len(items))
|
||
for _, it := range items {
|
||
parts = append(parts, strconv.Itoa(it.StationNo))
|
||
}
|
||
return strings.Join(parts, ",")
|
||
}
|
||
|
||
// StationFlowMapOfWO 组合条目 → 工位号→工艺ID
|
||
func StationFlowMapOfWO(items []FlowItem) map[int]int {
|
||
m := make(map[int]int, len(items))
|
||
for _, it := range items {
|
||
m[it.StationNo] = it.FlowId
|
||
}
|
||
return m
|
||
}
|
||
|
||
// ValidateFlowCombination 保存工单时校验工艺组合:
|
||
// 非空、工艺存在且启用(ACTIVE)、工位存在、工位号不重复;返回按工位号升序的条目。
|
||
func (s *Service) ValidateFlowCombination(ctx context.Context, raw []map[string]int) ([]FlowItem, error) {
|
||
items := flowMapsToItems(raw)
|
||
if len(items) == 0 {
|
||
return nil, errors.New("请先选择工艺组合(至少一条 工艺+工位)")
|
||
}
|
||
sort.SliceStable(items, func(i, j int) bool { return items[i].StationNo < items[j].StationNo })
|
||
seen := map[int]bool{}
|
||
for _, it := range items {
|
||
if seen[it.StationNo] {
|
||
return nil, fmt.Errorf("工位 %d 在工艺组合中重复", it.StationNo)
|
||
}
|
||
seen[it.StationNo] = true
|
||
flow, err := s.ctx.EntClient.ProcessFlow.Get(ctx, it.FlowId)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("工艺不存在(ID=%d)", it.FlowId)
|
||
}
|
||
if flow.Status != "ACTIVE" {
|
||
return nil, fmt.Errorf("工艺「%s」已停用", flow.Name)
|
||
}
|
||
exist, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(it.StationNo)).Exist(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !exist {
|
||
return nil, fmt.Errorf("工位 %d 不存在", it.StationNo)
|
||
}
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
// ValidateWOCombinationForSchedule 排产前校验(工单组合是唯一路线源头,排产只继承不可改):
|
||
// 组合为空不可排产;组合里每个工位当前绑定工艺必须与工单一致且工位启用。
|
||
func (s *Service) ValidateWOCombinationForSchedule(ctx context.Context, wo *ent.WorkOrder) error {
|
||
items, _ := RouteStationsFromWO(ctx, s.ctx.EntClient, wo)
|
||
if len(items) == 0 {
|
||
return errors.New("请先在工单中选择工艺组合,才能排产")
|
||
}
|
||
for _, it := range items {
|
||
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(it.StationNo)).First(ctx)
|
||
if err != nil {
|
||
return fmt.Errorf("工位 %d 不存在", it.StationNo)
|
||
}
|
||
if st.FlowId != it.FlowId || st.Status != "ENABLED" {
|
||
return fmt.Errorf("工位%d当前工艺与工单不匹配", it.StationNo)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// flowNameById 工艺名称(查不到回退「工艺{id}」)
|
||
func (s *Service) flowNameById(ctx context.Context, id int) string {
|
||
if id > 0 {
|
||
if f, err := s.ctx.EntClient.ProcessFlow.Get(ctx, id); err == nil && f.Name != "" {
|
||
return f.Name
|
||
}
|
||
}
|
||
return "工艺" + strconv.Itoa(id)
|
||
}
|