fix: rebuild MES as compilable go-zero+ent backend (renamed bj_power_mes), restore 3 workstation projects from pristine original, align naming; all Go projects go build clean
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package temp_station
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"bj_power_mes/constants"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/equipment"
|
||||
"bj_power_mes/ent/equipmenttype"
|
||||
"bj_power_mes/ent/job"
|
||||
"bj_power_mes/ent/recipestep"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ListBufferSlotsLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewListBufferSlotsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBufferSlotsLogic {
|
||||
return &ListBufferSlotsLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListBufferSlotsLogic) ListBufferSlots() (*types.BufferSlotsReply, error) {
|
||||
// 查询所有已分配暂存台槽位的 job(不限 PositionType,以覆盖"不在位"状态)
|
||||
jobs, err := l.svcCtx.EntClient.Job.Query().
|
||||
Where(
|
||||
job.StatusNotIn(
|
||||
constants.JobStatus_Completed,
|
||||
constants.JobStatus_Scrapped,
|
||||
),
|
||||
job.TempSlotNoNotNil(),
|
||||
).
|
||||
Order(job.ByID()).All(l.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list buffer slots: %w", err)
|
||||
}
|
||||
|
||||
// 批量查询 recipe step(用于判断"待下线"和获取步骤名称)
|
||||
type stepKey struct {
|
||||
recipeID int
|
||||
stepID string
|
||||
}
|
||||
type stepInfo struct {
|
||||
stepType constants.StepType
|
||||
stepName string
|
||||
}
|
||||
stepInfos := make(map[stepKey]stepInfo)
|
||||
for _, j := range jobs {
|
||||
k := stepKey{j.RecipeId, j.CurrentStepId}
|
||||
if _, exists := stepInfos[k]; !exists && j.CurrentStepId != "" {
|
||||
step, err := l.svcCtx.EntClient.RecipeStep.Query().
|
||||
Where(recipestep.RecipeIdEQ(j.RecipeId), recipestep.StepIdEQ(j.CurrentStepId)).
|
||||
First(l.ctx)
|
||||
if err == nil {
|
||||
stepInfos[k] = stepInfo{stepType: step.StepType, stepName: step.StepName}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按槽位号建立索引
|
||||
occupiedMap := make(map[int]*types.BufferSlotReply, len(jobs))
|
||||
for _, j := range jobs {
|
||||
if j.TempSlotNo == nil {
|
||||
continue
|
||||
}
|
||||
slotNo := *j.TempSlotNo
|
||||
si := stepInfos[stepKey{j.RecipeId, j.CurrentStepId}]
|
||||
status := deriveSlotStatus(j, si.stepType)
|
||||
occupiedMap[slotNo] = &types.BufferSlotReply{
|
||||
SlotNo: slotNo,
|
||||
Status: status,
|
||||
Occupied: j.PositionType == constants.PositionType_OnBuffer,
|
||||
Job: &types.JobBrief{
|
||||
Id: j.ID,
|
||||
WorkOrderId: j.WorkOrderId,
|
||||
WorkpieceNo: j.WorkpieceNo,
|
||||
ProductTypeId: j.ProductTypeId,
|
||||
Status: string(j.Status),
|
||||
CurrentStepId: j.CurrentStepId,
|
||||
CurrentStepName: si.stepName,
|
||||
DockNo: nilIntDeref(j.DockNo),
|
||||
DockSlotNo: nilIntDeref(j.DockSlotNo),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
total := 8
|
||||
if equip, _ := l.svcCtx.EntClient.Equipment.Query().
|
||||
Where(equipment.HasEquipmentTypeWith(equipmenttype.CodeEQ(constants.EquipmentTypeCode_TempStore))).
|
||||
First(l.ctx); equip != nil && equip.SlotCount > 0 {
|
||||
total = equip.SlotCount
|
||||
}
|
||||
|
||||
// 生成所有槽位(含空槽)
|
||||
slots := make([]types.BufferSlotReply, total)
|
||||
occupied := 0
|
||||
for i := 0; i < total; i++ {
|
||||
slotNo := i + 1
|
||||
if s, ok := occupiedMap[slotNo]; ok {
|
||||
slots[i] = *s
|
||||
if s.Status == "Processing" || s.Status == "Done" {
|
||||
occupied++
|
||||
}
|
||||
} else {
|
||||
slots[i] = types.BufferSlotReply{
|
||||
SlotNo: slotNo,
|
||||
Status: "Idle",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &types.BufferSlotsReply{
|
||||
Slots: slots,
|
||||
Occupied: occupied,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// deriveSlotStatus 根据 job 状态和 recipe step 类型推导暂存台槽位显示状态:
|
||||
// - Processing: 工件在暂存台上,正常流转中
|
||||
// - Done: 工件在暂存台上,等待下到接驳台(当前步骤为 UNLOAD_DOCK)
|
||||
// - Reserved: 槽位已分配,工件在设备上或手爪上(不在位)
|
||||
func deriveSlotStatus(j *ent.Job, stepType constants.StepType) string {
|
||||
if j.PositionType == constants.PositionType_OnBuffer {
|
||||
if stepType == constants.StepType_UnloadDock {
|
||||
return "Done"
|
||||
}
|
||||
return "Processing"
|
||||
}
|
||||
return "Reserved"
|
||||
}
|
||||
|
||||
// nilIntDeref 安全地解引用 *int, nil 时返回 0
|
||||
func nilIntDeref(p *int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
return *p
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package temp_station
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/common/errorx"
|
||||
"bj_power_mes/constants"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/dockslot"
|
||||
"bj_power_mes/ent/job"
|
||||
"bj_power_mes/ent/recipestep"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ResetBufferLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// 重置暂存台:将在位工件回写到接驳台位置或完成下线
|
||||
func NewResetBufferLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetBufferLogic {
|
||||
return &ResetBufferLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// ResetBuffer 重置暂存台,根据每个工件当前 step 选择两种处理:
|
||||
// - 待下线 (当前 step 为 UNLOAD_DOCK): 视作人工完成下线,status=COMPLETED,
|
||||
// completedAt=now,tempSlotNo=NULL,work_order.finishedNum+1,不占接驳台
|
||||
// - 其它 (Processing): 回写到接驳台位置,positionType=ON_DOCK,
|
||||
// dockNo/dockSlotNo 优先回原位,占用 dock_slot
|
||||
//
|
||||
// 仅处理 PositionType=ON_BUFFER 的"在位"工件;Reserved (工件在设备上) 不动.
|
||||
// 整个流程在单事务内完成,任一失败回滚.
|
||||
func (l *ResetBufferLogic) ResetBuffer() (*types.ResetBufferReply, error) {
|
||||
// 前置检查:仅允许在没有进行中工单时重置,避免与生产调度/补料并发写入
|
||||
hasRunning, err := l.svcCtx.EntClient.WorkOrder.Query().
|
||||
Where(workorder.StatusEQ(constants.WorkOrderStatus_InProgress)).
|
||||
Exist(l.ctx)
|
||||
if err != nil {
|
||||
l.Errorf("查询进行中工单失败: %v", err)
|
||||
return nil, errorx.NewDirectError("查询进行中工单失败")
|
||||
}
|
||||
if hasRunning {
|
||||
return nil, errorx.NewDirectError("有进行中的工单,请先暂停或完成后再重置暂存台")
|
||||
}
|
||||
|
||||
// 进入重置态:阻止 EventLoop 在重置期间发起新补料 dispatch
|
||||
if l.svcCtx.EventLoop != nil {
|
||||
if ok, reason := l.svcCtx.EventLoop.BeginReset(); !ok {
|
||||
return nil, errorx.NewDirectError(reason)
|
||||
}
|
||||
defer l.svcCtx.EventLoop.EndReset()
|
||||
}
|
||||
|
||||
// 1) 查询所有"在位"暂存台工件
|
||||
jobs, err := l.svcCtx.EntClient.Job.Query().
|
||||
Where(
|
||||
job.PositionTypeEQ(constants.PositionType_OnBuffer),
|
||||
job.StatusNotIn(
|
||||
constants.JobStatus_Completed,
|
||||
constants.JobStatus_Scrapped,
|
||||
),
|
||||
).
|
||||
Order(job.ByID()).All(l.ctx)
|
||||
if err != nil {
|
||||
l.Errorf("查询暂存台工件失败: %v", err)
|
||||
return nil, errorx.NewDirectError("查询暂存台工件失败")
|
||||
}
|
||||
|
||||
if len(jobs) == 0 {
|
||||
return &types.ResetBufferReply{Reset: 0, Items: []types.ResetBufferItem{}}, nil
|
||||
}
|
||||
|
||||
// 2) 批量查询 recipe step 类型,识别"待下线"工件
|
||||
type stepKey struct {
|
||||
recipeID int
|
||||
stepID string
|
||||
}
|
||||
stepTypes := make(map[stepKey]constants.StepType)
|
||||
for _, j := range jobs {
|
||||
k := stepKey{j.RecipeId, j.CurrentStepId}
|
||||
if _, ok := stepTypes[k]; ok || j.CurrentStepId == "" {
|
||||
continue
|
||||
}
|
||||
st, err := l.svcCtx.EntClient.RecipeStep.Query().
|
||||
Where(recipestep.RecipeIdEQ(j.RecipeId), recipestep.StepIdEQ(j.CurrentStepId)).
|
||||
First(l.ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
stepTypes[k] = st.StepType
|
||||
}
|
||||
|
||||
// 3) 开启事务
|
||||
tx, err := l.svcCtx.EntClient.Tx(l.ctx)
|
||||
if err != nil {
|
||||
l.Errorf("开启事务失败: %v", err)
|
||||
return nil, errorx.NewDirectError("系统错误")
|
||||
}
|
||||
|
||||
// 4) 读取所有 dock_slot 建立占用索引 (仅"回写"路径需要)
|
||||
allDocks, err := tx.DockSlot.Query().Order(dockslot.ByDockNo(), dockslot.BySlotNo()).All(l.ctx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("读取 dock_slot 失败: %v", err)
|
||||
return nil, errorx.NewDirectError("读取 dock_slot 失败")
|
||||
}
|
||||
// key: dockNo*100+slotNo → *DockSlot
|
||||
dockIndex := make(map[int]*ent.DockSlot, len(allDocks))
|
||||
for _, d := range allDocks {
|
||||
dockIndex[d.DockNo*100+d.SlotNo] = d
|
||||
}
|
||||
|
||||
// 累计每个工单完成数,事务末尾一次性写 work_order
|
||||
finishedDelta := make(map[int]int)
|
||||
|
||||
// 5) 逐个工件处理
|
||||
items := make([]types.ResetBufferItem, 0, len(jobs))
|
||||
now := time.Now()
|
||||
for _, jb := range jobs {
|
||||
fromSlot := 0
|
||||
if jb.TempSlotNo != nil {
|
||||
fromSlot = *jb.TempSlotNo
|
||||
}
|
||||
|
||||
// 5a) 待下线 → 完成
|
||||
if stepTypes[stepKey{jb.RecipeId, jb.CurrentStepId}] == constants.StepType_UnloadDock {
|
||||
_, err = tx.Job.UpdateOneID(jb.ID).
|
||||
SetStatus(constants.JobStatus_Completed).
|
||||
SetCompletedAt(now).
|
||||
ClearTempSlotNo().
|
||||
AddVersion(1).
|
||||
Save(l.ctx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("完成工件失败 jobId=%d: %v", jb.ID, err)
|
||||
return nil, errorx.NewDirectError("完成工件失败")
|
||||
}
|
||||
finishedDelta[jb.WorkOrderId]++
|
||||
|
||||
items = append(items, types.ResetBufferItem{
|
||||
JobId: jb.ID,
|
||||
WorkpieceNo: jb.WorkpieceNo,
|
||||
FromSlotNo: fromSlot,
|
||||
// DockNo/DockSlotNo 为 0 表示工件已下线,未占用接驳台
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 5b) Processing → 回写接驳台
|
||||
targetDockNo, targetSlotNo, err := pickDockSlot(jb, dockIndex)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("分配接驳台位置失败 jobId=%d: %v", jb.ID, err)
|
||||
return nil, errorx.NewDirectError(fmt.Sprintf("分配接驳台位置失败 jobId=%d: %v", jb.ID, err))
|
||||
}
|
||||
|
||||
// 占用 dock_slot
|
||||
ds := dockIndex[targetDockNo*100+targetSlotNo]
|
||||
if _, err := tx.DockSlot.UpdateOneID(ds.ID).
|
||||
SetStatus(1).
|
||||
SetCurrentJobId(jb.ID).
|
||||
SetWorkpieceTypeId(jb.ProductTypeId).
|
||||
SetWorkOrderId(jb.WorkOrderId).
|
||||
Save(l.ctx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("更新 dock_slot 失败 jobId=%d dock=%d:%d: %v", jb.ID, targetDockNo, targetSlotNo, err)
|
||||
return nil, errorx.NewDirectError("更新 dock_slot 失败")
|
||||
}
|
||||
// 索引同步更新, 避免后续 job 重复占同一槽位
|
||||
ds.Status = 1
|
||||
ds.CurrentJobId = &jb.ID
|
||||
|
||||
_, err = tx.Job.UpdateOneID(jb.ID).
|
||||
SetPositionType(constants.PositionType_OnDock).
|
||||
SetPositionRefId(fmt.Sprintf("%d:%d", targetDockNo, targetSlotNo)).
|
||||
SetDockNo(targetDockNo).
|
||||
SetDockSlotNo(targetSlotNo).
|
||||
ClearTempSlotNo().
|
||||
AddVersion(1).
|
||||
Save(l.ctx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("更新 job 失败 jobId=%d: %v", jb.ID, err)
|
||||
return nil, errorx.NewDirectError("更新 job 失败")
|
||||
}
|
||||
|
||||
items = append(items, types.ResetBufferItem{
|
||||
JobId: jb.ID,
|
||||
WorkpieceNo: jb.WorkpieceNo,
|
||||
FromSlotNo: fromSlot,
|
||||
DockNo: targetDockNo,
|
||||
DockSlotNo: targetSlotNo,
|
||||
})
|
||||
}
|
||||
|
||||
// 6) 一次性更新 work_order.finishedNum
|
||||
for workOrderID, delta := range finishedDelta {
|
||||
if delta == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.WorkOrder.UpdateOneID(workOrderID).AddFinishedNum(delta).Save(l.ctx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
l.Errorf("更新工单完成数失败 workOrderId=%d delta=%d: %v", workOrderID, delta, err)
|
||||
return nil, errorx.NewDirectError("更新工单完成数失败")
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
l.Errorf("提交事务失败: %v", err)
|
||||
return nil, errorx.NewDirectError("提交事务失败")
|
||||
}
|
||||
|
||||
// 同步释放 TempStoreActor 内存槽位:事务内已清空 job.temp_slot_no,
|
||||
// 但 Actor 内存 slots 仍记着旧 jobID,不释放会导致后续 AllocateSlot 假性判满。
|
||||
if l.svcCtx.EventLoop != nil {
|
||||
jobIDs := make([]int, 0, len(items))
|
||||
for _, it := range items {
|
||||
jobIDs = append(jobIDs, it.JobId)
|
||||
}
|
||||
l.svcCtx.EventLoop.ReleaseTempSlots(jobIDs)
|
||||
}
|
||||
|
||||
// 7) 通知前端刷新两个面板
|
||||
if l.svcCtx.SSEHandler != nil {
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventTempStationUpdate, "{}")
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventDockDataUpdate, "{}")
|
||||
}
|
||||
|
||||
l.Infof("暂存台重置完成: 处理 %d 个工件 (含完成: %d)", len(items), sumValues(finishedDelta))
|
||||
return &types.ResetBufferReply{Reset: len(items), Items: items}, nil
|
||||
}
|
||||
|
||||
// pickDockSlot 为单个 job 选定接驳台位置:
|
||||
// 1. 优先用 job.dockNo/dockSlotNo, 仅当该槽位 status=0(空) 时直接占用
|
||||
// 2. 否则从所有 status=0 的 dock_slot 中按 dockNo,slotNo 顺序取第一个
|
||||
// 3. 全部满则报错
|
||||
func pickDockSlot(jb *ent.Job, dockIndex map[int]*ent.DockSlot) (int, int, error) {
|
||||
// 优先回原位
|
||||
if jb.DockNo != nil && jb.DockSlotNo != nil {
|
||||
key := (*jb.DockNo)*100 + (*jb.DockSlotNo)
|
||||
if ds, ok := dockIndex[key]; ok && ds.Status == 0 {
|
||||
return ds.DockNo, ds.SlotNo, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 顺序分配 (Go map 无序,遍历找最小空闲 key)
|
||||
best := -1
|
||||
for k, ds := range dockIndex {
|
||||
if ds.Status != 0 {
|
||||
continue
|
||||
}
|
||||
if best < 0 || k < best {
|
||||
best = k
|
||||
}
|
||||
}
|
||||
if best < 0 {
|
||||
return 0, 0, fmt.Errorf("接驳台已无空闲槽位")
|
||||
}
|
||||
ds := dockIndex[best]
|
||||
return ds.DockNo, ds.SlotNo, nil
|
||||
}
|
||||
|
||||
func sumValues(m map[int]int) int {
|
||||
s := 0
|
||||
for _, v := range m {
|
||||
s += v
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user