1. 修改 ProcessCommandAck.php,將「同步商品」與「同步廣告」指令加入日誌紀錄白名單,確保同步結果正確寫入機台日誌。 2. 在 MachineController 新增 temperatureAjax API,從機台日誌中提取歷史溫度數據供圖表使用。 3. 在 web.php 註冊溫度數據 AJAX 查詢路由。 4. 在機台管理頁面 (index.blade.php) 的日誌面板中整合 ApexCharts,並於「機台狀態」分頁內嵌入溫度趨勢曲線圖。 5. 更新 Alpine.js 邏輯,實現圖表與日期篩選的同步連動,並優化面板展開時的圖表渲染。 6. 新增繁體中文、英文、日文的「溫度趨勢」翻譯標籤,並修復語言檔中的重複 key 與翻譯內容。
271 lines
8.3 KiB
PHP
271 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Models\Machine\Machine;
|
|
use App\Services\Machine\MachineService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
class MachineController extends AdminController
|
|
{
|
|
public function __construct(protected MachineService $machineService)
|
|
{
|
|
}
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$per_page = $request->input('per_page', 10);
|
|
|
|
$query = Machine::query();
|
|
|
|
// 搜尋:名稱或序號
|
|
if ($search = $request->input('search')) {
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('serial_no', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 預加載統計資料
|
|
$machines = $query->orderBy("last_heartbeat_at", "desc")
|
|
->orderBy("id", "desc")
|
|
->paginate($per_page)
|
|
->withQueryString();
|
|
|
|
return view('admin.machines.index', compact('machines'));
|
|
}
|
|
|
|
/**
|
|
* 更新機台基本資訊 (目前僅名稱)
|
|
*/
|
|
public function update(Request $request, Machine $machine)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
]);
|
|
|
|
$machine->update($validated);
|
|
|
|
return redirect()->route('admin.machines.index')
|
|
->with('success', __('Machine updated successfully.'));
|
|
}
|
|
|
|
/**
|
|
* 顯示特定機台的日誌與詳細資訊
|
|
*/
|
|
public function show(int $id): View
|
|
{
|
|
$machine = Machine::with([
|
|
'logs' => function ($query) {
|
|
$query->latest()->limit(50);
|
|
}
|
|
])->findOrFail($id);
|
|
|
|
return view('admin.machines.show', compact('machine'));
|
|
}
|
|
|
|
|
|
/**
|
|
* AJAX: 取得機台抽屜面板所需的歷程日誌
|
|
*/
|
|
public function logsAjax(Request $request, Machine $machine)
|
|
{
|
|
$per_page = $request->input('per_page', 20);
|
|
|
|
$startDate = $request->get('start_date');
|
|
$endDate = $request->get('end_date');
|
|
|
|
$logs = $machine->logs()
|
|
->when($request->level, function ($query, $level) {
|
|
return $query->where('level', $level);
|
|
})
|
|
->when($startDate, function ($query, $start) {
|
|
return $query->where('created_at', '>=', str_replace('T', ' ', $start));
|
|
})
|
|
->when($endDate, function ($query, $end) {
|
|
return $query->where('created_at', '<=', str_replace('T', ' ', $end));
|
|
})
|
|
->when($request->type, function ($query, $type) {
|
|
return $query->where('type', $type);
|
|
})
|
|
->latest()
|
|
->paginate($per_page);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $logs->items(),
|
|
'pagination' => [
|
|
'total' => $logs->total(),
|
|
'current_page' => $logs->currentPage(),
|
|
'last_page' => $logs->lastPage(),
|
|
]
|
|
]);
|
|
}
|
|
|
|
|
|
/**
|
|
* 機台使用率統計
|
|
*/
|
|
public function utilization(Request $request): View
|
|
{
|
|
// 取得當前使用者有權限的所有機台 (已透過 Global Scope 過濾)
|
|
$machines = Machine::all();
|
|
|
|
$date = $request->get('date', now()->toDateString());
|
|
$service = app(\App\Services\Machine\MachineService::class);
|
|
$fleetStats = $service->getFleetStats($date);
|
|
|
|
return view('admin.machines.utilization', [
|
|
'machines' => $machines,
|
|
'fleetStats' => $fleetStats,
|
|
'compactMachines' => $machines->map(fn($m) => [
|
|
'id' => $m->id,
|
|
'name' => $m->name,
|
|
'serial_no' => $m->serial_no,
|
|
'status' => $m->status
|
|
])->values()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AJAX: 取得機台所有貨道資訊 (供效期管理視覺化圖表使用)
|
|
*/
|
|
public function slotsAjax(Machine $machine)
|
|
{
|
|
$slots = $machine->slots()
|
|
->with('product:id,name,image_url')
|
|
->orderByRaw('CAST(slot_no AS UNSIGNED) ASC')
|
|
->get();
|
|
|
|
// 取得該機台目前所有等待中的庫存更新指令 (Pending Commands)
|
|
$pendingSlotNos = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
|
|
->where('command_type', 'reload_stock')
|
|
->where('status', 'pending')
|
|
->get()
|
|
->pluck('payload.slot_no')
|
|
->flatten()
|
|
->toArray();
|
|
|
|
// 將待處理狀態注入貨道物件
|
|
$slots->each(function ($slot) use ($pendingSlotNos) {
|
|
$slot->is_pending = in_array((int) $slot->slot_no, $pendingSlotNos);
|
|
});
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'machine' => $machine->only(['id', 'name', 'serial_no']),
|
|
'slots' => $slots
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AJAX: 更新貨道資訊 (庫存、效期、批號)
|
|
*/
|
|
public function updateSlotExpiry(Request $request, Machine $machine)
|
|
{
|
|
// 確保庫存數值為整數,處理如 "01" 轉為 1 的情況
|
|
if ($request->has('stock') && !is_null($request->stock) && $request->stock !== '') {
|
|
$request->merge(['stock' => (int) $request->stock]);
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'slot_no' => 'required|integer',
|
|
'stock' => 'nullable|integer|min:0',
|
|
'expiry_date' => 'nullable|date',
|
|
'batch_no' => 'nullable|string|max:50',
|
|
]);
|
|
|
|
try {
|
|
$this->machineService->updateSlot($machine, $validated, auth()->id());
|
|
|
|
session()->flash('success', __('Slot updated successfully.'));
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => __('Slot updated successfully.')
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
], 422);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 取得機台統計數據 (AJAX)
|
|
*/
|
|
public function utilizationData(Request $request, $id = null)
|
|
{
|
|
$date = $request->get('date', now()->toDateString());
|
|
$service = app(\App\Services\Machine\MachineService::class);
|
|
|
|
if ($id) {
|
|
$machine = Machine::findOrFail($id);
|
|
$stats = $service->getUtilizationStats($machine, $date);
|
|
} else {
|
|
$stats = $service->getFleetStats($date);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $stats
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 機台維護紀錄 (開發中)
|
|
*/
|
|
public function maintenance(Request $request): View
|
|
{
|
|
return view('admin.machines.index', ['machines' => Machine::paginate(1)]); // Placeholder
|
|
}
|
|
|
|
/**
|
|
* AJAX: 清除機台所有未解決的警告與異常 (手動排除)
|
|
*/
|
|
public function resolveLogs(Machine $machine)
|
|
{
|
|
$machine->logs()
|
|
->where('is_resolved', false)
|
|
->whereIn('level', ['error', 'warning'])
|
|
->update(['is_resolved' => true]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => __('All issues marked as resolved.')
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AJAX: 取得機台溫度歷史紀錄 (供圖表使用)
|
|
*/
|
|
public function temperatureAjax(Request $request, Machine $machine)
|
|
{
|
|
$startDate = $request->get('start_date');
|
|
$endDate = $request->get('end_date');
|
|
|
|
$logs = $machine->logs()
|
|
->where('message', 'Temperature reported: :temp°C')
|
|
->when($startDate, function ($query, $start) {
|
|
return $query->where('created_at', '>=', str_replace('T', ' ', $start));
|
|
})
|
|
->when($endDate, function ($query, $end) {
|
|
return $query->where('created_at', '<=', str_replace('T', ' ', $end));
|
|
})
|
|
->oldest()
|
|
->get();
|
|
|
|
$chartData = [
|
|
'labels' => $logs->pluck('created_at')->map(fn($date) => $date->format('Y-m-d H:i:s'))->toArray(),
|
|
'values' => $logs->map(fn($log) => (int) ($log->context['temp'] ?? 0))->toArray(),
|
|
];
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $chartData
|
|
]);
|
|
}
|
|
}
|