star-cloud/app/Jobs/Machine/ProcessCommandAck.php
sky121113 34f1e823f0 [FIX] 修正庫存異動類型缺失與統一交易扣庫邏輯
1. 補全 machine_stock_movements 資料表 type 欄位缺失的 sale 與 decommission 列舉值,修復 SQL 寫入失敗問題。
2. 重構 TransactionService:移除過時的 remote_dispense 判斷,將所有經由交易流程(含支付代碼 100)的出貨統一記錄為 sale 異動。
3. 更新 MachineService:將 B009 貨道同步時的移除動作類型由 adjustment 改為正確的 decommission。
4. 語系更新:於 zh_TW.json 與 en.json 補全硬體狀態碼 0205 (貨道有物) 的翻譯。
2026-05-11 17:13:41 +08:00

157 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Jobs\Machine;
use App\Models\Machine\Machine;
use App\Models\Machine\MachineStockMovement;
use App\Models\Machine\RemoteCommand;
use App\Services\Machine\MachineService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessCommandAck implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected string $serialNo;
protected array $payload;
/**
* 建立 Job 實例
*
* @param string $serialNo 機台序號(由 Go Gateway 從 Topic 路徑注入)
* @param array $payload MQTT Payload包含 command_id, result, stock
*/
public function __construct(string $serialNo, array $payload)
{
$this->serialNo = $serialNo;
$this->payload = $payload;
}
/**
* 處理 B055 出貨回報 ACK
* 原始邏輯移植自 MachineController::reportDispenseResult()
*/
public function handle(MachineService $machineService): 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') {
// 遠端出貨指令僅紀錄狀態,不再執行庫存預扣或回滾。
// 真正的庫存異動將由機台發送的 Transaction Finalize (結案) 流程處理。
$msgKey = ($status === 'success') ? "Remote dispense successful for slot :slot" : "Remote dispense failed for slot :slot";
$level = ($status === 'success') ? 'info' : 'error';
ProcessStateLog::dispatch(
$machine->id,
$machine->company_id,
$msgKey,
$level,
['slot' => $command->payload['slot_no'] ?? 'N/A']
);
}
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),
]);
// 記錄維護類指令到機台日誌 (MachineLog)
if (in_array($command->command_type, ['reboot', 'reboot_card', 'lock', 'unlock', 'checkout', 'change', 'reload_stock'], true)) {
$msgKey = $status === 'success' ? 'log.command.success' : 'log.command.failed';
$level = $status === 'success' ? 'info' : 'warning';
ProcessStateLog::dispatch(
$machine->id,
$machine->company_id,
$msgKey,
$level,
['type' => __("command.{$command->command_type}")]
);
}
Log::info("MQTT CommandAck processed", [
'serial_no' => $this->serialNo,
'command_id' => $commandId,
'status' => $status,
]);
}
}