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>
201 lines
6.6 KiB
PHP
201 lines
6.6 KiB
PHP
<?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);
|
||
}
|
||
}
|