[FIX] 修正 IoT 管理介面分頁持久化與實作 B055 遠端出貨 API
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 3m58s

1. 視圖持久化優化:將 index/stock 視圖切換從 x-if 改為 x-show,解決 HSSelect 在切換分頁後失效的問題。
2. 變數存取安全:為所有 selectedMachine 屬性存取補上可選鏈 (?.) 保護,防止 x-show 模式下的 null 錯誤。
3. UI 體驗提升:放大「指令中心」與「庫存管理」歷史紀錄的時間字體至 15px 並加粗顯示。
4. API 功能實作:在 routes/api.php 與 MachineController 中實作 B055 遠端指令出貨控制端點。
5. 文件同步:更新 SKILL.md 技術規格文件,明確定義 B055 的請求與回應格式。
6. 樣式調整:修改 app.css 優化奢華風 UI 字體與間距細節。
This commit is contained in:
2026-04-15 10:54:58 +08:00
parent f49938d1a7
commit 376f43fa3a
8 changed files with 1404 additions and 797 deletions

View File

@@ -17,7 +17,49 @@ class RemoteController extends Controller
{
$machines = Machine::withCount(['slots'])->orderBy('last_heartbeat_at', 'desc')->orderBy('id', 'desc')->get();
$selectedMachine = null;
$history = RemoteCommand::where('command_type', '!=', 'reload_stock')->with(['machine', 'user'])->latest()->limit(50)->get();
$historyQuery = RemoteCommand::where('command_type', '!=', 'reload_stock')
->with(['machine', 'user']);
if ($request->filled('search')) {
$search = $request->input('search');
$historyQuery->where(function($q) use ($search) {
$q->whereHas('machine', function($mq) use ($search) {
$mq->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
})->orWhereHas('user', function($uq) use ($search) {
$uq->where('name', 'like', "%{$search}%");
});
});
}
// 時間區間過濾 (created_at)
if ($request->filled('start_date') || $request->filled('end_date')) {
try {
if ($request->filled('start_date')) {
$start = \Illuminate\Support\Carbon::parse($request->input('start_date'));
$historyQuery->where('created_at', '>=', $start);
}
if ($request->filled('end_date')) {
$end = \Illuminate\Support\Carbon::parse($request->input('end_date'));
$historyQuery->where('created_at', '<=', $end);
}
} catch (\Exception $e) {
// 忽略解析錯誤
}
}
// 指令類型過濾
if ($request->filled('command_type')) {
$historyQuery->where('command_type', $request->input('command_type'));
}
// 狀態過濾
if ($request->filled('status')) {
$historyQuery->where('status', $request->input('status'));
}
$history = $historyQuery->latest()->paginate($request->input('per_page', 10));
if ($request->has('machine_id')) {
$selectedMachine = Machine::with(['slots.product', 'commands' => function($query) {
@@ -112,7 +154,44 @@ class RemoteController extends Controller
}
])->orderBy('last_heartbeat_at', 'desc')->orderBy('id', 'desc')->get();
$history = RemoteCommand::where('command_type', 'reload_stock')->with(['machine', 'user'])->latest()->limit(50)->get();
$historyQuery = RemoteCommand::with(['machine', 'user']);
$historyQuery->where('command_type', 'reload_stock');
if ($request->filled('search')) {
$search = $request->input('search');
$historyQuery->where(function($q) use ($search) {
$q->whereHas('machine', function($mq) use ($search) {
$mq->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
})->orWhereHas('user', function($uq) use ($search) {
$uq->where('name', 'like', "%{$search}%");
});
});
}
// 時間區間過濾 (created_at)
if ($request->filled('start_date') || $request->filled('end_date')) {
try {
if ($request->filled('start_date')) {
$start = \Illuminate\Support\Carbon::parse($request->input('start_date'));
$historyQuery->where('created_at', '>=', $start);
}
if ($request->filled('end_date')) {
$end = \Illuminate\Support\Carbon::parse($request->input('end_date'));
$historyQuery->where('created_at', '<=', $end);
}
} catch (\Exception $e) {
// 忽略解析錯誤
}
}
// 狀態過濾
if ($request->filled('status')) {
$historyQuery->where('status', $request->input('status'));
}
$history = $historyQuery->latest()->paginate($request->input('per_page', 10));
$selectedMachine = null;
if ($request->has('machine_id')) {

View File

@@ -145,6 +145,114 @@ class MachineController extends Controller
'status' => $status
], 202); // 202 Accepted
}
/**
* B055: Get Remote Dispense Queue (POST/GET)
* 用於獲取雲端下發的遠端出貨指令詳情。
*/
public function getDispenseQueue(Request $request)
{
$machine = $request->get('machine');
// 查找該機台狀態為「已發送 (sent)」且類型為「出貨 (dispense)」的最新指令
$command = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
->where('command_type', 'dispense')
->where('status', 'sent')
->latest()
->first();
if (!$command) {
return response()->json([
'success' => true,
'code' => 200,
'data' => []
]);
}
// 映射 Java APP 預期格式: res1 = ID, res2 = SlotNo
return response()->json([
'success' => true,
'code' => 200,
'data' => [
[
'res1' => (string) $command->id,
'res2' => (string) ($command->payload['slot_no'] ?? ''),
]
]
]);
}
/**
* B055: Report Remote Dispense Result (PUT)
* 用於機台回報遠端出貨執行的最終結果。
*/
public function reportDispenseResult(Request $request, \App\Services\Machine\MachineService $machineService)
{
$commandId = $request->input('id');
$type = $request->input('type'); // 通常為 0
$stockReported = $request->input('stock'); // 機台回報的剩餘庫存
$command = \App\Models\Machine\RemoteCommand::find($commandId);
if (!$command) {
return response()->json([
'success' => false,
'code' => 404,
'message' => 'Command not found'
], 404);
}
// 更新指令狀態 (0 代表成功)
$status = ($type == '0') ? 'success' : 'failed';
$payloadUpdates = [
'reported_stock' => $stockReported,
'report_type' => $type
];
// 若成功,連動更新貨道庫存
if ($status === 'success' && isset($command->payload['slot_no'])) {
$machine = $command->machine;
$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 {$slotNo}",
'info'
);
}
}
$command->update([
'status' => $status,
'executed_at' => now(),
'payload' => array_merge($command->payload, $payloadUpdates)
]);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Result reported'
]);
}
/**
* B018: Record Machine Restock/Setup Report (Asynchronous)