star-cloud/tests/Feature/Api/V1/MachineSettingB016Test.php
sky121113 779c59138f [FEAT] 商品與機台多語系架構,並補上系統設定同步操作
1. 新增 config/locales.php:定義 11 種系統支援語系白名單(原文+中文註記)、每台機台上限 5 種與 fallback (zh_TW)。
2. Company 新增 activeLocalesFor()/activeLocales():計算公司所有機台已開語系的聯集(去重、依白名單排序、保底 zh_TW),加快取與 forgetActiveLocales() 失效機制。
3. products 新增 spec_dictionary_key 欄位(migration);Product 新增 localized_spec accessor 與 specTranslations 關聯,商品規格比照名稱支援多語系。
4. ProductController 商品建立/更新改收 names 與 specs 物件,寫入 translations(group=product/product_spec),既有商品首次編輯補建 spec key,刪除時一併清除規格翻譯。
5. ProductCatalogService(B012)保留既有 t060v01_en/_jp 欄位,新增 t060v01_i18n/t060v03_i18n locale map,依公司語系聯集輸出並回退 zh_TW,確保線上舊 App 相容。
6. 機台系統設定(updateSystemSettings)新增 languages 顯示語系驗證與寫入(最多 5 種、白名單、去重,僅系統管理員),異動時失效語系聯集快取並重建商品目錄。
7. B014(getSettings)新增 LangSet 下發機台顯示語系(Languages 有序清單+Default 預設)。
8. 商品建立/編輯頁名稱與規格改為多語系 Tab(讀公司機台語系聯集、完成度標記),新增共用元件 product-locale-tabs;機台系統設定彈窗新增顯示語系勾選 UI(限 5 選、預設標記)。
9. 三語系 JSON(zh_TW/en/ja)新增 5 組對齊鍵並維持字母排序。
10. 同步更新文件:docs/API/product_multilingual_spec.md(規格書)、api-technical-specs SKILL.md(B012 i18n/B014 LangSet)、config/api-docs.php(B012/B014 範例與欄位)。
11. 系統設定同步:新增 sync-settings 路由與 update_settings 指令類型,ProcessCommandAck 與遠端指令歷史(remote index/tab-history)支援 update_settings 標籤,機台系統設定頁新增「同步設定」操作按鈕,並補 B016 回寫測試。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:00:10 +08:00

