Files
bj_power/bj_power_mes/internal/processor/eventloop/recovery.go
T

155 lines
4.9 KiB
Go
Raw Normal View History

package eventloop
import (
"context"
"fmt"
"log/slog"
"time"
"bj_power_mes/constants"
"bj_power_mes/ent"
"bj_power_mes/ent/equipmentslot"
"bj_power_mes/ent/job"
"bj_power_mes/ent/pallet"
"bj_power_mes/ent/workorder"
)
// RecoverOnStartup 启动时执行保守人工确认恢复。
// 先暂停所有工单再校验一致性——避免启动时未经验证的状态被调度器消费。
// 1. 将所有 IN_PROGRESS 的 work_order 改为 PAUSED
// 2. 校验 temp_slot_no 一致性(重复/越界)
// 3. 校验 equipment_slot 与 job 位置一致性
// 4. 不一致项创建 manual_action 等待人工确认
func (d *DBState) RecoverOnStartup(ctx context.Context) (recoveredJobs int, pendingRecoveryOrderIDs []int, manualActions []*ent.ManualAction, err error) {
// 逐个检查 IN_PROGRESS/PAUSING 订单:有 ON_EQUIPMENT 活跃工件的保持原状等待人工确认
activeOrders, _ := d.client.WorkOrder.Query().
Where(workorder.StatusIn(constants.WorkOrderStatus_InProgress, constants.WorkOrderStatus_Pausing)).
All(ctx)
var pendingOrderIDs []int
for _, o := range activeOrders {
onEquipCount, _ := d.client.Job.Query().
Where(
job.WorkOrderIdEQ(o.ID),
job.StatusNotIn(constants.JobStatus_Completed, constants.JobStatus_Scrapped),
job.PositionTypeEQ(constants.PositionType_OnEquipment),
).
Count(ctx)
if onEquipCount > 0 {
// 在设备中的工件需要人工确认,保持 IN_PROGRESS
pendingOrderIDs = append(pendingOrderIDs, o.ID)
slog.Info("recovery: order kept IN_PROGRESS for position confirmation", "orderId", o.ID)
} else {
// 没有设备中工件,安全切换为 PAUSED
_, _ = d.client.WorkOrder.UpdateOneID(o.ID).
SetStatus(constants.WorkOrderStatus_Paused).
Save(ctx)
}
}
jobs, err := d.GetActiveJobs(ctx)
if err != nil {
return 0, nil, nil, err
}
// 构建 jobID→orderID 映射,用于 ManualAction 关联工单
jobOrder := make(map[int]int, len(jobs))
for _, j := range jobs {
jobOrder[j.ID] = j.WorkOrderId
}
createMA := func(jobID int, desc string) {
ma, _ := d.CreateManualAction(ctx, constants.ManualActionType_RecoveryRequired, jobID, jobOrder[jobID], desc)
if ma != nil {
manualActions = append(manualActions, ma)
}
}
capacity := d.TempSlotCapacity(ctx)
slotUsers := make(map[int][]int)
for _, j := range jobs {
if j.TempSlotNo != nil {
slot := *j.TempSlotNo
if slot < 1 || slot > capacity {
createMA(j.ID, fmt.Sprintf("temp_slot_no %d out of range [1,%d]", slot, capacity))
continue
}
slotUsers[slot] = append(slotUsers[slot], j.ID)
}
}
for slot, users := range slotUsers {
if len(users) > 1 {
slog.Warn("recovery: duplicate temp slot", "slot", slot, "jobs", users)
for _, jID := range users {
createMA(jID, fmt.Sprintf("duplicate temp_slot_no %d", slot))
}
}
}
for _, j := range jobs {
if j.PositionType == constants.PositionType_OnEquipment {
equipID, slotNo := parsePositionRef(j.PositionRefId)
if equipID == 0 {
continue
}
slot, slotErr := d.client.EquipmentSlot.Query().
Where(equipmentslot.EquipmentIdEQ(equipID), equipmentslot.SlotNoEQ(slotNo)).
First(ctx)
if slotErr != nil {
slog.Warn("recovery: slot not found", "equip", equipID, "slot", slotNo)
createMA(j.ID, fmt.Sprintf("equipment slot %d:%d not found", equipID, slotNo))
continue
}
cjid := 0
if slot.CurrentJobId != nil {
cjid = *slot.CurrentJobId
}
if cjid != j.ID {
createMA(j.ID, fmt.Sprintf("equipment_slot %d:%d has job %d, expected %d", equipID, slotNo, cjid, j.ID))
}
}
}
// Pallet 状态推导
pallets, pErr := d.client.Pallet.Query().
Where(pallet.StatusIn(constants.PalletStatus_PendingProcess, constants.PalletStatus_Processing)).
All(ctx)
if pErr != nil {
slog.Warn("recovery: query pallets failed", "error", pErr)
} else {
for _, p := range pallets {
palletPred := job.HasPalletWith(pallet.IDEQ(p.ID))
activeCount, cErr := d.client.Job.Query().
Where(
palletPred,
job.StatusNotIn(constants.JobStatus_Completed, constants.JobStatus_Scrapped),
).
Count(ctx)
if cErr != nil {
continue
}
if activeCount == 0 {
// 全部终态 → COMPLETED
totalJobs, _ := d.client.Job.Query().Where(job.HasPalletWith(pallet.IDEQ(p.ID))).Count(ctx)
scrappedCount, _ := d.client.Job.Query().
Where(job.HasPalletWith(pallet.IDEQ(p.ID)), job.StatusEQ(constants.JobStatus_Scrapped)).
Count(ctx)
now := time.Now()
_, err := d.client.Pallet.UpdateOneID(p.ID).
SetStatus(constants.PalletStatus_Completed).
SetPassQty(totalJobs - scrappedCount).
SetNgQty(scrappedCount).
SetCompletedAt(now).
Save(ctx)
if err != nil {
slog.Warn("recovery: complete pallet failed", "palletId", p.ID, "error", err)
} else {
slog.Info("recovery: pallet completed", "palletId", p.ID)
}
}
// 存在非终态 Job → 保持不变,跟随 Job 恢复流程
}
}
return len(jobs), pendingOrderIDs, manualActions, nil
}