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) }