[RELEASE] feat/inventory-mqtt-resend → main:庫存效期管理 MQTT 下發逾時自動解鎖上線
1. slotsAjax 的 is_pending 僅計入 1 分鐘內的 pending reload_stock,逾時自動解鎖,避免機台未回 ACK 時介面永久卡住 2. updateSlot 重新下發時逾時舊指令標記 timeout、否則 superseded,與 dispense 邏輯一致 3. stock.blade.php 遠端指令紀錄補上 timeout 狀態多語系與徽章樣式
This commit is contained in:
commit
47073f0a96
@ -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()
|
||||||
|
|||||||
@ -508,10 +508,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'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user