serialNo = $serialNo; $this->payload = $payload; } /** * 處理 B055 出貨回報 ACK * 原始邏輯移植自 MachineController::reportDispenseResult() */ public function handle(): void { $commandId = $this->payload['command_id'] ?? null; $resultCode = $this->payload['result'] ?? null; $stockReported = $this->payload['stock'] ?? null; if (empty($commandId)) { Log::warning('MQTT CommandAck: Missing command_id', [ 'serial_no' => $this->serialNo, 'payload' => $this->payload ]); return; } $command = RemoteCommand::find($commandId); if (!$command) { Log::warning('MQTT CommandAck: Command not found', [ 'serial_no' => $this->serialNo, 'command_id' => $commandId ]); return; } $machine = $command->machine; // 【優先執行】更新機台為在線狀態 (只要收到訊息就代表在線,不受指令狀態影響) if ($machine) { $machine->update([ 'last_heartbeat_at' => now(), 'status' => 'online', ]); } // 核心邏輯:只有處於 pending 狀態的指令才處理後續的業務邏輯 (如庫存異動) if ($command->status !== 'pending') { Log::info('MQTT CommandAck: Command already processed, ignoring business logic', [ 'command_id' => $commandId, 'current_status' => $command->status, ]); return; } // 判定執行結果 (result: "success" 或 "0" 代表成功) $status = in_array($resultCode, ['success', '0'], true) ? 'success' : 'failed'; $payloadUpdates = [ 'reported_stock' => $stockReported, 'report_result' => $resultCode, ]; $machine = $command->machine; if ($command->command_type === 'dispense') { // 若成功,連動更新貨道庫存 if ($status === 'success' && isset($command->payload['slot_no'])) { $slotNo = $command->payload['slot_no']; $slot = $machine->slots()->where('slot_no', $slotNo)->first(); if ($slot) { $oldStock = $slot->stock; // 若 APP 回傳的庫存大於 0 則使用回傳值,否則執行扣減 $newStock = (int) ($stockReported ?? 0); if ($newStock <= 0 && $oldStock > 0) { $newStock = $oldStock - 1; } $finalStock = max(0, $newStock); $slot->update(['stock' => $finalStock]); // 紀錄庫存變化供前端顯示 $payloadUpdates['old_stock'] = $oldStock; $payloadUpdates['new_stock'] = $finalStock; ProcessStateLog::dispatch( $machine->id, $machine->company_id, "Remote dispense successful for slot :slot", 'info', ['slot' => $slotNo] ); } } } elseif ($command->command_type === 'reload_stock') { // 若同步失敗,自動 Rollback 回滾庫存 if ($status === 'failed' && isset($command->payload['slot_no'], $command->payload['old'])) { $slotNo = $command->payload['slot_no']; $slot = $machine->slots()->where('slot_no', $slotNo)->first(); if ($slot) { $oldData = $command->payload['old']; $slot->update([ 'stock' => $oldData['stock'], 'expiry_date' => $oldData['expiry_date'] ?? null, 'batch_no' => $oldData['batch_no'] ?? null, ]); Log::warning('MQTT CommandAck failed, rolled back inventory', [ 'serial_no' => $this->serialNo, 'slot_no' => $slotNo, 'rolled_back_to' => $oldData ]); } } } $command->update([ 'status' => $status, 'executed_at' => now(), 'payload' => array_merge($command->payload, $payloadUpdates), ]); Log::info("MQTT CommandAck processed", [ 'serial_no' => $this->serialNo, 'command_id' => $commandId, 'status' => $status, ]); } }