[FIX] 合併 feat/inventory-mqtt-resend:庫存效期 MQTT 逾時自動解鎖 + timeout 多語系

This commit is contained in:
sky121113 2026-06-24 09:06:32 +08:00
commit 1a1518c611
3 changed files with 13 additions and 3 deletions

View File

@ -208,9 +208,12 @@ class MachineController extends AdminController
->get(); ->get();
// 取得該機台目前所有等待中的庫存更新指令 (Pending Commands) // 取得該機台目前所有等待中的庫存更新指令 (Pending Commands)
// 僅將「1 分鐘內」的 pending 視為鎖定中;超過 1 分鐘未收到機台回傳 (ACK)
// 視為逾時,自動解除鎖定,讓使用者可重新編輯與重新下發 (與 dispense 逾時邏輯一致)
$pendingSlotNos = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id) $pendingSlotNos = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
->where('command_type', 'reload_stock') ->where('command_type', 'reload_stock')
->where('status', 'pending') ->where('status', 'pending')
->where('created_at', '>=', now()->subMinute())
->get() ->get()
->pluck('payload.slot_no') ->pluck('payload.slot_no')
->flatten() ->flatten()

View File

@ -510,10 +510,15 @@ class MachineService
->first(); ->first();
if ($pendingCommand) { if ($pendingCommand) {
// 直接覆蓋:將前一個尚未執行的指令標記為「已取代」 // 重新下發時覆蓋前一筆尚未執行的指令。
// 若舊指令已超過 1 分鐘未收到機台回傳 (ACK),視為逾時 (timeout)
// 否則為使用者主動覆蓋 (superseded)。與 dispense 逾時邏輯一致。
$isTimedOut = $pendingCommand->created_at->lt(now()->subMinute());
$pendingCommand->update([ $pendingCommand->update([
'status' => 'superseded', 'status' => $isTimedOut ? 'timeout' : 'superseded',
'note' => __('Superseded by new command'), 'note' => $isTimedOut
? __('Superseded by new command (Timeout)')
: __('Superseded by new command'),
]); ]);
} }

View File

@ -296,6 +296,7 @@
case 'sent': return 'bg-cyan-100 text-cyan-600 dark:bg-cyan-500/10 dark:text-cyan-400 border-cyan-200 dark:border-cyan-500/20'; case 'sent': return 'bg-cyan-100 text-cyan-600 dark:bg-cyan-500/10 dark:text-cyan-400 border-cyan-200 dark:border-cyan-500/20';
case 'success': return 'bg-emerald-100 text-emerald-600 dark:bg-emerald-500/10 dark:text-emerald-400 border-emerald-200 dark:border-emerald-500/20'; case 'success': return 'bg-emerald-100 text-emerald-600 dark:bg-emerald-500/10 dark:text-emerald-400 border-emerald-200 dark:border-emerald-500/20';
case 'failed': return 'bg-rose-100 text-rose-600 dark:bg-rose-500/10 dark:text-rose-400 border-rose-200 dark:border-rose-500/20'; case 'failed': return 'bg-rose-100 text-rose-600 dark:bg-rose-500/10 dark:text-rose-400 border-rose-200 dark:border-rose-500/20';
case 'timeout': return 'bg-rose-100 text-rose-600 dark:bg-rose-500/10 dark:text-rose-400 border-rose-200 dark:border-rose-500/20';
case 'superseded': return 'bg-slate-100 text-slate-500 dark:bg-slate-500/10 dark:text-slate-400 border-slate-200 dark:border-slate-500/20 opacity-80'; case 'superseded': return 'bg-slate-100 text-slate-500 dark:bg-slate-500/10 dark:text-slate-400 border-slate-200 dark:border-slate-500/20 opacity-80';
default: return 'bg-slate-100 text-slate-600 border-slate-200'; default: return 'bg-slate-100 text-slate-600 border-slate-200';
} }
@ -322,6 +323,7 @@
'sent': {{ Js::from(__('Sent')) }}, 'sent': {{ Js::from(__('Sent')) }},
'success': {{ Js::from(__('Success')) }}, 'success': {{ Js::from(__('Success')) }},
'failed': {{ Js::from(__('Failed')) }}, 'failed': {{ Js::from(__('Failed')) }},
'timeout': {{ Js::from(__('Timeout')) }},
'superseded': {{ Js::from(__('Superseded')) }} 'superseded': {{ Js::from(__('Superseded')) }}
}; };
return statuses[status] || status; return statuses[status] || status;