[FEAT] 新增 B016 機台系統設定回寫端點與文件
1. MachineController 新增 updateSettings (B016):機台主控台系統設定由系統方編輯後回寫雲端,僅系統管理員可操作,白名單布林開關合併進 machines.settings JSON 與 is_spring_slot_* 實體欄位,記錄 updater_id 2. routes/api.php 新增 PATCH /api/app/machine/setting/B016 路由,採 auth:sanctum (B000 User Token) 與 throttle:60,1 3. api-technical-specs SKILL.md 新增 §3.8 B016 規格:認證、白名單鍵、403/404/422 錯誤碼,並標注與 B014 下載的 PascalCase 命名方向不對稱 4. config/api-docs.php 新增 B016 條目,對齊既有 entry 格式 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
339b33d09b
commit
231cff9574
@ -204,6 +204,51 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 3.8 B016: 機台系統設定回寫 (Settings Write-back)
|
||||||
|
機台主控台「系統設定」頁由**系統方**編輯後,將開關狀態回寫雲端。為 B014 下載的反向操作。
|
||||||
|
|
||||||
|
- **URL**: PATCH /api/v1/app/machine/setting/B016
|
||||||
|
- **Authentication**: **User Token (Sanctum)** — 帶入 B000 核發之 `Authorization: Bearer <user_token>`,且**僅系統管理員 (`identity=system`)** 可操作;非系統方一律 403。
|
||||||
|
- **Headers**:
|
||||||
|
- `Content-Type: application/json`
|
||||||
|
- `Authorization: Bearer <user_token>`
|
||||||
|
- **Request Body:**
|
||||||
|
|
||||||
|
| 參數 | 類型 | 必填 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| machine | String | 是 | 機台序號 (serial_no) | SN202604130001 |
|
||||||
|
| settings | Object | 是 | 開關鍵值物件,鍵名採**後端原始 snake_case**(非 B014 的 PascalCase `*Set`)。只覆寫有送來的鍵,其餘保留 | {"tax_invoice_enabled": true} |
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **命名方向不對稱**:B014 **下載**以 `DevSet`/`CashSet`/... PascalCase 包裝下發;B016 **回寫**直接使用後端鍵名(如 `shopping_cart_enabled`、`cash_bill_1000`、`is_spring_slot_1_10`)。
|
||||||
|
> **白名單限定**:僅接受下列布林開關鍵,未列入者一律忽略;**不接受** `shopping_mode` 及 `OperationSet` 等非開關欄位(避免誤改)。
|
||||||
|
|
||||||
|
- **可回寫鍵(settings 之下,皆為布林):**
|
||||||
|
- 支付旗標:`shopping_cart_enabled`、`tax_invoice_enabled`、`card_terminal_enabled`、`credit_card_enabled`、`mobile_pay_enabled`、`card_pay_enabled`、`scan_pay_enabled`、`scan_pay_esun_enabled`、`scan_pay_tappay_enabled`、`cash_module_enabled`、`scan_pay_linepay_enabled`、`tappay_linepay`、`tappay_jkopay`、`tappay_easywallet`、`tappay_pipay`、`tappay_pluspay`
|
||||||
|
- 現金面額:`cash_bill_1000`、`cash_bill_500`、`cash_bill_100`、`cash_coin_50`、`cash_coin_10`、`cash_coin_5`、`cash_coin_1`
|
||||||
|
- 功能模組:`pickup_module_enabled`、`pickup_code_enabled`、`pass_code_enabled`、`welcome_gift_enabled`、`member_system_enabled`、`ambient_temp_monitoring_enabled`
|
||||||
|
- 貨道類型(machines 實體欄位):`is_spring_slot_1_10`、`is_spring_slot_11_20`、`is_spring_slot_21_30`、`is_spring_slot_31_40`、`is_spring_slot_41_50`、`is_spring_slot_51_60`(true=彈簧 / false=履帶)
|
||||||
|
|
||||||
|
- **Response Body (Success 200):**
|
||||||
|
|
||||||
|
| 參數 | 類型 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| success | Boolean | 是否成功 | true |
|
||||||
|
| code | Integer | 業務狀態碼 | 200 |
|
||||||
|
| message | String | 回應訊息 | Settings updated successfully. |
|
||||||
|
|
||||||
|
- **錯誤回應:**
|
||||||
|
|
||||||
|
| HTTP | 情境 | message |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| 403 | 非系統管理員 / 未登入 | Forbidden |
|
||||||
|
| 404 | 查無該序號機台 | Machine not found |
|
||||||
|
| 422 | `settings` 非物件/陣列 | Invalid settings payload |
|
||||||
|
|
||||||
|
> 註:寫回時同步更新 `machines.settings` JSON 與 `is_spring_slot_*` 實體欄位,並記錄 `updater_id`。為 system-admin 專用,套用 `withoutGlobalScopes` 跨租戶定位機台。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 3.9 B660: 取貨碼驗證 (Pickup Code)
|
### 3.9 B660: 取貨碼驗證 (Pickup Code)
|
||||||
處理機台端的 8 位數代碼取貨流程。
|
處理機台端的 8 位數代碼取貨流程。
|
||||||
|
|
||||||
|
|||||||
@ -534,6 +534,111 @@ class MachineController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* B016: Update Machine System Settings (Write-back from machine console)
|
||||||
|
* 機台主控台「系統設定」由系統方 (identity=system) 編輯後回寫雲端。
|
||||||
|
* 認證:B000 核發的使用者 Token (auth:sanctum);僅系統管理員可操作。
|
||||||
|
* Body: { machine: 序號, settings: { <後端鍵名>: bool, ... } }
|
||||||
|
* 接收的開關鍵名與 B014 getSettings 下發來源一致(machines.settings JSON 與
|
||||||
|
* is_spring_slot_* 實體欄位)。不處理 shopping_mode 等非開關欄位,避免誤改。
|
||||||
|
*/
|
||||||
|
public function updateSettings(Request $request)
|
||||||
|
{
|
||||||
|
// 1. 僅系統方可回寫(雙重把關:App 端隱藏控制 + 此處伺服器驗權)
|
||||||
|
$user = $request->user();
|
||||||
|
if (!$user || !$user->isSystemAdmin()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => 403,
|
||||||
|
'message' => __('Forbidden')
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查找機台
|
||||||
|
$serialNo = $request->input('machine');
|
||||||
|
$machine = Machine::withoutGlobalScopes()
|
||||||
|
->where('serial_no', $serialNo)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$machine) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => 404,
|
||||||
|
'message' => __('Machine not found')
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $request->input('settings', []);
|
||||||
|
if (!is_array($settings)) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => 422,
|
||||||
|
'message' => __('Invalid settings payload')
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 開關白名單:machines.settings JSON 內的布林鍵(對齊 B014 getSettings 來源)
|
||||||
|
$jsonBoolKeys = [
|
||||||
|
'shopping_cart_enabled', 'tax_invoice_enabled', 'card_terminal_enabled',
|
||||||
|
'credit_card_enabled', 'mobile_pay_enabled', 'card_pay_enabled', 'scan_pay_enabled',
|
||||||
|
'scan_pay_esun_enabled', 'scan_pay_tappay_enabled', 'cash_module_enabled', 'scan_pay_linepay_enabled',
|
||||||
|
'tappay_linepay', 'tappay_jkopay', 'tappay_easywallet', 'tappay_pipay', 'tappay_pluspay',
|
||||||
|
'cash_bill_1000', 'cash_bill_500', 'cash_bill_100',
|
||||||
|
'cash_coin_50', 'cash_coin_10', 'cash_coin_5', 'cash_coin_1',
|
||||||
|
'pickup_module_enabled', 'pickup_code_enabled', 'pass_code_enabled',
|
||||||
|
'welcome_gift_enabled', 'member_system_enabled', 'ambient_temp_monitoring_enabled',
|
||||||
|
];
|
||||||
|
// 直接欄位白名單:machines 實體布林欄位(貨道類型)
|
||||||
|
$columnBoolKeys = [
|
||||||
|
'is_spring_slot_1_10', 'is_spring_slot_11_20', 'is_spring_slot_21_30',
|
||||||
|
'is_spring_slot_31_40', 'is_spring_slot_41_50', 'is_spring_slot_51_60',
|
||||||
|
];
|
||||||
|
// 同時具備實體欄位的 settings 鍵(mutator 同步,需一併寫回避免不一致)
|
||||||
|
$mirrorColumns = [
|
||||||
|
'tax_invoice_enabled', 'card_terminal_enabled', 'scan_pay_esun_enabled',
|
||||||
|
'scan_pay_linepay_enabled', 'shopping_cart_enabled', 'welcome_gift_enabled',
|
||||||
|
'cash_module_enabled', 'member_system_enabled', 'ambient_temp_monitoring_enabled',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 4. 合併 JSON 設定(只覆寫有送來的鍵,其餘保留)
|
||||||
|
$jsonSettings = $machine->settings ?? [];
|
||||||
|
foreach ($jsonBoolKeys as $k) {
|
||||||
|
if (array_key_exists($k, $settings)) {
|
||||||
|
$jsonSettings[$k] = (bool) $settings[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 整理實體欄位更新(貨道 + 鏡像欄位)
|
||||||
|
$columnData = [];
|
||||||
|
foreach ($columnBoolKeys as $k) {
|
||||||
|
if (array_key_exists($k, $settings)) {
|
||||||
|
$columnData[$k] = (bool) $settings[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($mirrorColumns as $k) {
|
||||||
|
if (array_key_exists($k, $jsonSettings)) {
|
||||||
|
$columnData[$k] = (bool) $jsonSettings[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$machine->update(array_merge($columnData, [
|
||||||
|
'settings' => $jsonSettings,
|
||||||
|
'updater_id' => $user->id,
|
||||||
|
]));
|
||||||
|
|
||||||
|
\Log::info('B016 Machine settings updated', [
|
||||||
|
'machine' => $machine->serial_no,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'keys' => array_keys($settings),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => __('Settings updated successfully.')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* B660: Verify/Consume Pickup Code
|
* B660: Verify/Consume Pickup Code
|
||||||
* POST: 驗證 8 位數取貨碼 (與原邏輯一致,但加入日誌)
|
* POST: 驗證 8 位數取貨碼 (與原邏輯一致,但加入日誌)
|
||||||
|
|||||||
@ -277,6 +277,59 @@ return [
|
|||||||
],
|
],
|
||||||
'notes' => '此 API 為機台初始化引導用,目前不強制驗證 User Token。金流密鑰與發票設定來源為機台關聯的金流配置 (payment_configs);DevSet / CashSet / FunctionSet / ShoppingMode 來源為 machines.settings、OperationSet / HardwareSet 來源為 machines 實體欄位,全部採機台端 *Set + PascalCase 風格(DevSet/CashSet 對齊機台既有結構,其餘為新定義,機台 App 端需另案實作)。'
|
'notes' => '此 API 為機台初始化引導用,目前不強制驗證 User Token。金流密鑰與發票設定來源為機台關聯的金流配置 (payment_configs);DevSet / CashSet / FunctionSet / ShoppingMode 來源為 machines.settings、OperationSet / HardwareSet 來源為 machines 實體欄位,全部採機台端 *Set + PascalCase 風格(DevSet/CashSet 對齊機台既有結構,其餘為新定義,機台 App 端需另案實作)。'
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => 'B016: 機台系統設定回寫 (Settings Write-back)',
|
||||||
|
'slug' => 'b016-settings-writeback',
|
||||||
|
'method' => 'PATCH',
|
||||||
|
'path' => '/api/v1/app/machine/setting/B016',
|
||||||
|
'description' => '機台主控台「系統設定」頁由系統方編輯後回寫雲端,為 B014 下載的反向操作。需帶 B000 核發之 User Token,且僅系統管理員可操作。鍵名採後端原始 snake_case(非 B014 的 PascalCase *Set),僅白名單布林開關生效,不接受 shopping_mode 等非開關欄位。',
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer <user_token>',
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'parameters' => [
|
||||||
|
'machine' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => true,
|
||||||
|
'description' => '機台序號 (serial_no)',
|
||||||
|
'example' => 'SN202604130001'
|
||||||
|
],
|
||||||
|
'settings' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'required' => true,
|
||||||
|
'description' => "開關鍵值物件(皆為布林),鍵名採後端原始 snake_case,只覆寫有送來的鍵、其餘保留。白名單外鍵一律忽略。可回寫鍵:\n• 支付旗標:shopping_cart_enabled / tax_invoice_enabled / card_terminal_enabled / credit_card_enabled / mobile_pay_enabled / card_pay_enabled / scan_pay_enabled / scan_pay_esun_enabled / scan_pay_tappay_enabled / cash_module_enabled / scan_pay_linepay_enabled / tappay_linepay / tappay_jkopay / tappay_easywallet / tappay_pipay / tappay_pluspay\n• 現金面額:cash_bill_1000 / cash_bill_500 / cash_bill_100 / cash_coin_50 / cash_coin_10 / cash_coin_5 / cash_coin_1\n• 功能模組:pickup_module_enabled / pickup_code_enabled / pass_code_enabled / welcome_gift_enabled / member_system_enabled / ambient_temp_monitoring_enabled\n• 貨道類型(machines 實體欄位):is_spring_slot_1_10 / 11_20 / 21_30 / 31_40 / 41_50 / 51_60(true=彈簧 / false=履帶)",
|
||||||
|
'example' => ['tax_invoice_enabled' => true, 'is_spring_slot_1_10' => false]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'response_parameters' => [
|
||||||
|
'success' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'description' => '是否成功',
|
||||||
|
],
|
||||||
|
'code' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
'description' => '業務狀態碼',
|
||||||
|
],
|
||||||
|
'message' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => '回應訊息',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'request' => [
|
||||||
|
'machine' => 'SN202604130001',
|
||||||
|
'settings' => [
|
||||||
|
'tax_invoice_enabled' => true,
|
||||||
|
'cash_module_enabled' => false,
|
||||||
|
'is_spring_slot_1_10' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'response' => [
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => 'Settings updated successfully.',
|
||||||
|
],
|
||||||
|
'notes' => '僅系統管理員 (identity=system) 可操作,非系統方回 403 Forbidden;查無序號回 404 Machine not found;settings 非物件回 422 Invalid settings payload。寫回時同步更新 machines.settings JSON 與 is_spring_slot_* 實體欄位並記錄 updater_id,套用 withoutGlobalScopes 跨租戶定位機台。'
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => 'B005: 廣告清單同步 (Ad Sync)',
|
'name' => 'B005: 廣告清單同步 (Ad Sync)',
|
||||||
'slug' => 'b005-ad-sync',
|
'slug' => 'b005-ad-sync',
|
||||||
|
|||||||
@ -53,6 +53,11 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
|
|||||||
Route::get('machine/setting/B014', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getSettings']);
|
Route::get('machine/setting/B014', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getSettings']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 機台端回寫系統設定 (B016):僅系統方 (identity=system) 可操作,認證使用 B000 核發的使用者 Token
|
||||||
|
Route::prefix('app')->middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
|
||||||
|
Route::patch('machine/setting/B016', [App\Http\Controllers\Api\V1\App\MachineController::class, 'updateSettings']);
|
||||||
|
});
|
||||||
|
|
||||||
Route::prefix('app')->middleware(['iot.auth', 'throttle:100,1'])->group(function () {
|
Route::prefix('app')->middleware(['iot.auth', 'throttle:100,1'])->group(function () {
|
||||||
// 機台管理員 B000 登入驗證 (需帶機台 API Token)
|
// 機台管理員 B000 登入驗證 (需帶機台 API Token)
|
||||||
Route::post('admin/login/B000', [\App\Http\Controllers\Api\V1\App\MachineAuthController::class, 'loginB000'])->middleware('throttle:30,1');
|
Route::post('admin/login/B000', [\App\Http\Controllers\Api\V1\App\MachineAuthController::class, 'loginB000'])->middleware('throttle:30,1');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user