1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段 2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据 3. 重构日排产为工位产量分配模式,替代原接驳台列表 4. 新增备料单工位级字段与分批出库支持 5. 移除定时同步可生产数量,改为WMS实时拉取接口 6. 过滤操作日志,排除登录/退出相关日志 7. 调整仪表盘工序展示逻辑为今日派工路线 8. 新增接驳台维护菜单与基础数据
185 lines
6.6 KiB
Go
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
|
|
}
|