package logic import ( "context" "errors" "sort" "strconv" "strings" "time" "bj_power_mes/ent" "bj_power_mes/ent/stationprocess" ) // TodayStr 当前日期 yyyy-MM-dd(派工/路由默认生效日) func TodayStr() string { return time.Now().Format("2006-01-02") } // StationProcessReq 工位↔工序 派工请求(按 effectDate+shift 可换;同工位同天同班次唯一) type StationProcessReq struct { StationNo int `json:"stationNo"` ProcessCode int `json:"processCode"` // 1~12 为工序编号;0=解除派工(该工位不参与任何工序) EffectDate string `json:"effectDate"` // yyyy-MM-dd Shift string `json:"shift"` // 班次(默认空串=单班) Operator string `json:"operator"` } // UpsertStationProcess 覆盖式保存单条工位派工。 // 先按「其余既有行 + 本次新行」做连续性/单调不降校验,不合法整体回退(避免半截写入)。 // processCode=0 表示解除该工位派工(仅删不插)。 func (s *Service) UpsertStationProcess(ctx context.Context, req StationProcessReq) error { if req.StationNo <= 0 { return errors.New("工位号必填") } if req.EffectDate == "" { return errors.New("生效日期必填") } // 拟生效集合:取同一 生效日+班次 的既有行,剔除本工位旧行,再并入本次新行 rows, err := s.ctx.EntClient.StationProcess.Query(). Where(stationprocess.EffectDate(req.EffectDate), stationprocess.Shift(req.Shift)). Order(ent.Asc(stationprocess.FieldStationNo)).All(ctx) if err != nil { return err } merged := make([]*ent.StationProcess, 0, len(rows)+1) for _, r := range rows { if r.StationNo == req.StationNo { continue } merged = append(merged, r) } if req.ProcessCode > 0 { merged = append(merged, &ent.StationProcess{ StationNo: req.StationNo, ProcessCode: req.ProcessCode, EffectDate: req.EffectDate, Shift: req.Shift, }) } // 连续性校验:已派工工位(processCode>0)沿工位号必须单调不降、不可穿插。 // 未派工(processCode=0)工位视为中性、不参与校验(可穿插在已派工工位之间)。 if err := ValidateStationProcessContinuity(merged); err != nil { return err } // 覆盖式写入:删旧 + 插新(唯一索引兜底防重复) if _, err := s.ctx.EntClient.StationProcess.Delete(). Where(stationprocess.EffectDate(req.EffectDate), stationprocess.Shift(req.Shift), stationprocess.StationNo(req.StationNo)). Exec(ctx); err != nil { return err } if req.ProcessCode > 0 { if _, err := s.ctx.EntClient.StationProcess.Create(). SetStationNo(req.StationNo).SetProcessCode(req.ProcessCode). SetEffectDate(req.EffectDate).SetShift(req.Shift).SetOperator(req.Operator).Save(ctx); err != nil { return err } s.ctx.EventLog.Write(ctx, "station.process.save", req.EffectDate, req.Operator, "station_process", req.EffectDate, "保存工位派工", map[string]any{"stationNo": req.StationNo, "processCode": req.ProcessCode, "shift": req.Shift}) } else { s.ctx.EventLog.Write(ctx, "station.process.clear", req.EffectDate, req.Operator, "station_process", req.EffectDate, "解除工位派工", map[string]any{"stationNo": req.StationNo, "shift": req.Shift}) } s.notifyDashboard() return nil } // ListStationProcess 查询某 生效日+班次 的全部派工(按工位号升序) func (s *Service) ListStationProcess(ctx context.Context, effectDate, shift string) ([]*ent.StationProcess, error) { if effectDate == "" { effectDate = TodayStr() } return s.ctx.EntClient.StationProcess.Query(). Where(stationprocess.EffectDate(effectDate), stationprocess.Shift(shift)). Order(ent.Asc(stationprocess.FieldStationNo)).All(ctx) } // ValidateStationProcessContinuity 校验工位派工连续性: // 已派工工位(processCode>0)按工位号升序必须单调不降(不可出现 1→2→1 的穿插/回退)。 // processCode=0(未派工)工位中性、不参与校验。 func ValidateStationProcessContinuity(rows []*ent.StationProcess) error { type pair struct { stationNo int processCode int } ps := make([]pair, 0, len(rows)) for _, r := range rows { if r.ProcessCode > 0 { ps = append(ps, pair{r.StationNo, r.ProcessCode}) } } sort.Slice(ps, func(i, j int) bool { return ps[i].stationNo < ps[j].stationNo }) for i := 1; i < len(ps); i++ { if ps[i].processCode < ps[i-1].processCode { return errors.New("工位派工不合法:工序编号必须沿工位号单调不降、不可穿插(如 3/5/7 号位同为工序1 合法,但 4 号位不得穿插其他工序)") } } return nil } // RouteStations 派生某 生效日+班次 的工位路线(按工位号升序的工位号列表)。 func RouteStations(ctx context.Context, client *ent.Client, effectDate, shift string) ([]int, error) { rows, err := client.StationProcess.Query(). Where(stationprocess.EffectDate(effectDate), stationprocess.Shift(shift)). Order(ent.Asc(stationprocess.FieldStationNo)).All(ctx) if err != nil { return nil, err } out := make([]int, 0, len(rows)) for _, r := range rows { if r.ProcessCode > 0 { out = append(out, r.StationNo) } } return out, nil } // RouteStationProcessCodeMap 派生 工位号 → 工序编号 映射(仅含已派工工位)。 func RouteStationProcessCodeMap(ctx context.Context, client *ent.Client, effectDate, shift string) (map[int]int, error) { rows, err := client.StationProcess.Query(). Where(stationprocess.EffectDate(effectDate), stationprocess.Shift(shift)).All(ctx) if err != nil { return nil, err } m := map[int]int{} for _, r := range rows { if r.ProcessCode > 0 { m[r.StationNo] = r.ProcessCode } } return m, nil } // RouteProcessCodes 派生某 生效日+班次 的工序编号集合(去重、升序)。 func RouteProcessCodes(ctx context.Context, client *ent.Client, effectDate, shift string) ([]int, error) { rows, err := client.StationProcess.Query(). Where(stationprocess.EffectDate(effectDate), stationprocess.Shift(shift)).All(ctx) if err != nil { return nil, err } seen := map[int]bool{} out := []int{} for _, r := range rows { if r.ProcessCode > 0 && !seen[r.ProcessCode] { seen[r.ProcessCode] = true out = append(out, r.ProcessCode) } } sort.Ints(out) return out, nil } // RouteStationsStr 派生工位路线字符串(逗号分隔,供追溯/看板展示)。 func RouteStationsStr(ctx context.Context, client *ent.Client, effectDate, shift string) (string, error) { rs, err := RouteStations(ctx, client, effectDate, shift) if err != nil { return "", err } parts := make([]string, len(rs)) for i, n := range rs { parts[i] = strconv.Itoa(n) } return strings.Join(parts, ","), nil }