Files
bj_power/bj_power_mes/internal/logic/route.go
T
SunYF d3eda6b588 feat&refactor: 2026-09-22 半成品业务模型重构与功能完善
- 重构半成品流转逻辑:进度权威源改为 workpiece.currentStationNo,新增 completedText 快照字段,废除 doneProcessCodes
- 清理物料品类中半成品类型,存量数据幂等清理,调整物料管理方式文案为批次/序列号
- 新增日排产状态专用更新接口,修复工单工艺校验逻辑
- 新增物料档案代理接口、深路径文件下载接口
- 优化前端界面文案与工位选择逻辑,补充追溯页半成品流转展示
- 调整WMS端物料品类校验与入库接口契约
- 新增/更新数据库表结构与实体类代码
2026-09-22 14:42:52 +08:00

147 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)、工位存在且 ENABLED 且当前绑定工艺与工单一致、工位号不重复;返回按工位号升序的条目。
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)).First(ctx)
if err != nil {
return nil, fmt.Errorf("工位 %d 不存在", it.StationNo)
}
// 工位当前绑定工艺必须与工单一致且工位启用(与排产校验 ValidateWOCombinationForSchedule 同口径同文案),
// 否则工单下发后工位被改绑/停用会导致路线漂移
if exist.FlowId != it.FlowId || exist.Status != "ENABLED" {
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)
}