From 9185382565ccb215195613e572b32ef6b8dbf84c Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 09:14:50 +0800 Subject: [PATCH 1/6] ci: make prod deploy avoid container recreate --- .gitea/workflows/deploy-prod.yaml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy-prod.yaml b/.gitea/workflows/deploy-prod.yaml index 262200b..97aaa36 100644 --- a/.gitea/workflows/deploy-prod.yaml +++ b/.gitea/workflows/deploy-prod.yaml @@ -31,9 +31,21 @@ jobs: docker run --rm -v /home/twsystem1003/star-cloud:/target busybox mkdir -p /target/docker/emqx cat docker/emqx/acl.conf | docker run -i --rm -v /home/twsystem1003/star-cloud:/target busybox sh -c "cat > /target/docker/emqx/acl.conf" - - name: Step 1 - Build & Deploy Containers + - name: Step 1 - Ensure Existing Containers Are Running run: | - WWWGROUP=1001 WWWUSER=1001 docker-compose up -d --build + for container in \ + star-cloud-mysql \ + star-cloud-redis \ + star-cloud-emqx + do + if ! docker inspect "${container}" >/dev/null 2>&1; then + echo ">>> Required container is missing: ${container}" + exit 1 + fi + + echo ">>> Ensuring ${container} is running..." + docker start "${container}" >/dev/null + done for container in star-cloud-mysql star-cloud-redis star-cloud-emqx; do echo ">>> Waiting for ${container} to become healthy..." @@ -56,6 +68,21 @@ jobs: done done + for container in \ + star-cloud-laravel \ + star-cloud-queue \ + star-cloud-mqtt-worker \ + star-cloud-mqtt-gateway + do + if ! docker inspect "${container}" >/dev/null 2>&1; then + echo ">>> Required container is missing: ${container}" + exit 1 + fi + + echo ">>> Ensuring ${container} is running..." + docker start "${container}" >/dev/null + done + - name: Step 2 - Deploy Code run: | echo ">>> Syncing code to star-cloud-laravel..." From 953581db38e5acb7203ed8f57371991a1f96023a Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 11:45:48 +0800 Subject: [PATCH 2/6] =?UTF-8?q?[FEAT]=20=E6=96=B0=E5=A2=9E=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E6=BA=AB=E5=BA=A6=E4=B8=8A=E9=99=90=E7=9B=A3=E6=B8=AC?= =?UTF-8?q?=E8=88=87=E9=81=A0=E7=AB=AF=E9=A2=A8=E6=89=87=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 資料庫移轉:新增機台環境溫度、環境溫度上限設定,以及是否啟用環境溫度監測等欄位。 2. 後端核心:更新 Machine Model 與 MachineController,實作機台環境溫度設定的更新與歷史日誌 Ajax API。 3. MQTT 佇列:於 ListenMqttQueue 指令與 ProcessAmbientTemp Job 實作機台上報環境溫度的接收與寫入,並加入 Redis 快取防止重複日誌洗版。 4. 遠端指令:擴充 RemoteController 並於指令中心實作 'fanon'、'fanoff'、'fanauto' 與 'ambient_temp_limit' (環境溫度上限) 指令下發。 5. 前端頁面:於機台列表加入「環境溫度上限」微調計數器 UI,並整合 ApexCharts 折線圖展示溫度趨勢;更新遠端控制中心按鈕與歷史篩選器。 6. 多語系:同步更新並完美對齊 'zh_TW.json'、'en.json' 與 'ja.json',遵循字母 ksort 排序與斜線不逸出規範。 --- app/Console/Commands/ListenMqttQueue.php | 4 + .../MachineSettingController.php | 3 + .../Controllers/Admin/MachineController.php | 69 ++++++ .../Controllers/Admin/RemoteController.php | 2 +- app/Jobs/Machine/ProcessAmbientTemp.php | 94 ++++++++ app/Models/Machine/Machine.php | 6 + config/api-docs.php | 24 +- ...perature_and_setting_to_machines_table.php | 33 +++ ...p_monitoring_enabled_to_machines_table.php | 30 +++ lang/en.json | 18 ++ lang/ja.json | 18 ++ lang/zh_TW.json | 18 ++ .../basic-settings/machines/index.blade.php | 26 ++- .../partials/tab-system-settings.blade.php | 2 + .../views/admin/machines/index.blade.php | 212 +++++++++++++++++- resources/views/admin/remote/index.blade.php | 74 ++++++ .../partials/tab-history-index.blade.php | 4 + routes/web.php | 1 + 18 files changed, 623 insertions(+), 15 deletions(-) create mode 100644 app/Jobs/Machine/ProcessAmbientTemp.php create mode 100644 database/migrations/2026_06_04_100930_add_ambient_temperature_and_setting_to_machines_table.php create mode 100644 database/migrations/2026_06_04_110221_add_ambient_temp_monitoring_enabled_to_machines_table.php diff --git a/app/Console/Commands/ListenMqttQueue.php b/app/Console/Commands/ListenMqttQueue.php index 1b541f5..d5b6ddd 100644 --- a/app/Console/Commands/ListenMqttQueue.php +++ b/app/Console/Commands/ListenMqttQueue.php @@ -95,6 +95,10 @@ class ListenMqttQueue extends Command $finalPayload = is_array($payload) ? $payload : ['raw_data' => $payload]; ProcessHeartbeat::dispatch($serialNo, $finalPayload); break; + case 'ambient_temp': + $finalPayload = is_array($payload) ? $payload : ['temperature' => $payload]; + \App\Jobs\Machine\ProcessAmbientTemp::dispatch($serialNo, $finalPayload); + break; case 'status': ProcessStatus::dispatch($serialNo, $payload); break; diff --git a/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php b/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php index fa1dfc8..ad6a1b5 100644 --- a/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php +++ b/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php @@ -213,6 +213,7 @@ class MachineSettingController extends AdminController 'scan_pay_linepay_enabled' => 'boolean', 'shopping_cart_enabled' => 'boolean', 'cash_module_enabled' => 'boolean', + 'ambient_temp_monitoring_enabled' => 'boolean', 'machine_model_id' => 'required|exists:machine_models,id', 'payment_config_id' => 'nullable|exists:payment_configs,id', 'location' => 'nullable|string|max:255', @@ -333,6 +334,7 @@ class MachineSettingController extends AdminController 'welcome_gift_enabled', 'cash_module_enabled', 'member_system_enabled', + 'ambient_temp_monitoring_enabled', ]; $data = []; foreach ($allowedFields as $field) { @@ -361,6 +363,7 @@ class MachineSettingController extends AdminController 'welcome_gift_enabled', 'cash_module_enabled', 'member_system_enabled', + 'ambient_temp_monitoring_enabled', ]; if (!in_array($field, $allowedFields)) { diff --git a/app/Http/Controllers/Admin/MachineController.php b/app/Http/Controllers/Admin/MachineController.php index 4b6e017..235151f 100644 --- a/app/Http/Controllers/Admin/MachineController.php +++ b/app/Http/Controllers/Admin/MachineController.php @@ -43,10 +43,48 @@ class MachineController extends AdminController { $validated = $request->validate([ 'name' => 'required|string|max:255', + 'ambient_temp_setting' => 'nullable|integer', ]); $machine->update($validated); + if ($machine->wasChanged('ambient_temp_setting')) { + $newSetting = $machine->ambient_temp_setting; + + // 將先前未執行的同類型指令標記為 superseded (被取代) + \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id) + ->where('command_type', 'ambient_temp_limit') + ->where('status', 'pending') + ->update([ + 'status' => 'superseded', + 'remark' => __('Superseded by new command'), + 'executed_at' => now(), + ]); + + // 建立新指令 + $command = \App\Models\Machine\RemoteCommand::create([ + 'machine_id' => $machine->id, + 'user_id' => auth()->id() ?: 1, + 'command_type' => 'ambient_temp_limit', + 'payload' => [ + 'temperature' => $newSetting !== null ? (int)$newSetting : null + ], + 'status' => 'pending', + 'remark' => $newSetting !== null + ? __('Temperature reaches :temp degrees, the fan will turn on', ['temp' => $newSetting]) + : __('Disabled ambient temperature monitoring') + ]); + + // 發送 MQTT 指令 + $mqttService = app(\App\Services\Machine\MqttService::class); + $mqttService->pushCommand( + $machine->serial_no, + $command->command_type, + $command->payload, + (string)$command->id + ); + } + return redirect()->route('admin.machines.index') ->with('success', __('Machine updated successfully.')); } @@ -267,4 +305,35 @@ class MachineController extends AdminController 'data' => $chartData ]); } + + /** + * AJAX: 取得機台環境溫度歷史紀錄 (供圖表使用) + */ + public function ambientTemperatureAjax(Request $request, Machine $machine) + { + $startDate = $request->get('start_date'); + $endDate = $request->get('end_date'); + + $logs = $machine->logs() + ->where('type', 'ambient_temp') + ->where('message', 'Ambient 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 = $logs->map(fn($log) => [ + 'x' => $log->created_at->getTimestamp() * 1000, + 'y' => (int) ($log->context['temp'] ?? 0), + ])->values()->toArray(); + + return response()->json([ + 'success' => true, + 'data' => $chartData + ]); + } } diff --git a/app/Http/Controllers/Admin/RemoteController.php b/app/Http/Controllers/Admin/RemoteController.php index 13b832c..9fceafc 100644 --- a/app/Http/Controllers/Admin/RemoteController.php +++ b/app/Http/Controllers/Admin/RemoteController.php @@ -139,7 +139,7 @@ class RemoteController extends Controller { $validated = $request->validate([ 'machine_id' => 'required|exists:machines,id', - 'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense', + 'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense,fanon,fanoff,fanauto', 'amount' => 'nullable|integer|min:0', 'slot_no' => 'nullable|string', 'note' => 'nullable|string|max:255', diff --git a/app/Jobs/Machine/ProcessAmbientTemp.php b/app/Jobs/Machine/ProcessAmbientTemp.php new file mode 100644 index 0000000..83d9133 --- /dev/null +++ b/app/Jobs/Machine/ProcessAmbientTemp.php @@ -0,0 +1,94 @@ +serialNo = $serialNo; + $this->payload = (array) $payload; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $machine = Machine::withoutGlobalScopes()->where('serial_no', $this->serialNo)->first(); + + if (!$machine) { + Log::warning("MQTT Ambient Temp: Machine not found", ['serial_no' => $this->serialNo]); + return; + } + + // 環境溫度欄位更新 + // 支持機台傳送 payload 為 {"temperature": 25} 或 {"ambient_temp": 25} 等結構 + $temp = null; + if (isset($this->payload['temperature'])) { + $temp = (int) $this->payload['temperature']; + } elseif (isset($this->payload['ambient_temp'])) { + $temp = (int) $this->payload['ambient_temp']; + } elseif (isset($this->payload['raw_data'])) { + $temp = (int) $this->payload['raw_data']; + } + + if ($temp === null) { + Log::warning("MQTT Ambient Temp: Missing temperature field in payload", [ + 'serial_no' => $this->serialNo, + 'payload' => $this->payload + ]); + return; + } + + Log::debug("ProcessAmbientTemp: Ambient temperature reported for {$this->serialNo}: {$temp}"); + + $updateData = [ + 'ambient_temperature' => $temp, + ]; + + // 讀取最後一次記錄日誌的溫度與時間,避免頻繁寫入相同的溫度日誌 + $tempLogCacheKey = "machine:{$this->serialNo}:last_ambient_temp_log"; + $lastLogEntry = Cache::get($tempLogCacheKey); + + // 優先從快取取值,若無則從資料庫取 + $oldTemp = isset($lastLogEntry['value']) ? (int)$lastLogEntry['value'] : $machine->ambient_temperature; + + if ($temp !== (int)$oldTemp) { + Log::debug("ProcessAmbientTemp: Ambient temperature changed from {$oldTemp} to {$temp}. Triggering log."); + \App\Jobs\Machine\ProcessStateLog::dispatch( + $machine->id, + $machine->company_id, + "Ambient temperature reported: :temp°C", + 'info', + ['temp' => $temp], + 'ambient_temp' // type 欄位 + ); + } + + // 更新快取 (存活時間為 7 天) + Cache::put($tempLogCacheKey, [ + 'value' => $temp, + 'at' => now()->toDateTimeString() + ], 604800); + + $machine->update($updateData); + } +} diff --git a/app/Models/Machine/Machine.php b/app/Models/Machine/Machine.php index 526b1ba..378fcd6 100644 --- a/app/Models/Machine/Machine.php +++ b/app/Models/Machine/Machine.php @@ -70,6 +70,9 @@ class Machine extends Model 'current_page', 'door_status', 'temperature', + 'ambient_temperature', + 'ambient_temp_setting', + 'ambient_temp_monitoring_enabled', 'firmware_version', 'api_token', 'last_heartbeat_at', @@ -309,6 +312,9 @@ class Machine extends Model protected $casts = [ 'last_heartbeat_at' => 'datetime', 'temperature' => 'integer', + 'ambient_temperature' => 'integer', + 'ambient_temp_setting' => 'integer', + 'ambient_temp_monitoring_enabled' => 'boolean', 'welcome_gift_enabled' => 'boolean', 'is_spring_slot_1_10' => 'boolean', 'is_spring_slot_11_20' => 'boolean', diff --git a/config/api-docs.php b/config/api-docs.php index 190c65a..cd1c8de 100644 --- a/config/api-docs.php +++ b/config/api-docs.php @@ -773,7 +773,7 @@ return [ 'qos' => 1, 'description' => '雲端主動下發的系統級別控制指令。包含重啟、設備鎖定解鎖、購物車結帳等無額外參數的指令。', 'payload_parameters' => [ - 'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖), update_ads (廣告同步), update_products (商品資料同步)'], + 'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖), update_ads (廣告同步), update_products (商品資料同步), fanon (開啟風扇), fanoff (關閉風扇), fanauto (風扇自動控制)'], 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], 'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'], ], @@ -784,6 +784,28 @@ return [ ], ], + [ + 'name' => '指令下發:環境溫度上限 (Ambient Temperature Upper Limit)', + 'slug' => 'mqtt-command-ambient-temp-limit', + 'action' => 'SUB', + 'topic' => 'machine/{serial_no}/command', + 'qos' => 1, + 'description' => '雲端主動下發「環境溫度上限」指令。指示機台更新環境溫度開啟風扇的閾值設定。', + 'payload_parameters' => [ + 'command' => ['type' => 'string', 'description' => '固定為 "ambient_temp_limit"'], + 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], + 'payload' => ['type' => 'object', 'description' => '溫度設定參數'], + 'payload.temperature' => ['type' => 'integer', 'description' => '環境溫度上限閾值(可為 null 表示停用監測)'], + ], + 'payload_example' => [ + 'command' => 'ambient_temp_limit', + 'command_id' => '1122334455', + 'payload' => [ + 'temperature' => 28 + ] + ], + ], + [ 'name' => '指令執行回報 (Machine to Cloud)', 'slug' => 'mqtt-command-ack', diff --git a/database/migrations/2026_06_04_100930_add_ambient_temperature_and_setting_to_machines_table.php b/database/migrations/2026_06_04_100930_add_ambient_temperature_and_setting_to_machines_table.php new file mode 100644 index 0000000..affd750 --- /dev/null +++ b/database/migrations/2026_06_04_100930_add_ambient_temperature_and_setting_to_machines_table.php @@ -0,0 +1,33 @@ +integer('ambient_temperature')->nullable()->after('temperature')->comment('環境溫度'); + } + if (!Schema::hasColumn('machines', 'ambient_temp_setting')) { + $table->integer('ambient_temp_setting')->nullable()->after('ambient_temperature')->comment('環境溫度設定值'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('machines', function (Blueprint $table) { + $table->dropColumn(['ambient_temperature', 'ambient_temp_setting']); + }); + } +}; diff --git a/database/migrations/2026_06_04_110221_add_ambient_temp_monitoring_enabled_to_machines_table.php b/database/migrations/2026_06_04_110221_add_ambient_temp_monitoring_enabled_to_machines_table.php new file mode 100644 index 0000000..a37ea67 --- /dev/null +++ b/database/migrations/2026_06_04_110221_add_ambient_temp_monitoring_enabled_to_machines_table.php @@ -0,0 +1,30 @@ +boolean('ambient_temp_monitoring_enabled')->default(false)->after('ambient_temp_setting')->comment('是否啟用環境溫度監測'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('machines', function (Blueprint $table) { + $table->dropColumn('ambient_temp_monitoring_enabled'); + }); + } +}; diff --git a/lang/en.json b/lang/en.json index 0e8263f..7fb4709 100644 --- a/lang/en.json +++ b/lang/en.json @@ -120,6 +120,13 @@ "All Warehouses": "All Warehouses", "All issues marked as resolved.": "All issues marked as resolved.", "All slots are fully stocked": "All slots are fully stocked", + "Ambient Temperature": "Ambient Temperature", + "Ambient Temperature Log": "Ambient Temperature Log", + "Ambient Temperature Monitoring": "Ambient Temperature Monitoring", + "Ambient Temperature Setting": "Ambient Temperature Setting", + "Ambient Temperature Trend": "Ambient Temperature Trend", + "Ambient Temperature Upper Limit": "Ambient Temperature Upper Limit", + "Ambient temperature reported: :temp°C": "Ambient temperature reported: :temp°C", "Amount": "Amount", "Amount / Payment": "Amount / Payment", "Amount Discount": "Amount Discount", @@ -236,6 +243,7 @@ "CASH": "CASH", "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS", "Calculate Replenishment": "Calculate Replenishment", + "Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "Can be empty. If left blank, it means ambient temperature monitoring is disabled.", "Cancel": "Cancel", "Cancel Gift": "Cancel Gift", "Cancel Order": "Cancel Order", @@ -511,6 +519,7 @@ "Disable Product Confirmation": "Disable Product Confirmation", "Disable Warehouse": "Disable Warehouse", "Disabled": "Disabled", + "Disabled ambient temperature monitoring": "Disabled ambient temperature monitoring", "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?", "Discord Notifications": "Discord Notifications", "Discord Webhook URL": "Discord Webhook URL", @@ -632,6 +641,7 @@ "English": "English", "Ensure your account is using a long, random password to stay secure.": "Ensure your account is using a long, random password to stay secure.", "Enter ad material name": "Enter ad material name", + "Enter ambient temperature setting...": "Enter ambient temperature setting...", "Enter full address": "Enter full address", "Enter login ID": "Enter login ID", "Enter machine location": "Enter machine location", @@ -690,6 +700,10 @@ "Failed to send test notification.": "Failed to send test notification.", "Failed to send test notification. Please verify the Webhook URL.": "Failed to send test notification. Please verify the Webhook URL.", "Failed to update machine images: ": "Failed to update machine images: ", + "Fan Auto": "Fan Auto", + "Fan Controls": "Fan Controls", + "Fan Off": "Fan Off", + "Fan On": "Fan On", "Feature Settings": "Feature Settings", "Feature Toggles": "Feature Toggles", "Field": "Field", @@ -822,6 +836,7 @@ "Half Points Amount": "Half Points Amount", "Hardware & Network": "Hardware & Network", "Hardware & Slots": "Hardware & Slots", + "Hardware Peripheral Settings": "Hardware Peripheral Settings", "HashIV": "HashIV", "HashKey": "HashKey", "Heartbeat": "Heartbeat", @@ -1955,6 +1970,7 @@ "Temperature Limit": "Temperature Limit", "Temperature Restored": "Temperature Restored", "Temperature Trend": "Temperature Trend", + "Temperature reaches :temp degrees, the fan will turn on": "Temperature reaches :temp°C, the fan will turn on", "Temperature reported: :temp°C": "Temperature reported: :temp°C", "Temperature updated to :temp°C": "Temperature updated to :temp°C", "Tenant": "Tenant", @@ -2084,6 +2100,7 @@ "Update Product": "Update Product", "Update Settings": "Update Settings", "Update Welcome Gift": "Update Welcome Gift", + "Update ambient temperature setting to :temp°C": "Update ambient temperature setting to :temp°C", "Update existing role and permissions.": "Update existing role and permissions.", "Update failed": "Update failed", "Update identification for your asset": "Update identification for your asset", @@ -2098,6 +2115,7 @@ "Upload file or provide URL": "Upload file or provide URL", "Uploading new images will replace all existing images.": "Uploading new images will replace all existing images.", "Uploading...": "Uploading...", + "Upper limit; the fan will turn on when reached": "Upper limit; the fan will turn on when reached", "Usage Completed": "Usage Completed", "Usage Limit": "Usage Limit", "Usage Logs": "Usage Logs", diff --git a/lang/ja.json b/lang/ja.json index c04b0d0..9d75944 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -120,6 +120,13 @@ "All Warehouses": "すべての倉庫", "All issues marked as resolved.": "すべての問題が解決済みとしてマークされました。", "All slots are fully stocked": "すべてのスロットが補充完了しました", + "Ambient Temperature": "周囲温度", + "Ambient Temperature Log": "周囲温度ログ", + "Ambient Temperature Monitoring": "環境温度監視", + "Ambient Temperature Setting": "周囲温度設定", + "Ambient Temperature Trend": "周囲温度トレンド", + "Ambient Temperature Upper Limit": "環境温度上限", + "Ambient temperature reported: :temp°C": "環境温度受信: :temp°C", "Amount": "金額", "Amount / Payment": "金額 / 支払い", "Amount Discount": "金額割引", @@ -236,6 +243,7 @@ "CASH": "現金", "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "倉庫間の移動または機器からの返品を作成", "Calculate Replenishment": "補充量を計算", + "Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "空欄にすることができます。空欄の場合、周囲温度の監視は無効になります。", "Cancel": "キャンセル", "Cancel Gift": "ギフトをキャンセル", "Cancel Order": "注文キャンセル", @@ -511,6 +519,7 @@ "Disable Product Confirmation": "商品無効化確認", "Disable Warehouse": "倉庫無効化", "Disabled": "無効済み", + "Disabled ambient temperature monitoring": "環境温度監視を停止しました", "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "顧客を無効にすると配下の全アカウントも無効になります。続行しますか?", "Discord Notifications": "Discord通知", "Discord Webhook URL": "Discord Webhook URL", @@ -632,6 +641,7 @@ "English": "英語", "Ensure your account is using a long, random password to stay secure.": "安全を保つため、長くランダムなパスワードを使用してください。", "Enter ad material name": "広告素材名を入力", + "Enter ambient temperature setting...": "周囲温度設定を入力してください...", "Enter full address": "完全な住所を入力", "Enter login ID": "ログインIDを入力してください", "Enter machine location": "機器の設置場所を入力してください", @@ -690,6 +700,10 @@ "Failed to send test notification.": "テスト通知の送信に失敗しました。", "Failed to send test notification. Please verify the Webhook URL.": "テスト通知の送信に失敗しました。Webhook URLを確認してください。", "Failed to update machine images: ": "機器画像の更新に失敗しました: ", + "Fan Auto": "ファン自動", + "Fan Controls": "ファン制御", + "Fan Off": "ファン停止", + "Fan On": "ファン起動", "Feature Settings": "機能設定", "Feature Toggles": "機能切り替え", "Field": "Field", @@ -822,6 +836,7 @@ "Half Points Amount": "ハーフポイント金額", "Hardware & Network": "ハードウェア・ネットワーク", "Hardware & Slots": "ハードウェア・スロット", + "Hardware Peripheral Settings": "ハードウェア周辺機器設定", "HashIV": "HashIV", "HashKey": "HashKey", "Heartbeat": "ハートビート", @@ -1955,6 +1970,7 @@ "Temperature Limit": "温度しきい値", "Temperature Restored": "温度正常復帰", "Temperature Trend": "温度傾向", + "Temperature reaches :temp degrees, the fan will turn on": "温度が :temp度 に達するとファンが作動します", "Temperature reported: :temp°C": "温度が報告されました: :temp°C", "Temperature updated to :temp°C": "温度が :temp°C に更新されました", "Tenant": "顧客 (テナント)", @@ -2084,6 +2100,7 @@ "Update Product": "商品更新", "Update Settings": "設定更新", "Update Welcome Gift": "ウェルカムギフトを更新", + "Update ambient temperature setting to :temp°C": "環境温度設定を :temp°C に更新しました", "Update existing role and permissions.": "既存のロールと権限設定を更新します。", "Update failed": "更新失敗", "Update identification for your asset": "資産の識別情報を更新", @@ -2098,6 +2115,7 @@ "Upload file or provide URL": "Upload file or provide URL", "Uploading new images will replace all existing images.": "新しい写真をアップロードすると、既存のすべての写真が置き換えられます。", "Uploading...": "アップロード中...", + "Upper limit; the fan will turn on when reached": "温度上限;この温度に達するとファンが作動します", "Usage Completed": "使用完了", "Usage Limit": "使用回数上限", "Usage Logs": "使用履歴", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index 32e077e..c8dd558 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -120,6 +120,13 @@ "All Warehouses": "所有倉庫", "All issues marked as resolved.": "所有異常已標記為已排除。", "All slots are fully stocked": "所有貨道已滿貨", + "Ambient Temperature": "環境溫度", + "Ambient Temperature Log": "環境溫度回傳", + "Ambient Temperature Monitoring": "環境溫度監測", + "Ambient Temperature Setting": "環境溫度設定", + "Ambient Temperature Trend": "環境溫度趨勢", + "Ambient Temperature Upper Limit": "環境溫度上限", + "Ambient temperature reported: :temp°C": "環境溫度回傳::temp°C", "Amount": "金額", "Amount / Payment": "金額 / 支付", "Amount Discount": "固定金額", @@ -236,6 +243,7 @@ "CASH": "CASH", "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "建立倉庫間調撥或機台退庫單", "Calculate Replenishment": "計算補貨量", + "Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "可留空,留空表示不啟用監控功能", "Cancel": "取消", "Cancel Gift": "取消此來店禮", "Cancel Order": "取消訂單", @@ -511,6 +519,7 @@ "Disable Product Confirmation": "停用商品確認", "Disable Warehouse": "停用倉庫", "Disabled": "已停用", + "Disabled ambient temperature monitoring": "已停用環境溫度監測", "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "停用此客戶將會連帶停用該客戶下的所有帳號,確定要繼續嗎?", "Discord Notifications": "Discord通知", "Discord Webhook URL": "Discord Webhook 網址", @@ -632,6 +641,7 @@ "English": "英文", "Ensure your account is using a long, random password to stay secure.": "確保您的帳號使用了足夠強度的隨機密碼以維持安全。", "Enter ad material name": "輸入廣告素材名稱", + "Enter ambient temperature setting...": "請輸入環境溫度設定...", "Enter full address": "輸入完整地址", "Enter login ID": "請輸入登入帳號", "Enter machine location": "請輸入機台地點", @@ -690,6 +700,10 @@ "Failed to send test notification.": "發送測試通知失敗。", "Failed to send test notification. Please verify the Webhook URL.": "發送測試通知失敗,請確認 Webhook 網址。", "Failed to update machine images: ": "更新機台圖片失敗:", + "Fan Auto": "風扇自動", + "Fan Controls": "風扇控制", + "Fan Off": "關閉風扇", + "Fan On": "開啟風扇", "Feature Settings": "功能設定", "Feature Toggles": "功能開關", "Field": "欄位", @@ -822,6 +836,7 @@ "Half Points Amount": "半點數金額", "Hardware & Network": "硬體與網路", "Hardware & Slots": "硬體與貨道", + "Hardware Peripheral Settings": "硬體周邊設定", "HashIV": "HashIV", "HashKey": "HashKey", "Heartbeat": "心跳狀態", @@ -1955,6 +1970,7 @@ "Temperature Limit": "溫度限制", "Temperature Restored": "溫度恢復正常", "Temperature Trend": "溫度趨勢", + "Temperature reaches :temp degrees, the fan will turn on": "溫度達 :temp度 將開啟風扇", "Temperature reported: :temp°C": "回報溫度 : :temp°C", "Temperature updated to :temp°C": "溫度已更新為::temp°C", "Tenant": "客戶", @@ -2084,6 +2100,7 @@ "Update Product": "更新商品", "Update Settings": "更新設定", "Update Welcome Gift": "更新來店禮", + "Update ambient temperature setting to :temp°C": "更新環境溫度設定為 :temp°C", "Update existing role and permissions.": "更新現有角色與權限設定。", "Update failed": "更新失敗", "Update identification for your asset": "更新您的資產識別名稱", @@ -2098,6 +2115,7 @@ "Upload file or provide URL": "上傳檔案或提供連結", "Uploading new images will replace all existing images.": "上傳新照片將會取代所有現有照片。", "Uploading...": "上傳中...", + "Upper limit; the fan will turn on when reached": "溫度上限,達到此溫度時風扇將會開啟", "Usage Completed": "使用完成", "Usage Limit": "使用次數上限", "Usage Logs": "使用紀錄", diff --git a/resources/views/admin/basic-settings/machines/index.blade.php b/resources/views/admin/basic-settings/machines/index.blade.php index b3f3514..975f438 100644 --- a/resources/views/admin/basic-settings/machines/index.blade.php +++ b/resources/views/admin/basic-settings/machines/index.blade.php @@ -106,7 +106,8 @@ shopping_cart_enabled: machine.shopping_cart_enabled === true || machine.shopping_cart_enabled === 1 || machine.shopping_cart_enabled === '1', welcome_gift_enabled: machine.welcome_gift_enabled === true || machine.welcome_gift_enabled === 1 || machine.welcome_gift_enabled === '1', cash_module_enabled: machine.cash_module_enabled === true || machine.cash_module_enabled === 1 || machine.cash_module_enabled === '1', - member_system_enabled: machine.member_system_enabled === true || machine.member_system_enabled === 1 || machine.member_system_enabled === '1' + member_system_enabled: machine.member_system_enabled === true || machine.member_system_enabled === 1 || machine.member_system_enabled === '1', + ambient_temp_monitoring_enabled: machine.ambient_temp_monitoring_enabled === true || machine.ambient_temp_monitoring_enabled === 1 || machine.ambient_temp_monitoring_enabled === '1' }; this.showMachineSettingsModal = true; }, @@ -610,6 +611,29 @@ + + +
+
+

{{ __('Hardware Peripheral Settings') }}

+
+
+ +
+
welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift'); if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine'); if ($machine->member_system_enabled) $activeFeatures[] = __('Member System'); + if ($machine->ambient_temp_monitoring_enabled) $activeFeatures[] = __('Ambient Temperature Monitoring'); @endphp @@ -139,6 +140,7 @@ if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift'); if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine'); if ($machine->member_system_enabled) $activeFeatures[] = __('Member System'); + if ($machine->ambient_temp_monitoring_enabled) $activeFeatures[] = __('Ambient Temperature Monitoring'); @endphp
diff --git a/resources/views/admin/machines/index.blade.php b/resources/views/admin/machines/index.blade.php index 56b0792..d0a0572 100644 --- a/resources/views/admin/machines/index.blade.php +++ b/resources/views/admin/machines/index.blade.php @@ -11,6 +11,8 @@ showResolveConfirm: false, editMachineId: '', editMachineName: '', + editAmbientTempSetting: '', + editAmbientTempMonitoringEnabled: false, activeTab: 'status', currentMachineId: '', isLoadingTable: false, @@ -55,6 +57,7 @@ totalLogs: 0, temperatureChart: null, + ambientTemperatureChart: null, init() { const now = new Date(); @@ -63,11 +66,24 @@ this.startDate = formatDate(now, '00:00'); this.endDate = formatDate(now, '23:59'); - this.$watch('activeTab', () => this.fetchLogs(1)); + this.$watch('activeTab', (val) => { + this.fetchLogs(1); + if (val === 'status') { + this.fetchTemperatureData(); + } else if (val === 'ambient_temp') { + this.fetchAmbientTemperatureData(); + } + }); // 監聽日期變動,同步更新圖表 (僅在當前為狀態分頁時) - this.$watch('startDate', () => { if(this.activeTab === 'status') this.fetchTemperatureData(); }); - this.$watch('endDate', () => { if(this.activeTab === 'status') this.fetchTemperatureData(); }); + this.$watch('startDate', () => { + if(this.activeTab === 'status') this.fetchTemperatureData(); + if(this.activeTab === 'ambient_temp') this.fetchAmbientTemperatureData(); + }); + this.$watch('endDate', () => { + if(this.activeTab === 'status') this.fetchTemperatureData(); + if(this.activeTab === 'ambient_temp') this.fetchAmbientTemperatureData(); + }); }, async openLogPanel(id, sn, name) { @@ -96,6 +112,106 @@ } catch (e) { console.error('fetchTemperatureData error:', e); } }, + async fetchAmbientTemperatureData() { + if (this.activeTab !== 'ambient_temp') return; + try { + let url = `/admin/machines/${this.currentMachineId}/ambient-temperature-ajax?`; + if (this.startDate) url += '&start_date=' + this.startDate; + if (this.endDate) url += '&end_date=' + this.endDate; + + const res = await fetch(url); + const data = await res.json(); + if (data.success) { + this.initAmbientTemperatureChart(data.data); + } + } catch (e) { console.error('fetchAmbientTemperatureData error:', e); } + }, + + initAmbientTemperatureChart(chartData) { + this.$nextTick(() => { + const chartEl = document.querySelector("#ambient-temperature-chart"); + if (!chartEl) return; + + const isDark = document.documentElement.classList.contains('dark'); + + const options = { + series: [{ + name: "{{ __('Ambient Temperature') }}", + data: chartData + }], + chart: { + id: 'ambient-temp-chart', + type: 'area', + height: 200, + toolbar: { show: false }, + zoom: { enabled: false }, + animations: { enabled: false }, + background: 'transparent', + accessibility: { enabled: false } + }, + colors: ['#3b82f6'], + fill: { + type: 'gradient', + gradient: { + shadeIntensity: 1, + opacityFrom: 0.45, + opacityTo: 0.05, + stops: [20, 100] + } + }, + dataLabels: { enabled: false }, + stroke: { + curve: 'smooth', + width: 3 + }, + grid: { + borderColor: isDark ? '#1e293b' : '#f1f5f9', + strokeDashArray: 4, + padding: { left: 10, right: 10 } + }, + xaxis: { + type: 'datetime', + labels: { + show: true, + style: { colors: '#94a3b8', fontSize: '10px', fontWeight: 600 }, + datetimeUTC: false, + format: 'HH:mm', + hideOverlappingLabels: true, + }, + axisBorder: { show: false }, + axisTicks: { show: false }, + tooltip: { enabled: false }, + tickAmount: 6 + }, + yaxis: { + labels: { + style: { colors: '#94a3b8', fontSize: '10px', fontWeight: 600 }, + formatter: (val) => val + '°C' + } + }, + tooltip: { + theme: isDark ? 'dark' : 'light', + x: { + show: true, + format: 'yyyy/MM/dd HH:mm:ss' + }, + y: { + title: { + formatter: (seriesName) => seriesName + ': ' + } + } + } + }; + + if (this.ambientTemperatureChart) { + this.ambientTemperatureChart.updateOptions(options); + } else { + this.ambientTemperatureChart = new ApexCharts(chartEl, options); + this.ambientTemperatureChart.render(); + } + }); + }, + initTemperatureChart(chartData) { this.$nextTick(() => { const chartEl = document.querySelector("#temperature-chart"); @@ -181,9 +297,11 @@ }); }, - openEditModal(id, name) { + openEditModal(id, name, ambientTempSetting = '', ambientTempMonitoringEnabled = false) { this.editMachineId = id; this.editMachineName = name; + this.editAmbientTempSetting = ambientTempSetting; + this.editAmbientTempMonitoringEnabled = ambientTempMonitoringEnabled === true || ambientTempMonitoringEnabled === 1 || ambientTempMonitoringEnabled === '1' || ambientTempMonitoringEnabled === 'true'; this.showEditModal = true; }, @@ -493,7 +611,7 @@
- - + +
- +
- +
- +
{{ __('Loading Data') }}...

- +
@@ -859,6 +982,21 @@
+ + +
+
+

+ + {{ __('Ambient Temperature Trend') }} +

+ + @ + +
+
+
+ +
+ + + {{-- Luxury +/- Counter UI --}} +
+ {{-- Minus Button --}} + + +
+ + {{-- Input Field --}} +
+ + + {{-- Clear Button --}} + +
+ +
+ + {{-- Plus Button --}} + +
+ + {{-- Help Text --}} +

+ {{ __('Can be empty. If left blank, it means ambient temperature monitoring is disabled.') }} +

+
+ +
+
+
+ {{ + __('Fan Controls') }} +
+
+ + + + + + + + +
+
+
diff --git a/resources/views/admin/remote/partials/tab-history-index.blade.php b/resources/views/admin/remote/partials/tab-history-index.blade.php index 4021490..f474dff 100644 --- a/resources/views/admin/remote/partials/tab-history-index.blade.php +++ b/resources/views/admin/remote/partials/tab-history-index.blade.php @@ -89,6 +89,10 @@ 'checkout' => __('Remote Settlement'), 'lock' => __('Lock Page Lock'), 'unlock' => __('Lock Page Unlock'), + 'fanon' => __('Fan On'), + 'fanoff' => __('Fan Off'), + 'fanauto' => __('Fan Auto'), + 'ambient_temp_limit' => __('Ambient Temperature Upper Limit'), 'change' => __('Remote Change'), 'dispense' => __('Remote Dispense'), 'update_ads' => __('Sync Ads'), diff --git a/routes/web.php b/routes/web.php index 8ad5fa2..b7f68e9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -63,6 +63,7 @@ Route::middleware(['auth', 'auth.session', 'verified', 'tenant.access'])->prefix Route::post('/{machine}/slots/expiry', [App\Http\Controllers\Admin\MachineController::class, 'updateSlotExpiry'])->name('slots.expiry.update'); Route::get('/{machine}/logs-ajax', [App\Http\Controllers\Admin\MachineController::class, 'logsAjax'])->name('logs-ajax'); Route::get('/{machine}/temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'temperatureAjax'])->name('temperature-ajax'); + Route::get('/{machine}/ambient-temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'ambientTemperatureAjax'])->name('ambient-temperature-ajax'); Route::post('/{machine}/resolve-logs', [App\Http\Controllers\Admin\MachineController::class, 'resolveLogs'])->name('resolve-logs'); }); Route::resource('machines', App\Http\Controllers\Admin\MachineController::class); From cacae84a85fd077a11b8e51a40f63f2ec5bfbc9c Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 11:52:58 +0800 Subject: [PATCH 3/6] =?UTF-8?q?[DOCS]=20=E8=A3=9C=E4=B8=8A=E6=A9=9F?= =?UTF-8?q?=E5=8F=B0=E7=92=B0=E5=A2=83=E6=BA=AB=E5=BA=A6=E4=B8=8A=E5=A0=B1?= =?UTF-8?q?=E7=9A=84=20MQTT=20API=20=E6=96=87=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 在 config/api-docs.php 新增 machine/{serial_no}/ambient_temp Topic 的說明與參數定義。 --- config/api-docs.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/config/api-docs.php b/config/api-docs.php index cd1c8de..6002308 100644 --- a/config/api-docs.php +++ b/config/api-docs.php @@ -806,6 +806,22 @@ return [ ], ], + [ + 'name' => '機台環境溫度上報 (Ambient Temperature Report)', + 'slug' => 'mqtt-ambient-temp-report', + 'action' => 'PUB', + 'topic' => 'machine/{serial_no}/ambient_temp', + 'qos' => 1, + 'description' => '機台主動上報當前環境溫度。支援 temperature 或 ambient_temp 欄位上報。', + 'payload_parameters' => [ + 'temperature' => ['type' => 'integer', 'description' => '當前環境溫度值 (例如 28)'], + 'ambient_temp' => ['type' => 'integer', 'description' => '當前環境溫度值 (相容相應硬體參數)'], + ], + 'payload_example' => [ + 'temperature' => 28 + ], + ], + [ 'name' => '指令執行回報 (Machine to Cloud)', 'slug' => 'mqtt-command-ack', From 7579a0b5be24a537c0b497c9ff325186584ea7ff Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 12:00:54 +0800 Subject: [PATCH 4/6] =?UTF-8?q?[FEAT]=20=E8=AA=BF=E6=95=B4=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E6=BA=AB=E5=BA=A6=E4=B8=8A=E5=A0=B1=E6=96=87=E6=AA=94?= =?UTF-8?q?=E9=A0=86=E5=BA=8F=E8=88=87=E6=A9=9F=E5=8F=B0=E7=A1=AC=E9=AB=94?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=97=A5=E8=AA=8C=E6=AD=B8=E9=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 文件調整:將 config/api-docs.php 中的「機台環境溫度上報 (mqtt-ambient-temp-report)」MQTT API 區塊位置向上移至「機台硬體事件上報 (mqtt-event)」上方。 2. 日誌歸類:修改 ProcessMachineEvent.php,將機台硬體事件 (event) 日誌寫入資料庫時的 type 從 'status' (機台狀態) 改為 'ambient_temp' (環境溫度回傳),以直接顯示於「環境溫度回傳」Tab。 --- app/Jobs/Machine/ProcessMachineEvent.php | 4 +-- config/api-docs.php | 32 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/Jobs/Machine/ProcessMachineEvent.php b/app/Jobs/Machine/ProcessMachineEvent.php index b6f0535..0a282ea 100644 --- a/app/Jobs/Machine/ProcessMachineEvent.php +++ b/app/Jobs/Machine/ProcessMachineEvent.php @@ -45,10 +45,10 @@ class ProcessMachineEvent implements ShouldQueue return; } - // 寫入機台狀態日誌,type 為 'status' 以便正確歸類顯示於「機台狀態」日誌中 + // 寫入環境溫度日誌,type 為 'ambient_temp' 以便正確歸類顯示於「環境溫度回傳」日誌中 $machine->logs()->create([ 'company_id' => $machine->company_id, - 'type' => 'status', // 忠實呈現於機台狀態中 + 'type' => 'ambient_temp', // 歸類至環境溫度回傳 'level' => 'info', 'message' => $event, // 例如 "fanon" 或 "fanoff" 'context' => $this->payload, diff --git a/config/api-docs.php b/config/api-docs.php index 6002308..94009ef 100644 --- a/config/api-docs.php +++ b/config/api-docs.php @@ -540,6 +540,22 @@ return [ 'status' => 'online' ], ], + [ + 'name' => '機台環境溫度上報 (Ambient Temperature Report)', + 'slug' => 'mqtt-ambient-temp-report', + 'action' => 'PUB', + 'topic' => 'machine/{serial_no}/ambient_temp', + 'qos' => 1, + 'description' => '機台主動上報當前環境溫度。支援 temperature 或 ambient_temp 欄位上報。', + 'payload_parameters' => [ + 'temperature' => ['type' => 'integer', 'description' => '當前環境溫度值 (例如 28)'], + 'ambient_temp' => ['type' => 'integer', 'description' => '當前環境溫度值 (相容相應硬體參數)'], + ], + 'payload_example' => [ + 'temperature' => 28 + ], + ], + [ 'name' => '機台硬體事件上報 (Event)', 'slug' => 'mqtt-event', @@ -806,22 +822,6 @@ return [ ], ], - [ - 'name' => '機台環境溫度上報 (Ambient Temperature Report)', - 'slug' => 'mqtt-ambient-temp-report', - 'action' => 'PUB', - 'topic' => 'machine/{serial_no}/ambient_temp', - 'qos' => 1, - 'description' => '機台主動上報當前環境溫度。支援 temperature 或 ambient_temp 欄位上報。', - 'payload_parameters' => [ - 'temperature' => ['type' => 'integer', 'description' => '當前環境溫度值 (例如 28)'], - 'ambient_temp' => ['type' => 'integer', 'description' => '當前環境溫度值 (相容相應硬體參數)'], - ], - 'payload_example' => [ - 'temperature' => 28 - ], - ], - [ 'name' => '指令執行回報 (Machine to Cloud)', 'slug' => 'mqtt-command-ack', From 7ceb7644f050f104c8a7871916bc66d7a819d4da Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 13:01:49 +0800 Subject: [PATCH 5/6] =?UTF-8?q?[FIX]=20=E7=B5=B1=E4=B8=80=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E8=A8=AD=E5=AE=9A=E6=AC=8A=E9=99=90=E6=A9=9F=E5=88=B6?= =?UTF-8?q?=E4=B8=A6=E6=B8=85=E9=99=A4=E5=86=97=E9=A4=98=E7=89=B9=E6=AE=8A?= =?UTF-8?q?=E6=AC=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修正 RoleSeeder.php:移除了預設分配給客戶管理員角色模板的 menu.basic.discord-notifications 與 menu.basic.apk-versions 權限,使其運作機制與機台設定、金流設定完全一致,預設不分配且避免部署時被強制重灌。 2. 清除冗餘權限:從 Seeder 權限宣告中移除專案未引用的 menu.special-permission.apk-versions 與 menu.special-permission.discord-notifications。 3. 語系檔對齊整理:自 zh_TW.json, en.json, ja.json 中刪除冗餘的特殊權限翻譯 Key,並重新按字母排序(ksort)且還原斜線。 4. 修正單元測試:調整 DiscordNotificationSecurityTest.php,在 setUp 中為測試角色手動指派基本通知權限以通過 Gate 檢查,成功修正測試案例。 --- database/seeders/RoleSeeder.php | 6 - lang/en.json | 2 - lang/ja.json | 2 - lang/zh_TW.json | 2 - scratch/dedupe_lang.py | 253 ------------------ scratch/test_breadcrumbs.php | 66 ----- .../Admin/DiscordNotificationSecurityTest.php | 2 + 7 files changed, 2 insertions(+), 331 deletions(-) delete mode 100644 scratch/dedupe_lang.py delete mode 100644 scratch/test_breadcrumbs.php diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index 6f46764..50859d9 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -58,8 +58,6 @@ class RoleSeeder extends Seeder 'menu.reservation', 'menu.special-permission', 'menu.special-permission.clear-stock', - 'menu.special-permission.apk-versions', - 'menu.special-permission.discord-notifications', 'menu.basic', 'menu.basic.machines', 'menu.basic.payment-configs', @@ -123,10 +121,6 @@ class RoleSeeder extends Seeder 'menu.reservation', 'menu.special-permission', 'menu.special-permission.clear-stock', - 'menu.special-permission.apk-versions', - 'menu.special-permission.discord-notifications', - 'menu.basic.discord-notifications', - 'menu.basic.apk-versions', ]); } } \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 7fb4709..40e204d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2361,9 +2361,7 @@ "menu.sales.records": "Sales Records", "menu.sales.store-gifts": "Store Gifts", "menu.special-permission": "Special Permissions", - "menu.special-permission.apk-versions": "APK Versions", "menu.special-permission.clear-stock": "Clear Stock", - "menu.special-permission.discord-notifications": "Discord Notifications", "menu.warehouses": "Warehouse Management", "menu.warehouses.inventory": "Inventory Management", "menu.warehouses.machine-inventory": "Machine Inventory Overview", diff --git a/lang/ja.json b/lang/ja.json index 9d75944..5856093 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -2361,9 +2361,7 @@ "menu.sales.records": "販売記録", "menu.sales.store-gifts": "来店ギフト", "menu.special-permission": "特別権限", - "menu.special-permission.apk-versions": "APKバージョン", "menu.special-permission.clear-stock": "在庫クリア", - "menu.special-permission.discord-notifications": "Discord通知", "menu.warehouses": "倉庫管理", "menu.warehouses.inventory": "在庫管理", "menu.warehouses.machine-inventory": "機器在庫概要", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index c8dd558..e025f01 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -2361,9 +2361,7 @@ "menu.sales.records": "銷售紀錄", "menu.sales.store-gifts": "來店禮", "menu.special-permission": "特殊權限", - "menu.special-permission.apk-versions": "APK版本", "menu.special-permission.clear-stock": "庫存清空", - "menu.special-permission.discord-notifications": "Discord通知", "menu.warehouses": "倉儲管理", "menu.warehouses.inventory": "庫存管理", "menu.warehouses.machine-inventory": "機台庫存概覽", diff --git a/scratch/dedupe_lang.py b/scratch/dedupe_lang.py deleted file mode 100644 index 6022d1b..0000000 --- a/scratch/dedupe_lang.py +++ /dev/null @@ -1,253 +0,0 @@ -import json -import os - -files = { - 'zh_TW': '/home/mama/projects/star-cloud/lang/zh_TW.json', - 'en': '/home/mama/projects/star-cloud/lang/en.json', - 'ja': '/home/mama/projects/star-cloud/lang/ja.json' -} - -new_keys = { - "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": { - "zh_TW": "建立倉庫間調撥或機台退庫單", - "en": "Create stock transfers between warehouses and machine returns", - "ja": "倉庫間の転送および機台からの返品伝票を作成" - }, - "Create and manage replenishment orders from warehouse to machines": { - "zh_TW": "建立並管理從倉庫到機台的補貨單", - "en": "Create and manage replenishment orders from warehouse to machines", - "ja": "倉庫から機台への補充伝票の作成と管理" - }, - "Create stock transfers between warehouses and machine returns": { - "zh_TW": "建立倉庫間調撥或機台退庫單", - "en": "Create stock transfers between warehouses and machine returns", - "ja": "倉庫間の転送および機台からの返品伝票を作成" - }, - "Create Transfer Order": { - "zh_TW": "建立調撥單", - "en": "Create Transfer Order", - "ja": "転送伝票を作成" - }, - "Create Replenishment Order": { - "zh_TW": "建立補貨單", - "en": "Create Replenishment Order", - "ja": "補充伝票を作成" - }, - "New Transfer": { - "zh_TW": "新增調撥單", - "en": "New Transfer", - "ja": "新規転送" - }, - "New Replenishment": { - "zh_TW": "新增補貨單", - "en": "New Replenishment", - "ja": "新規補充" - }, - "New Stock-In Order": { - "zh_TW": "新增進貨單", - "en": "New Stock-In Order", - "ja": "新規入庫伝票" - }, - "No transfer orders found": { - "zh_TW": "查無調撥單紀錄", - "en": "No transfer orders found", - "ja": "転送伝票が見つかりません" - }, - "No replenishment orders found": { - "zh_TW": "查無補貨單紀錄", - "en": "No replenishment orders found", - "ja": "補充伝票が見つかりません" - }, - "No stock data found": { - "zh_TW": "查無庫存資料", - "en": "No stock data found", - "ja": "在庫データが見つかりません" - }, - "No stock-in orders found": { - "zh_TW": "查無進貨單紀錄", - "en": "No stock-in orders found", - "ja": "入庫伝票が見つかりません" - }, - "No movement records found": { - "zh_TW": "查無異動紀錄", - "en": "No movement records found", - "ja": "移動履歴が見つかりません" - }, - "Replenishment Orders": { - "zh_TW": "機台補貨單", - "en": "Replenishment Orders", - "ja": "機台補充伝票" - }, - "Search products...": { - "zh_TW": "搜尋商品...", - "en": "Search products...", - "ja": "商品を検索..." - }, - "Search Product": { - "zh_TW": "搜尋商品", - "en": "Search Product", - "ja": "商品を検索" - }, - "Select Warehouse": { - "zh_TW": "選擇倉庫", - "en": "Select Warehouse", - "ja": "倉庫を選択" - }, - "Select Machine": { - "zh_TW": "選擇機台", - "en": "Select Machine", - "ja": "機台を選択" - }, - "Select Product": { - "zh_TW": "選擇商品", - "en": "Select Product", - "ja": "商品を選択" - }, - "Source Warehouse": { - "zh_TW": "來源倉庫", - "en": "Source Warehouse", - "ja": "発送元倉庫" - }, - "Target Warehouse": { - "zh_TW": "目標倉庫", - "en": "Target Warehouse", - "ja": "対象倉庫" - }, - "Stock-In Orders": { - "zh_TW": "進貨單管理", - "en": "Stock-In Orders", - "ja": "入庫伝票" - }, - "Stock-In Management": { - "zh_TW": "進貨管理", - "en": "Stock-In Management", - "ja": "入庫管理" - }, - "Stock flow history": { - "zh_TW": "庫存流向歷史", - "en": "Stock flow history", - "ja": "在庫移動履歴" - }, - "Stock-In Orders Tab": { - "zh_TW": "進貨單管理", - "en": "Stock-In Orders", - "ja": "入庫伝票" - }, - "Transfer Orders": { - "zh_TW": "調撥單管理", - "en": "Transfer Orders", - "ja": "転送伝票" - }, - "Track stock-in orders and movement history": { - "zh_TW": "追蹤進貨單據與庫存異動紀錄", - "en": "Track stock-in orders and movement history", - "ja": "入庫伝票および移動履歴の追跡" - }, - "Warehouse to Warehouse": { - "zh_TW": "倉庫對倉庫", - "en": "Warehouse to Warehouse", - "ja": "倉庫間転送" - }, - "Warehouse Transfer": { - "zh_TW": "倉庫調撥", - "en": "Warehouse Transfer", - "ja": "倉庫転送" - }, - "Warehouse Management": { - "zh_TW": "倉儲管理", - "en": "Warehouse Management", - "ja": "倉庫管理" - }, - "Warehouse purchase records": { - "zh_TW": "倉庫採購進貨紀錄", - "en": "Warehouse purchase records", - "ja": "倉庫購入記録" - }, - "Machine to Warehouse": { - "zh_TW": "機台對倉庫", - "en": "Machine to Warehouse", - "ja": "機台から倉庫" - }, - "Machine Return": { - "zh_TW": "機台退庫", - "en": "Machine Return", - "ja": "機台返品" - }, - "Machine Replenishment": { - "zh_TW": "機台補貨單", - "en": "Machine Replenishment", - "ja": "機台補充" - }, - "Movement History": { - "zh_TW": "異動紀錄", - "en": "Movement History", - "ja": "移動履歴" - }, - "Movement Logs": { - "zh_TW": "異動紀錄", - "en": "Movement Logs", - "ja": "移動ログ" - }, - "Manage stock transfers between warehouses and machine returns": { - "zh_TW": "建立倉庫間調撥或機台退庫單", - "en": "Manage stock transfers between warehouses and machine returns", - "ja": "倉庫間の転送および機台からの返品伝票を作成" - }, - "Main": { - "zh_TW": "總倉", - "en": "Main", - "ja": "総倉庫" - }, - "Note": { - "zh_TW": "備註", - "en": "Note", - "ja": "備考" - }, - "Transfer Audit": { - "zh_TW": "調撥單", - "en": "Transfer Audit", - "ja": "転送監査" - }, - "Transfers": { - "zh_TW": "調撥單", - "en": "Transfers", - "ja": "転送" - }, - "Inventory Management": { - "zh_TW": "庫存管理", - "en": "Inventory Management", - "ja": "在庫管理" - }, - "Manage stock levels, stock-in orders, and movement history": { - "zh_TW": "追蹤庫存水位、進貨單與異動紀錄", - "en": "Manage stock levels, stock-in orders, and movement history", - "ja": "在庫レベル、入庫伝票、および移動履歴の管理" - }, - "Track stock levels, stock-in orders, and movement history": { - "zh_TW": "追蹤庫存水位、進貨單與異動紀錄", - "en": "Track stock levels, stock-in orders, and movement history", - "ja": "在庫レベル、入庫伝票、および移動履歴の追跡" - } -} - -for lang, path in files.items(): - if not os.path.exists(path): - continue - - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - # Update menu.warehouses - if lang == 'zh_TW': - data['menu.warehouses'] = "倉儲管理" - - # Add/Update keys - for k, v in new_keys.items(): - if lang in v: - data[k] = v[lang] - elif lang == 'en': - data[k] = k - - # Sort keys and write back - with open(path, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=4, sort_keys=True) diff --git a/scratch/test_breadcrumbs.php b/scratch/test_breadcrumbs.php deleted file mode 100644 index e76c2e6..0000000 --- a/scratch/test_breadcrumbs.php +++ /dev/null @@ -1,66 +0,0 @@ - __('Dashboard'), - 'url' => '/admin/dashboard', - 'active' => $routeName === 'admin.dashboard' - ]; - - $moduleMap = [ - 'admin.warehouses' => __('Warehouse Management'), - // ... simplified - ]; - - $foundModule = null; - foreach ($moduleMap as $prefix => $label) { - if (str_starts_with($routeName, $prefix)) { - $foundModule = [ - 'label' => $label, - 'url' => '#', - 'active' => false - ]; - break; - } - } - - if ($foundModule) { - $links[] = $foundModule; - } - - $segments = explode('.', $routeName); - $lastSegment = end($segments); - - // Simplifed page labels - $pageLabel = match($lastSegment) { - 'inventory' => __('Inventory Management'), - 'index' => 'Warehouse Overview', - default => null, - }; - - if ($pageLabel) { - $links[] = [ - 'label' => $pageLabel, - 'active' => true - ]; - } - - echo "Route: $routeName\n"; - foreach ($links as $link) { - echo " - " . $link['label'] . ($link['active'] ? " (active)" : "") . "\n"; - } - echo "\n"; -} - -testBreadcrumbs('admin.warehouses.inventory'); -testBreadcrumbs('admin.warehouses.index'); diff --git a/tests/Feature/Admin/DiscordNotificationSecurityTest.php b/tests/Feature/Admin/DiscordNotificationSecurityTest.php index 3ea2977..8de5368 100644 --- a/tests/Feature/Admin/DiscordNotificationSecurityTest.php +++ b/tests/Feature/Admin/DiscordNotificationSecurityTest.php @@ -61,6 +61,8 @@ class DiscordNotificationSecurityTest extends TestCase // 確保 tenantAdminTemplate 包含我們新加的 menu.basic.discord-notifications $tenantRole->syncPermissions($tenantAdminTemplate->permissions); + // 手動指派以測試有權限時的存取行為 (因現已改為比照機台設定預設不配給範本) + $tenantRole->givePermissionTo('menu.basic.discord-notifications'); $this->tenantAdmin->assignRole($tenantRole); // 5. 建立普通無此權限之帳號 From 06d93889b363c2639e15f23e25aeb9cfc4576a55 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 4 Jun 2026 13:13:20 +0800 Subject: [PATCH 6/6] =?UTF-8?q?[FEAT]=20=E4=BE=9D=E6=93=9A=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E6=BA=AB=E5=BA=A6=E7=9B=A3=E6=B8=AC=E5=95=9F=E7=94=A8?= =?UTF-8?q?=E7=8B=80=E6=85=8B=E5=8B=95=E6=85=8B=E9=A1=AF=E7=A4=BA=E6=97=A5?= =?UTF-8?q?=E8=AA=8C=20Tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 在 machines/index.blade.php 的 Alpine x-data 中新增 currentMachineAmbientTempEnabled 屬性。 2. 修改 openLogPanel 方法以傳入機台的環境溫度監測啟用狀態。 3. 於機台列表所有呼叫 openLogPanel 處帶入 ambient_temp_monitoring_enabled。 4. 在「環境溫度回傳」Tab 的按鈕上加入 x-show 與 x-cloak,使該 Tab 僅在機台啟用監測時顯示,避免非必要 Tab 的干擾與界面閃爍。 --- resources/views/admin/machines/index.blade.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/resources/views/admin/machines/index.blade.php b/resources/views/admin/machines/index.blade.php index d0a0572..5e184f7 100644 --- a/resources/views/admin/machines/index.blade.php +++ b/resources/views/admin/machines/index.blade.php @@ -42,6 +42,7 @@ }, currentMachineSn: '', currentMachineName: '', + currentMachineAmbientTempEnabled: false, logs: [], loading: false, inventoryLoading: false, @@ -86,10 +87,11 @@ }); }, - async openLogPanel(id, sn, name) { + async openLogPanel(id, sn, name, ambientTempEnabled = false) { this.currentMachineId = id; this.currentMachineSn = sn; this.currentMachineName = name; + this.currentMachineAmbientTempEnabled = ambientTempEnabled === true || ambientTempEnabled === 1 || ambientTempEnabled === '1' || ambientTempEnabled === 'true'; this.slots = []; this.showLogPanel = true; this.activeTab = 'status'; @@ -474,7 +476,7 @@ + @click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}', {{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }})">
@@ -621,7 +623,7 @@