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:
SunYF
2026-08-27 10:56:41 +08:00
parent 380715f8a9
commit 371b494c54
523 changed files with 67923 additions and 24758 deletions
@@ -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
}