201 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Api\V1;
use App\Models\Machine\Machine;
use App\Models\System\Company;
use App\Models\System\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
/**
* B016機台端回寫系統設定。機台僅可編輯硬體貨道 (is_spring_slot_*)
* 其餘系統設定一律忽略;僅系統方 (company_id = null) 可操作。
*/
class MachineSettingB016Test extends TestCase
{
use RefreshDatabase;
private const URL = '/api/v1/app/machine/setting/B016';
private Company $company;
private Machine $machine;
protected function setUp(): void
{
parent::setUp();
$this->company = Company::create(['name' => 'Test Company']);
// 六個貨道初始皆為 false履帶
$this->machine = Machine::factory()->create([
'company_id' => $this->company->id,
'is_spring_slot_1_10' => false,
'is_spring_slot_11_20' => false,
'is_spring_slot_21_30' => false,
'is_spring_slot_31_40' => false,
'is_spring_slot_41_50' => false,
'is_spring_slot_51_60' => false,
]);
}
private function actingAsSystemAdmin(): User
{
$admin = User::factory()->create(['company_id' => null]);
Sanctum::actingAs($admin);
return $admin;
}
/**
* 系統方部分更新:只送兩個貨道 → 只有這兩個被改,其餘四個維持原值,並寫入 updater_id。
*/
public function test_system_admin_partial_update_only_touches_sent_slots(): void
{
$admin = $this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => [
'is_spring_slot_1_10' => true,
'is_spring_slot_51_60' => true,
],
])->assertStatus(200)->assertJson(['success' => true, 'code' => 200]);
$this->assertDatabaseHas('machines', [
'id' => $this->machine->id,
'is_spring_slot_1_10' => 1, // 有送 → 改成彈簧
'is_spring_slot_51_60' => 1, // 有送 → 改成彈簧
'is_spring_slot_11_20' => 0, // 未送 → 維持履帶
'is_spring_slot_21_30' => 0,
'is_spring_slot_31_40' => 0,
'is_spring_slot_41_50' => 0,
'updater_id' => $admin->id,
]);
}
/**
* 一次送齊六個貨道:全部正確寫入。
*/
public function test_system_admin_can_update_all_six_slots(): void
{
$this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => [
'is_spring_slot_1_10' => true,
'is_spring_slot_11_20' => true,
'is_spring_slot_21_30' => true,
'is_spring_slot_31_40' => true,
'is_spring_slot_41_50' => true,
'is_spring_slot_51_60' => true,
],
])->assertStatus(200)->assertJson(['success' => true]);
$this->machine->refresh();
$this->assertTrue($this->machine->is_spring_slot_1_10);
$this->assertTrue($this->machine->is_spring_slot_11_20);
$this->assertTrue($this->machine->is_spring_slot_21_30);
$this->assertTrue($this->machine->is_spring_slot_31_40);
$this->assertTrue($this->machine->is_spring_slot_41_50);
$this->assertTrue($this->machine->is_spring_slot_51_60);
}
/**
* 非貨道鍵(如支付/現金旗標)一律忽略:只跟著的貨道鍵會生效。
*/
public function test_non_slot_keys_are_ignored(): void
{
$this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => [
'is_spring_slot_1_10' => true,
'cash_module_enabled' => true, // 非白名單 → 忽略
'credit_card_enabled' => true, // 非白名單 → 忽略
'shopping_cart_enabled' => false, // 非白名單 → 忽略
],
])->assertStatus(200)->assertJson(['success' => true]);
$this->machine->refresh();
$this->assertTrue($this->machine->is_spring_slot_1_10);
// 非貨道欄位不存在於 machines 直接欄位,確認沒被當成欄位寫入而報錯即可(請求已 200
}
/**
* 完全沒送任何貨道鍵 → 422無可更新欄位
*/
public function test_payload_without_any_slot_key_returns_422(): void
{
$this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => [
'cash_module_enabled' => true,
],
])->assertStatus(422)->assertJson(['success' => false, 'code' => 422]);
}
/**
* settings 非陣列 → 422。
*/
public function test_non_array_settings_returns_422(): void
{
$this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => 'not-an-array',
])->assertStatus(422)->assertJson(['success' => false, 'code' => 422]);
}
/**
* 機台不存在 → 404。
*/
public function test_unknown_machine_returns_404(): void
{
$this->actingAsSystemAdmin();
$this->patchJson(self::URL, [
'machine' => 'SN-DOES-NOT-EXIST',
'settings' => ['is_spring_slot_1_10' => true],
])->assertStatus(404)->assertJson(['success' => false, 'code' => 404]);
}
/**
* 公司管理員company_id 不為 null非系統方→ 403且資料不變。
*/
public function test_company_admin_is_forbidden(): void
{
$companyAdmin = User::factory()->create([
'company_id' => $this->company->id,
'is_admin' => true,
]);
Sanctum::actingAs($companyAdmin);
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => ['is_spring_slot_1_10' => true],
])->assertStatus(403)->assertJson(['success' => false, 'code' => 403]);
$this->assertDatabaseHas('machines', [
'id' => $this->machine->id,
'is_spring_slot_1_10' => 0,
]);
}
/**
* 未帶 token未認證→ 401。
*/
public function test_unauthenticated_returns_401(): void
{
$this->patchJson(self::URL, [
'machine' => $this->machine->serial_no,
'settings' => ['is_spring_slot_1_10' => true],
])->assertStatus(401);
}
}