155 lines
4.9 KiB
Go
155 lines
4.9 KiB
Go
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
|
|
}
|