[FEAT] 遠端指令中心優化與規格同步
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m11s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m11s
1. 實作遠端指令去重機制 (Supersede):避免重複下達相同待執行指令。 2. 修正遠端指令發送後的 Toast 提示邏輯,確保頁面跳轉後正確顯示回饋。 3. 增加 RemoteCommand 操作者 (user_id) 紀錄與狀態列舉擴充 (superseded)。 4. 修復機台列表「最後頁面」欄位對照錯誤,同步更新 Machine Model 與 API 規格。 5. 優化遠端指令中心 UI:放大卡片字體、調整側面欄間距,符合極簡奢華風規範。 6. 更新 API 技術規格書 (SKILL.md) 與 config/api-docs.php,補全所有機台代碼 (66-611) 與指令。 7. 補全繁體中文、英文、日文多語系翻譯檔案。
This commit is contained in:
@@ -131,7 +131,9 @@ class MachineController extends AdminController
|
||||
'batch_no' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$this->machineService->updateSlot($machine, $validated);
|
||||
$this->machineService->updateSlot($machine, $validated, auth()->id());
|
||||
|
||||
session()->flash('success', __('Slot updated successfully.'));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
@@ -15,18 +15,32 @@ class RemoteController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$machines = Machine::withCount(['slots'])->orderBy('name')->get();
|
||||
$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();
|
||||
|
||||
if ($request->has('machine_id')) {
|
||||
$selectedMachine = Machine::with(['slots.product', 'commands' => function($query) {
|
||||
$query->latest()->limit(10);
|
||||
$query->where('command_type', '!=', 'reload_stock')
|
||||
->latest()
|
||||
->limit(10);
|
||||
}])->find($request->machine_id);
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'machine' => $selectedMachine,
|
||||
'commands' => $selectedMachine ? $selectedMachine->commands : []
|
||||
]);
|
||||
}
|
||||
|
||||
return view('admin.remote.index', [
|
||||
'machines' => $machines,
|
||||
'selectedMachine' => $selectedMachine,
|
||||
'history' => $history,
|
||||
'title' => __('Remote Command Center'),
|
||||
'subtitle' => __('Execute maintenance and operational commands remotely')
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -50,15 +64,35 @@ class RemoteController extends Controller
|
||||
$payload['slot_no'] = $validated['slot_no'];
|
||||
}
|
||||
|
||||
// 指令去重:將同機台、同類型的 pending 指令標記為「已取代」
|
||||
RemoteCommand::where('machine_id', $validated['machine_id'])
|
||||
->where('command_type', $validated['command_type'])
|
||||
->where('status', 'pending')
|
||||
->update([
|
||||
'status' => 'superseded',
|
||||
'note' => __('Superseded by new command'),
|
||||
'executed_at' => now(),
|
||||
]);
|
||||
|
||||
RemoteCommand::create([
|
||||
'machine_id' => $validated['machine_id'],
|
||||
'user_id' => auth()->id(),
|
||||
'command_type' => $validated['command_type'],
|
||||
'payload' => $payload,
|
||||
'status' => 'pending',
|
||||
'note' => $validated['note'] ?? null,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Command has been queued successfully.'));
|
||||
session()->flash('success', __('Command has been queued successfully.'));
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('Command has been queued successfully.')
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,17 +104,31 @@ class RemoteController extends Controller
|
||||
'slots as slots_count',
|
||||
'slots as low_stock_count' => function ($query) {
|
||||
$query->where('stock', '<=', 5);
|
||||
},
|
||||
'slots as expiring_soon_count' => function ($query) {
|
||||
$query->whereNotNull('expiry_date')
|
||||
->where('expiry_date', '<=', now()->addDays(7))
|
||||
->where('expiry_date', '>=', now()->startOfDay());
|
||||
}
|
||||
])->orderBy('name')->get();
|
||||
])->orderBy('last_heartbeat_at', 'desc')->orderBy('id', 'desc')->get();
|
||||
|
||||
$history = RemoteCommand::where('command_type', 'reload_stock')->with(['machine', 'user'])->latest()->limit(50)->get();
|
||||
|
||||
$selectedMachine = null;
|
||||
if ($request->has('machine_id')) {
|
||||
$selectedMachine = Machine::with('slots.product')->find($request->machine_id);
|
||||
$selectedMachine = Machine::with(['slots.product', 'commands' => function($query) {
|
||||
$query->where('command_type', 'reload_stock')
|
||||
->latest()
|
||||
->limit(50);
|
||||
}])->find($request->machine_id);
|
||||
}
|
||||
|
||||
return view('admin.remote.stock', [
|
||||
'machines' => $machines,
|
||||
'selectedMachine' => $selectedMachine,
|
||||
'history' => $history,
|
||||
'title' => __('Stock & Expiry Management'),
|
||||
'subtitle' => __('Real-time monitoring and adjustment of cargo lane inventory and expiration dates')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,12 @@ class MachineController extends Controller
|
||||
{
|
||||
$machine = $request->get('machine');
|
||||
$slots = $machine->slots()->with('product')->get();
|
||||
|
||||
// 自動轉 Success: 若機台來撈 B017,代表之前的 reload_stock 指令已成功被機台響應
|
||||
\App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
|
||||
->where('command_type', 'reload_stock')
|
||||
->where('status', 'sent')
|
||||
->update(['status' => 'success', 'executed_at' => now()]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
@@ -106,6 +106,11 @@ class Machine extends Model
|
||||
return $this->hasMany(MachineSlot::class);
|
||||
}
|
||||
|
||||
public function commands()
|
||||
{
|
||||
return $this->hasMany(RemoteCommand::class);
|
||||
}
|
||||
|
||||
public function machineModel()
|
||||
{
|
||||
return $this->belongsTo(MachineModel::class);
|
||||
@@ -133,21 +138,20 @@ class Machine extends Model
|
||||
'3' => 'Admin Page',
|
||||
'4' => 'Replenishment Page',
|
||||
'5' => 'Tutorial Page',
|
||||
'60' => 'Purchasing',
|
||||
'61' => 'Locked Page',
|
||||
'62' => 'Dispense Failed',
|
||||
'301' => 'Slot Test',
|
||||
'302' => 'Slot Test',
|
||||
'401' => 'Payment Selection',
|
||||
'402' => 'Waiting for Payment',
|
||||
'403' => 'Dispensing',
|
||||
'404' => 'Receipt Printing',
|
||||
'601' => 'Pass Code',
|
||||
'602' => 'Pickup Code',
|
||||
'603' => 'Message Display',
|
||||
'604' => 'Cancel Purchase',
|
||||
'605' => 'Purchase Finished',
|
||||
'611' => 'Welcome Gift Status',
|
||||
'6' => 'Purchasing',
|
||||
'7' => 'Locked Page',
|
||||
'60' => 'Dispense Success',
|
||||
'61' => 'Slot Test',
|
||||
'62' => 'Payment Selection',
|
||||
'63' => 'Waiting for Payment',
|
||||
'64' => 'Dispensing',
|
||||
'65' => 'Receipt Printing',
|
||||
'66' => 'Pass Code',
|
||||
'67' => 'Pickup Code',
|
||||
'68' => 'Message Display',
|
||||
'69' => 'Cancel Purchase',
|
||||
'610' => 'Purchase Finished',
|
||||
'611' => 'Welcome Gift',
|
||||
'612' => 'Dispense Failed',
|
||||
];
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class RemoteCommand extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'machine_id',
|
||||
'user_id',
|
||||
'command_type',
|
||||
'payload',
|
||||
'status',
|
||||
@@ -28,6 +29,11 @@ class RemoteCommand extends Model
|
||||
return $this->belongsTo(Machine::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for pending commands
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services\Machine;
|
||||
|
||||
use App\Models\Machine\Machine;
|
||||
use App\Models\Machine\MachineLog;
|
||||
use App\Models\Machine\RemoteCommand;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@@ -96,17 +97,25 @@ class MachineService
|
||||
*
|
||||
* @param Machine $machine
|
||||
* @param array $data
|
||||
* @param int|null $userId
|
||||
* @return void
|
||||
*/
|
||||
public function updateSlot(Machine $machine, array $data): void
|
||||
public function updateSlot(Machine $machine, array $data, ?int $userId = null): void
|
||||
{
|
||||
DB::transaction(function () use ($machine, $data) {
|
||||
DB::transaction(function () use ($machine, $data, $userId) {
|
||||
$slotNo = $data['slot_no'];
|
||||
$stock = $data['stock'] ?? null;
|
||||
$expiryDate = $data['expiry_date'] ?? null;
|
||||
$batchNo = $data['batch_no'] ?? null;
|
||||
$slot = $machine->slots()->where('slot_no', $slotNo)->firstOrFail();
|
||||
|
||||
// 紀錄舊數據以供 Payload 使用
|
||||
$oldData = [
|
||||
'stock' => $slot->stock,
|
||||
'expiry_date' => $slot->expiry_date ? Carbon::parse($slot->expiry_date)->toDateString() : null,
|
||||
'batch_no' => $slot->batch_no,
|
||||
];
|
||||
|
||||
$updateData = [
|
||||
'expiry_date' => $expiryDate,
|
||||
'batch_no' => $batchNo,
|
||||
@@ -114,6 +123,33 @@ class MachineService
|
||||
if ($stock !== null) $updateData['stock'] = (int)$stock;
|
||||
|
||||
$slot->update($updateData);
|
||||
|
||||
// 指令去重:將該機台所有尚未領取的舊庫存同步指令標記為「已取代」
|
||||
RemoteCommand::where('machine_id', $machine->id)
|
||||
->where('command_type', 'reload_stock')
|
||||
->where('status', 'pending')
|
||||
->update([
|
||||
'status' => 'superseded',
|
||||
'note' => __('Superseded by new adjustment'),
|
||||
'executed_at' => now(),
|
||||
]);
|
||||
|
||||
// 建立遠端指令紀錄 (Unified Command Concept)
|
||||
RemoteCommand::create([
|
||||
'machine_id' => $machine->id,
|
||||
'user_id' => $userId,
|
||||
'command_type' => 'reload_stock',
|
||||
'status' => 'pending',
|
||||
'payload' => [
|
||||
'slot_no' => $slotNo,
|
||||
'old' => $oldData,
|
||||
'new' => [
|
||||
'stock' => $stock !== null ? (int)$stock : $oldData['stock'],
|
||||
'expiry_date' => $expiryDate ?: null,
|
||||
'batch_no' => $batchNo ?: null,
|
||||
]
|
||||
]
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user