feat: 完成产线与仓储系统多模块迭代升级
本次迭代覆盖MES与WMS核心业务: 1. 新增接驳台托盘传感器读取与AGV对接能力 2. 完善工单排产、备料流程与权限体系拆分 3. 优化看板接口与前端路由、样式 4. 新增操作日志、库存盘点与角色保护逻辑 5. 修复代理地址、BOM保存等已知问题
This commit is contained in:
@@ -18,10 +18,14 @@ import (
|
||||
|
||||
// ---------- 工艺流程(块3) ----------
|
||||
|
||||
// FlowReq 工艺流程保存请求
|
||||
// StationNo:主工序编号 1..12(流程承载的工序号,沿用既有语义);
|
||||
// Stations:绑定工位列表(可多选,一个流程可绑多个工位;不传时默认只绑 StationNo)。
|
||||
type FlowReq struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StationNo int `json:"stationNo"`
|
||||
Stations []int `json:"stations"`
|
||||
PdfFile string `json:"pdfFile"`
|
||||
Status string `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
@@ -41,60 +45,96 @@ type FlowVO struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ListFlows 查询工艺流程(可按工位号过滤)
|
||||
// ListFlows 查询工艺流程(可按工位号过滤:命中绑定该工位或主工序号的流程)
|
||||
func (s *Service) ListFlows(ctx context.Context, stationNo int) ([]*FlowVO, error) {
|
||||
q := s.ctx.EntClient.ProcessFlow.Query().
|
||||
Order(ent.Asc(processflow.FieldProcessCode), ent.Asc(processflow.FieldID))
|
||||
if stationNo > 0 {
|
||||
q = q.Where(processflow.ProcessCode(stationNo))
|
||||
}
|
||||
flows, err := q.All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stations, _ := s.ctx.EntClient.Station.Query().All(ctx)
|
||||
flowToStations := map[int][]int{}
|
||||
for _, st := range stations {
|
||||
if st.FlowId > 0 {
|
||||
flowToStations[st.FlowId] = append(flowToStations[st.FlowId], st.StationNo)
|
||||
}
|
||||
}
|
||||
out := make([]*FlowVO, 0, len(flows))
|
||||
for _, f := range flows {
|
||||
// 过滤:指定工位号时,只返回"绑定列表或主工序号"命中该工位的流程
|
||||
if stationNo > 0 {
|
||||
hit := false
|
||||
for _, st := range flowToStations[f.ID] {
|
||||
if st == stationNo {
|
||||
hit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hit && f.ProcessCode != stationNo {
|
||||
continue
|
||||
}
|
||||
}
|
||||
vo := &FlowVO{
|
||||
Id: f.ID, Name: f.Name, StationNo: f.ProcessCode,
|
||||
PdfFile: f.PdfFile, Status: f.Status, Remark: f.Remark,
|
||||
Stations: []int{}, Steps: s.flowSteps(ctx, f.ID, f.ProcessCode), CreatedAt: f.CreatedAt,
|
||||
}
|
||||
for _, st := range stations {
|
||||
if st.FlowId == f.ID {
|
||||
vo.Stations = append(vo.Stations, st.StationNo)
|
||||
}
|
||||
if list, ok := flowToStations[f.ID]; ok {
|
||||
vo.Stations = list
|
||||
}
|
||||
out = append(out, vo)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SaveFlow 保存工艺流程(可一次绑定多个工位)。
|
||||
// 约束:① 工位号/绑定工位 1..12;② 一个工位同时只能绑定一个"启用"流程(保存时校验冲突);
|
||||
// ③ 停用流程不再自动解绑工位(绑定关系由本保存接口显式维护,启停只切流程状态)。
|
||||
func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) error {
|
||||
if req.Name == "" {
|
||||
return errors.New("流程名称必填")
|
||||
}
|
||||
if req.StationNo < 1 || req.StationNo > 12 {
|
||||
return errors.New("请选择工位号(1~12,固定12个工位)")
|
||||
return errors.New("请选择主工序编号(1~12)")
|
||||
}
|
||||
// 解析绑定工位列表:显式 stations 优先,否则回退到 StationNo(兼容旧调用)
|
||||
bindStations := req.Stations
|
||||
if len(bindStations) == 0 {
|
||||
bindStations = []int{req.StationNo}
|
||||
}
|
||||
seen := map[int]bool{}
|
||||
stations := []int{}
|
||||
for _, no := range bindStations {
|
||||
if no < 1 || no > 12 || seen[no] {
|
||||
continue
|
||||
}
|
||||
seen[no] = true
|
||||
stations = append(stations, no)
|
||||
}
|
||||
if len(stations) == 0 {
|
||||
return errors.New("请至少选择一个绑定工位")
|
||||
}
|
||||
status := req.Status
|
||||
if status == "" {
|
||||
status = "ACTIVE"
|
||||
}
|
||||
oldStationNo := 0
|
||||
if req.Id > 0 {
|
||||
if old, err := s.ctx.EntClient.ProcessFlow.Get(ctx, req.Id); err == nil {
|
||||
oldStationNo = old.ProcessCode
|
||||
}
|
||||
if status != "ACTIVE" && status != "INACTIVE" {
|
||||
return errors.New("非法状态")
|
||||
}
|
||||
if status == "ACTIVE" {
|
||||
q := s.ctx.EntClient.ProcessFlow.Query().
|
||||
Where(processflow.ProcessCode(req.StationNo), processflow.Status("ACTIVE"))
|
||||
if req.Id > 0 {
|
||||
q = q.Where(processflow.IDNEQ(req.Id))
|
||||
}
|
||||
if exist, err := q.Exist(ctx); err == nil && exist {
|
||||
return fmt.Errorf("工位 %d 已存在启用的工艺流程,请先停用旧流程", req.StationNo)
|
||||
// 校验:绑定工位不得已被其他"启用"流程占用(一个工位只能绑一个启用流程)
|
||||
bound, err := s.ctx.EntClient.Station.Query().
|
||||
Where(station.StationNoIn(stations...), station.FlowIdGT(0)).All(ctx)
|
||||
if err == nil {
|
||||
flowID := req.Id
|
||||
for _, st := range bound {
|
||||
if flowID > 0 && st.FlowId == flowID {
|
||||
continue // 绑定的是本流程自身,允许
|
||||
}
|
||||
other, gErr := s.ctx.EntClient.ProcessFlow.Get(ctx, st.FlowId)
|
||||
if gErr == nil && other.Status == "ACTIVE" {
|
||||
return fmt.Errorf("工位 %d 已被启用流程「%s」绑定,请先在流程/工位中解除绑定", st.StationNo, other.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
var flowID int
|
||||
@@ -117,19 +157,15 @@ func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) er
|
||||
}
|
||||
flowID = f.ID
|
||||
}
|
||||
// 工位号变更:解绑旧工位
|
||||
if oldStationNo > 0 && oldStationNo != req.StationNo {
|
||||
_, _ = s.ctx.EntClient.Station.Update().
|
||||
Where(station.StationNo(oldStationNo), station.FlowId(flowID)).
|
||||
SetFlowId(0).Save(ctx)
|
||||
// 绑定同步:把绑定列表整体指向该流程(旧绑定该流程但不在列表内的工位解绑)
|
||||
boundNow, _ := s.ctx.EntClient.Station.Query().Where(station.FlowId(flowID)).All(ctx)
|
||||
for _, st := range boundNow {
|
||||
if !seen[st.StationNo] {
|
||||
_, _ = s.ctx.EntClient.Station.UpdateOneID(st.ID).SetFlowId(0).Save(ctx)
|
||||
}
|
||||
}
|
||||
// 绑定同步:工位N 绑定该启用流程
|
||||
if status == "ACTIVE" {
|
||||
s.syncStationFlow(ctx, req.StationNo, flowID)
|
||||
} else {
|
||||
_, _ = s.ctx.EntClient.Station.Update().
|
||||
Where(station.StationNo(req.StationNo), station.FlowId(flowID)).
|
||||
SetFlowId(0).Save(ctx)
|
||||
for _, no := range stations {
|
||||
s.syncStationFlow(ctx, no, flowID)
|
||||
}
|
||||
// 覆盖式重建该流程的工序步骤及考核标准
|
||||
_, _ = s.ctx.EntClient.ProcessStep.Delete().Where(processstep.FlowId(flowID)).Exec(ctx)
|
||||
@@ -162,7 +198,7 @@ func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) er
|
||||
}
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "process.flow.save", "", operator, "process_flow", "", "维护工艺流程",
|
||||
map[string]any{"flowId": flowID, "stationNo": req.StationNo, "steps": len(req.Steps)})
|
||||
map[string]any{"flowId": flowID, "processCode": req.StationNo, "stations": stations, "steps": len(req.Steps)})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -179,11 +215,10 @@ func (s *Service) syncStationFlow(ctx context.Context, stationNo, flowID int) {
|
||||
}
|
||||
|
||||
func (s *Service) DeleteFlow(ctx context.Context, id int, operator string) error {
|
||||
// 有工位绑定的流程不允许删除
|
||||
cnt, err := s.ctx.EntClient.Station.Query().Where(station.FlowId(id)).Count(ctx)
|
||||
if err == nil && cnt > 0 {
|
||||
return errors.New("该流程已被工位绑定,请先解绑工位")
|
||||
}
|
||||
// 删除前先解除所有工位绑定,再清步骤与流程本身
|
||||
_, _ = s.ctx.EntClient.Station.Update().
|
||||
Where(station.FlowId(id)).
|
||||
SetFlowId(0).Save(ctx)
|
||||
_, _ = s.ctx.EntClient.ProcessStep.Delete().Where(processstep.FlowId(id)).Exec(ctx)
|
||||
s.ctx.EventLog.Write(ctx, "process.flow.delete", "", operator, "process_flow", "", "删除工艺流程", map[string]any{"flowId": id})
|
||||
return s.ctx.EntClient.ProcessFlow.DeleteOneID(id).Exec(ctx)
|
||||
@@ -287,7 +322,8 @@ func (s *Service) SaveStation(ctx context.Context, req StationReq, operator stri
|
||||
}
|
||||
|
||||
// SetFlowStatus 工艺流程启用/停用。
|
||||
// 停用:自动解绑对应工位;启用:校验该工位无其他启用流程后自动绑定。
|
||||
// 新语义:绑定关系由「保存流程」显式维护(一个流程可绑多工位),启停只切换流程状态,
|
||||
// 不再自动解绑/绑定工位;停用流程对已绑定工位不可用(工位取步骤时按 ACTIVE 过滤)。
|
||||
func (s *Service) SetFlowStatus(ctx context.Context, id int, status, operator string) error {
|
||||
if id <= 0 {
|
||||
return errors.New("缺少 id")
|
||||
@@ -299,19 +335,10 @@ func (s *Service) SetFlowStatus(ctx context.Context, id int, status, operator st
|
||||
if err != nil {
|
||||
return errors.New("工艺流程不存在")
|
||||
}
|
||||
if status == "INACTIVE" {
|
||||
// 自动解绑该流程对应的工位
|
||||
_, _ = s.ctx.EntClient.Station.Update().
|
||||
Where(station.StationNo(flow.ProcessCode), station.FlowId(flow.ID)).
|
||||
SetFlowId(0).Save(ctx)
|
||||
} else {
|
||||
q := s.ctx.EntClient.ProcessFlow.Query().
|
||||
Where(processflow.ProcessCode(flow.ProcessCode), processflow.Status("ACTIVE"), processflow.IDNEQ(flow.ID))
|
||||
if exist, err := q.Exist(ctx); err == nil && exist {
|
||||
return fmt.Errorf("工位 %d 已存在其他启用的工艺流程,请先停用旧流程", flow.ProcessCode)
|
||||
}
|
||||
s.syncStationFlow(ctx, flow.ProcessCode, flow.ID)
|
||||
if flow.Status == status {
|
||||
return nil
|
||||
}
|
||||
// 启用无需额外动作:绑定由保存流程时校验(一个工位不会被两个启用流程占用)
|
||||
if err := s.ctx.EntClient.ProcessFlow.UpdateOneID(id).SetStatus(status).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -319,7 +346,7 @@ func (s *Service) SetFlowStatus(ctx context.Context, id int, status, operator st
|
||||
if status == "ACTIVE" {
|
||||
action = "启用工艺流程"
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "process.flow.status", "", operator, "process_flow", "", action, map[string]any{"flowId": id, "status": status, "name": flow.Name, "stationNo": flow.ProcessCode})
|
||||
s.ctx.EventLog.Write(ctx, "process.flow.status", "", operator, "process_flow", "", action, map[string]any{"flowId": id, "status": status, "name": flow.Name, "processCode": flow.ProcessCode})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -335,7 +362,7 @@ func (s *Service) StationTask(ctx context.Context, stationNo int) (map[string]an
|
||||
flow, _ = s.ctx.EntClient.ProcessFlow.Get(ctx, st.FlowId)
|
||||
}
|
||||
steps := []*ProcessStepTemplate{}
|
||||
if flow != nil {
|
||||
if flow != nil && flow.Status == "ACTIVE" {
|
||||
steps = s.flowSteps(ctx, flow.ID, flow.ProcessCode)
|
||||
}
|
||||
orderNos := []string{}
|
||||
@@ -349,6 +376,7 @@ func (s *Service) StationTask(ctx context.Context, stationNo int) (map[string]an
|
||||
"stationNo": st.StationNo,
|
||||
"stationName": st.Name,
|
||||
"flow": flow,
|
||||
"flowActive": flow != nil && flow.Status == "ACTIVE",
|
||||
"steps": steps,
|
||||
"orderNos": orderNos,
|
||||
}, nil
|
||||
|
||||
Reference in New Issue
Block a user