本次提交覆盖多个业务模块的功能完善与体验优化:
1. **鉴权与配置调整**:
- 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
- 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
- 工位配置放开1~12限制,改为仅校验大于0
2. **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3. **功能新增**:
- 新增工位类型、工艺路线与产线点位台账模块
- 添加工艺PDF预览面板、工位终端代理转发接口
- 新增操作日志按操作人列表筛选、工位登出日志记录
- 新增PLC移料指令与产线点位状态管理
4. **业务流程优化**:
- 调整BOM物料删除校验逻辑,优化工单备料计算
- 补充物料图号、检测单号等追溯字段
- 完善工艺流程图与工位绑定关系说明
- 优化前端页面文案与交互细节
5. **代码规范与维护**:
- 新增通用工具函数与前端静态资源
- 整理路由权限与中间件逻辑
- 修复部分接口与配置的不兼容问题
272 lines
8.3 KiB
Go
272 lines
8.3 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"bj_power_mes/ent"
|
|
"bj_power_mes/ent/processroute"
|
|
"bj_power_mes/ent/routesegment"
|
|
"bj_power_mes/ent/station"
|
|
)
|
|
|
|
// ---------- 工艺路线(M2:有序并行工位段) ----------
|
|
|
|
// RouteSegmentReq 路线段请求:一段 = 一个流程图 + 一组并行工位
|
|
type RouteSegmentReq struct {
|
|
Seq int `json:"seq"`
|
|
FlowId int `json:"flowId"`
|
|
Stations []int `json:"stations"`
|
|
SegmentType string `json:"segmentType"` // LINE / OFFLINE
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// RouteReq 工艺路线保存请求
|
|
type RouteReq struct {
|
|
Id int `json:"id"`
|
|
Name string `json:"name"`
|
|
ProductTypeId *int `json:"productTypeId"`
|
|
Status string `json:"status"`
|
|
Remark string `json:"remark"`
|
|
Segments []RouteSegmentReq `json:"segments"`
|
|
}
|
|
|
|
// RouteSegmentVO 路线段视图
|
|
type RouteSegmentVO struct {
|
|
Id int `json:"id"`
|
|
RouteId int `json:"routeId"`
|
|
Seq int `json:"seq"`
|
|
FlowId int `json:"flowId"`
|
|
FlowName string `json:"flowName"`
|
|
Stations []int `json:"stations"`
|
|
SegmentType string `json:"segmentType"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// RouteVO 工艺路线视图(含段列表)
|
|
type RouteVO struct {
|
|
Id int `json:"id"`
|
|
Name string `json:"name"`
|
|
ProductTypeId int `json:"productTypeId"`
|
|
ProductName string `json:"productName"`
|
|
Status string `json:"status"`
|
|
Remark string `json:"remark"`
|
|
Segments []*RouteSegmentVO `json:"segments"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// SaveRoute 保存工艺路线(含段)。约束:段序号>=0;LINE 段工位须存在且不跨段重复;OFFLINE 段(0/13 线下位)不校验工位。
|
|
func (s *Service) SaveRoute(ctx context.Context, req RouteReq, operator string) error {
|
|
if req.Name == "" {
|
|
return errors.New("路线名称必填")
|
|
}
|
|
status := req.Status
|
|
if status == "" {
|
|
status = "ACTIVE"
|
|
}
|
|
if status != "ACTIVE" && status != "INACTIVE" {
|
|
return errors.New("非法状态")
|
|
}
|
|
if len(req.Segments) == 0 {
|
|
return errors.New("路线至少包含一个段")
|
|
}
|
|
seenStation := map[int]bool{}
|
|
for _, seg := range req.Segments {
|
|
if seg.Seq < 0 {
|
|
return errors.New("段序号非法(从 0 开始)")
|
|
}
|
|
if seg.FlowId > 0 {
|
|
if _, err := s.ctx.EntClient.ProcessFlow.Get(ctx, seg.FlowId); err != nil {
|
|
return errors.New("所选工艺流程不存在")
|
|
}
|
|
}
|
|
stType := seg.SegmentType
|
|
if stType == "" {
|
|
stType = "LINE"
|
|
}
|
|
if stType != "LINE" && stType != "OFFLINE" {
|
|
return errors.New("段类型仅支持 LINE/OFFLINE")
|
|
}
|
|
if len(seg.Stations) == 0 {
|
|
return errors.New("每段至少包含一个工位")
|
|
}
|
|
if stType == "LINE" {
|
|
for _, no := range seg.Stations {
|
|
if no < 1 {
|
|
return errors.New("工位号非法")
|
|
}
|
|
if _, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(no)).First(ctx); err != nil {
|
|
return fmt.Errorf("工位 %d 不存在,请先在工位主数据中维护", no)
|
|
}
|
|
if seenStation[no] {
|
|
return fmt.Errorf("工位 %d 在路线中重复出现", no)
|
|
}
|
|
seenStation[no] = true
|
|
}
|
|
}
|
|
}
|
|
var routeID int
|
|
if req.Id > 0 {
|
|
upd := s.ctx.EntClient.ProcessRoute.UpdateOneID(req.Id).
|
|
SetName(req.Name).SetStatus(status).SetRemark(req.Remark)
|
|
if req.ProductTypeId != nil {
|
|
upd.SetProductTypeId(*req.ProductTypeId)
|
|
} else {
|
|
upd.ClearProductTypeId()
|
|
}
|
|
if _, err := upd.Save(ctx); err != nil {
|
|
return err
|
|
}
|
|
routeID = req.Id
|
|
} else {
|
|
cr := s.ctx.EntClient.ProcessRoute.Create().
|
|
SetName(req.Name).SetStatus(status).SetRemark(req.Remark)
|
|
if req.ProductTypeId != nil {
|
|
cr.SetProductTypeId(*req.ProductTypeId)
|
|
}
|
|
r, err := cr.Save(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
routeID = r.ID
|
|
}
|
|
_, _ = s.ctx.EntClient.RouteSegment.Delete().Where(routesegment.RouteId(routeID)).Exec(ctx)
|
|
for _, seg := range req.Segments {
|
|
stType := seg.SegmentType
|
|
if stType == "" {
|
|
stType = "LINE"
|
|
}
|
|
if _, err := s.ctx.EntClient.RouteSegment.Create().
|
|
SetRouteId(routeID).SetSeq(seg.Seq).SetFlowId(seg.FlowId).
|
|
SetStations(seg.Stations).SetSegmentType(stType).SetRemark(seg.Remark).Save(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.ctx.EventLog.Write(ctx, "route.save", "", operator, "process_route", "", "维护工艺路线",
|
|
map[string]any{"routeId": routeID, "segments": len(req.Segments)})
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) routeVO(ctx context.Context, r *ent.ProcessRoute) (*RouteVO, error) {
|
|
segs, err := s.ctx.EntClient.RouteSegment.Query().
|
|
Where(routesegment.RouteId(r.ID)).Order(ent.Asc(routesegment.FieldSeq)).All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
flows, _ := s.ctx.EntClient.ProcessFlow.Query().All(ctx)
|
|
flowMap := map[int]string{}
|
|
for _, f := range flows {
|
|
flowMap[f.ID] = f.Name
|
|
}
|
|
segVOs := make([]*RouteSegmentVO, 0, len(segs))
|
|
for _, seg := range segs {
|
|
segVOs = append(segVOs, &RouteSegmentVO{
|
|
Id: seg.ID, RouteId: seg.RouteId, Seq: seg.Seq,
|
|
FlowId: seg.FlowId, FlowName: flowMap[seg.FlowId],
|
|
Stations: seg.Stations, SegmentType: seg.SegmentType, Remark: seg.Remark,
|
|
})
|
|
}
|
|
return &RouteVO{
|
|
Id: r.ID, Name: r.Name, ProductTypeId: r.ProductTypeId,
|
|
Status: r.Status, Remark: r.Remark, Segments: segVOs, CreatedAt: r.CreatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// ListRoutes 查询工艺路线(page>0 真分页;否则全量)
|
|
func (s *Service) ListRoutes(ctx context.Context, name, status string, page, pageSize int) (any, error) {
|
|
q := s.ctx.EntClient.ProcessRoute.Query()
|
|
if name != "" {
|
|
q = q.Where(processroute.NameContainsFold(name))
|
|
}
|
|
if status != "" {
|
|
q = q.Where(processroute.Status(status))
|
|
}
|
|
routes, err := q.Order(ent.Desc(processroute.FieldID)).All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pts, _ := s.ctx.EntClient.ProductType.Query().All(ctx)
|
|
ptMap := map[int]string{}
|
|
for _, p := range pts {
|
|
ptMap[p.ID] = p.Name
|
|
}
|
|
out := make([]*RouteVO, 0, len(routes))
|
|
for _, r := range routes {
|
|
vo, err := s.routeVO(ctx, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vo.ProductName = ptMap[r.ProductTypeId]
|
|
out = append(out, vo)
|
|
}
|
|
if page > 0 {
|
|
total := len(out)
|
|
start := (page - 1) * pageSize
|
|
if start > total {
|
|
start = total
|
|
}
|
|
end := start + pageSize
|
|
if end > total {
|
|
end = total
|
|
}
|
|
return map[string]any{"total": total, "list": out[start:end], "page": page, "pageSize": pageSize}, nil
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// GetRoute 获取单条路线详情
|
|
func (s *Service) GetRoute(ctx context.Context, id int) (*RouteVO, error) {
|
|
r, err := s.ctx.EntClient.ProcessRoute.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, errors.New("工艺路线不存在")
|
|
}
|
|
return s.routeVO(ctx, r)
|
|
}
|
|
|
|
// GetActiveRouteByProductType 取某产品型号的启用路线(工单建单自动带出)
|
|
func (s *Service) GetActiveRouteByProductType(ctx context.Context, productTypeId int) (*RouteVO, error) {
|
|
r, err := s.ctx.EntClient.ProcessRoute.Query().
|
|
Where(processroute.ProductTypeId(productTypeId), processroute.Status("ACTIVE")).
|
|
Order(ent.Desc(processroute.FieldID)).First(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.routeVO(ctx, r)
|
|
}
|
|
|
|
// DeleteRoute 删除路线(先清段)
|
|
func (s *Service) DeleteRoute(ctx context.Context, id int, operator string) error {
|
|
_, _ = s.ctx.EntClient.RouteSegment.Delete().Where(routesegment.RouteId(id)).Exec(ctx)
|
|
s.ctx.EventLog.Write(ctx, "route.delete", "", operator, "process_route", "", "删除工艺路线", map[string]any{"routeId": id})
|
|
return s.ctx.EntClient.ProcessRoute.DeleteOneID(id).Exec(ctx)
|
|
}
|
|
|
|
// BuildRouteSnapshot 由路线构建工单段快照,并返回展开的工位顺序(仅 LINE 段,兼容旧工位终端 logic)
|
|
func (s *Service) BuildRouteSnapshot(route *RouteVO) ([]map[string]any, []int) {
|
|
snapshot := make([]map[string]any, 0, len(route.Segments))
|
|
lineStations := []int{}
|
|
seen := map[int]bool{}
|
|
for _, seg := range route.Segments {
|
|
snapshot = append(snapshot, map[string]any{
|
|
"seq": seg.Seq,
|
|
"flowId": seg.FlowId,
|
|
"flowName": seg.FlowName,
|
|
"stations": seg.Stations,
|
|
"segmentType": seg.SegmentType,
|
|
})
|
|
if seg.SegmentType == "LINE" {
|
|
for _, no := range seg.Stations {
|
|
if !seen[no] {
|
|
seen[no] = true
|
|
lineStations = append(lineStations, no)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sort.Ints(lineStations)
|
|
return snapshot, lineStations
|
|
}
|