Files
bj_power/bj_power_mes/internal/logic/stationprocess.go
T
SunYF 37f10d3a13 refactor: 重构产线工位与备料流程,移除冗余字段
1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段
2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据
3. 重构日排产为工位产量分配模式,替代原接驳台列表
4. 新增备料单工位级字段与分批出库支持
5. 移除定时同步可生产数量,改为WMS实时拉取接口
6. 过滤操作日志,排除登录/退出相关日志
7. 调整仪表盘工序展示逻辑为今日派工路线
8. 新增接驳台维护菜单与基础数据
2026-09-19 07:50:21 +08:00

185 lines
6.6 KiB
Go

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
}