diff --git a/.agents/skills/mqtt-communication-specs/SKILL.md b/.agents/skills/mqtt-communication-specs/SKILL.md
index 329b7fa..60ade80 100644
--- a/.agents/skills/mqtt-communication-specs/SKILL.md
+++ b/.agents/skills/mqtt-communication-specs/SKILL.md
@@ -279,6 +279,7 @@ Mqtt3Client client = MqttClient.builder()
- `change`: 遠端找零(夾帶 amount 參數)。
- `update_inventory`: 遠端即時修改庫存。
- `dispense`: 遠端出貨指令。
+- `update_ads`: 遠端廣告同步指令。
**`update_inventory` Payload 範例:**
```json
diff --git a/app/Http/Controllers/Admin/AdvertisementController.php b/app/Http/Controllers/Admin/AdvertisementController.php
index 76e589f..3b7ad67 100644
--- a/app/Http/Controllers/Admin/AdvertisementController.php
+++ b/app/Http/Controllers/Admin/AdvertisementController.php
@@ -282,4 +282,64 @@ class AdvertisementController extends AdminController
'message' => __('Assignment removed successfully.')
]);
}
+
+ /**
+ * 同步廣告至機台 (透過 MQTT 發送 update_ads 指令)
+ */
+ public function syncToMachine(Request $request, Machine $machine, \App\Services\Machine\MqttService $mqttService)
+ {
+ // 併行檢查:若有 pending 指令,超過 1 分鐘視為逾時可覆蓋
+ $pendingCommand = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
+ ->where('command_type', 'update_ads')
+ ->where('status', 'pending')
+ ->latest()
+ ->first();
+
+ if ($pendingCommand) {
+ $isTimedOut = $pendingCommand->created_at->lt(now()->subMinute());
+
+ if (!$isTimedOut) {
+ return response()->json([
+ 'success' => false,
+ 'message' => __('This machine has a pending command. Please wait.')
+ ], 422);
+ }
+
+ // 超過 1 分鐘:視為逾時,自動標記舊指令
+ $pendingCommand->update([
+ 'status' => 'timeout',
+ 'note' => __('Superseded by new command (Timeout)'),
+ ]);
+ }
+
+ $command = \App\Models\Machine\RemoteCommand::create([
+ 'machine_id' => $machine->id,
+ 'user_id' => auth()->id(),
+ 'command_type' => 'update_ads',
+ 'payload' => [],
+ 'status' => 'pending',
+ 'remark' => $request->filled('note') ? $request->input('note') : __('Manual Sync Ads'),
+ ]);
+
+ $success = $mqttService->pushCommand(
+ $machine->serial_no,
+ 'update_ads',
+ [],
+ (string) $command->id
+ );
+
+ if ($success) {
+ return response()->json([
+ 'success' => true,
+ 'message' => __('Sync command sent successfully.')
+ ]);
+ }
+
+ $command->update(['status' => 'failed', 'note' => 'MQTT push failed']);
+
+ return response()->json([
+ 'success' => false,
+ 'message' => __('Failed to send sync command.')
+ ], 500);
+ }
}
diff --git a/config/api-docs.php b/config/api-docs.php
index 5e8774e..66f01a4 100644
--- a/config/api-docs.php
+++ b/config/api-docs.php
@@ -674,6 +674,24 @@ return [
]
],
],
+ [
+ 'name' => '指令下發:廣告同步 (Update Ads)',
+ 'slug' => 'mqtt-command-update-ads',
+ 'action' => 'SUB',
+ 'topic' => 'machine/{serial_no}/command',
+ 'qos' => 1,
+ 'description' => '雲端主動下發「廣告同步」指令。指示機台重新拉取 B005 廣告清單。',
+ 'payload_parameters' => [
+ 'command' => ['type' => 'string', 'description' => '固定為 "update_ads"'],
+ 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
+ 'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'],
+ ],
+ 'payload_example' => [
+ 'command' => 'update_ads',
+ 'command_id' => '1122334456',
+ 'payload' => new \stdClass()
+ ],
+ ],
[
'name' => '指令下發:遠端找零 (Change)',
'slug' => 'mqtt-command-change',
diff --git a/lang/en.json b/lang/en.json
index 51a1f4c..6774098 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -1943,5 +1943,6 @@
"Total Orders": "Total Orders",
"Real-time fleet status and revenue monitoring": "Real-time fleet status and revenue monitoring",
"[StaffCard] Verification failed: :uid": "[StaffCard] Verification failed: :uid",
- "[PickupCode] Verification failed: :code": "[PickupCode] Verification failed: :code"
+ "[PickupCode] Verification failed: :code": "[PickupCode] Verification failed: :code",
+ "Sync Ads": "Sync Ads"
}
\ No newline at end of file
diff --git a/lang/ja.json b/lang/ja.json
index c98001b..cc55e6c 100644
--- a/lang/ja.json
+++ b/lang/ja.json
@@ -1942,5 +1942,6 @@
"Total Orders": "本日の合計注文",
"Real-time fleet status and revenue monitoring": "フリート全体のリアルタイムステータスと収益監視",
"[StaffCard] Verification failed: :uid": "[スタッフカード] 認証失敗: :uid",
- "[PickupCode] Verification failed: :code": "[受取コード] 認証失敗: :code"
+ "[PickupCode] Verification failed: :code": "[受取コード] 認証失敗: :code",
+ "Sync Ads": "広告を同期"
}
\ No newline at end of file
diff --git a/lang/zh_TW.json b/lang/zh_TW.json
index 163e489..4da5ba6 100644
--- a/lang/zh_TW.json
+++ b/lang/zh_TW.json
@@ -1591,6 +1591,7 @@
"This is a system administrator role. Its name is locked to ensure system stability.": "這是系統管理員角色,名稱已鎖定以確保系統穩定性。",
"This role belongs to another company and cannot be assigned.": "此角色屬於其他公司,無法指派。",
"This slot has a pending command. Please wait.": "此貨道已有指令正在執行,請稍後。",
+ "This machine has a pending command. Please wait.": "此機台已有指令正在執行,請稍後。",
"This slot has a pending update. Please wait for the previous command to complete.": "此貨道已有更新指令在執行中,請等候前一個指令完成。",
"This slot is syncing with the machine. Please wait.": "此貨道正在與機台同步中,請稍候。",
"Ticket": "憑證",
@@ -1943,5 +1944,10 @@
"Total Orders": "今日訂單",
"Real-time fleet status and revenue monitoring": "全機台即時狀態與營收監控",
"[StaffCard] Verification failed: :uid": "[員工卡] 驗證失敗: :uid",
- "[PickupCode] Verification failed: :code": "[取貨碼] 驗證失敗: :code"
+ "[PickupCode] Verification failed: :code": "[取貨碼] 驗證失敗: :code",
+ "Sync to Machine": "同步廣告至機台",
+ "Manual Sync Ads": "手動同步機台廣告",
+ "Sync command sent successfully.": "同步指令已成功送出",
+ "Failed to send sync command.": "無法送出同步指令",
+ "Sync Ads": "同步廣告"
}
\ No newline at end of file
diff --git a/resources/views/admin/ads/index.blade.php b/resources/views/admin/ads/index.blade.php
index 42f3bf6..ede43b7 100644
--- a/resources/views/admin/ads/index.blade.php
+++ b/resources/views/admin/ads/index.blade.php
@@ -22,7 +22,8 @@ $baseRoute = 'admin.data-config.advertisements';
"getMachineAds": "{{ route($baseRoute . ".machine.get", ":id") }}",
"assign": "{{ route($baseRoute . ".assign") }}",
"reorder": "{{ route($baseRoute . ".assignments.reorder") }}",
- "removeAssignment": "{{ route($baseRoute . ".assignment.remove", ":id") }}"
+ "removeAssignment": "{{ route($baseRoute . ".assignment.remove", ":id") }}",
+ "syncToMachine": "{{ route($baseRoute . ".machine.sync", ":id") }}"
}'>
{{ __('Please confirm the details below') }}
+