[FEAT] 基本設定的機台管理新增鑰匙編號欄位與位置調整
1. 於 `MachineSettingController.php` 的 `store` 驗證規則中新增 `key_no` 欄位。 2. 於 `index.blade.php` 的新增機台 Modal 及詳情 Drawer 中,加入「鑰匙編號」欄位與展示。 3. 於機台列表 `tab-machines.blade.php` (桌面表格與行動版卡片) 新增「鑰匙編號」顯示,並同步更新 Detail Drawer 參數。 4. 於 `edit.blade.php` 將「鑰匙編號」欄位移至「基本資訊」區塊並與「客戶公司」並排顯示。 5. 補齊並自動對齊繁中、英文、日文翻譯。 6. 撰寫 `MachineSettingTest.php` 測試案例,驗證基本設定中新增與更新機台鑰匙編號之邏輯。
This commit is contained in:
parent
11eca730f7
commit
0964981097
@ -138,6 +138,7 @@ class MachineSettingController extends AdminController
|
||||
'company_id' => 'nullable|exists:companies,id',
|
||||
'machine_model_id' => 'required|exists:machine_models,id',
|
||||
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
||||
'key_no' => 'nullable|string|max:255',
|
||||
'location' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'latitude' => 'nullable|numeric|between:-90,90',
|
||||
|
||||
@ -663,6 +663,7 @@
|
||||
"Enter ad material name": "Enter ad material name",
|
||||
"Enter ambient temperature setting...": "Enter ambient temperature setting...",
|
||||
"Enter full address": "Enter full address",
|
||||
"Enter key number": "Enter key number",
|
||||
"Enter login ID": "Enter login ID",
|
||||
"Enter machine location": "Enter machine location",
|
||||
"Enter machine name": "Enter machine name",
|
||||
|
||||
@ -663,6 +663,7 @@
|
||||
"Enter ad material name": "広告素材名を入力",
|
||||
"Enter ambient temperature setting...": "周囲温度設定を入力してください...",
|
||||
"Enter full address": "完全な住所を入力",
|
||||
"Enter key number": "鍵番号を入力してください",
|
||||
"Enter login ID": "ログインIDを入力してください",
|
||||
"Enter machine location": "機器の設置場所を入力してください",
|
||||
"Enter machine name": "機器名を入力してください",
|
||||
|
||||
@ -663,6 +663,7 @@
|
||||
"Enter ad material name": "輸入廣告素材名稱",
|
||||
"Enter ambient temperature setting...": "請輸入環境溫度設定...",
|
||||
"Enter full address": "輸入完整地址",
|
||||
"Enter key number": "請輸入鑰匙編號",
|
||||
"Enter login ID": "請輸入登入帳號",
|
||||
"Enter machine location": "請輸入機台地點",
|
||||
"Enter machine name": "請輸入機台名稱",
|
||||
|
||||
@ -161,6 +161,11 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Key No') }}</label>
|
||||
<input type="text" name="key_no" value="{{ old('key_no', $machine->key_no) }}" class="luxury-input w-full" placeholder="{{ __('Enter key number') }}">
|
||||
</div>
|
||||
|
||||
<div x-data="machineGeocoding()" class="col-span-full grid grid-cols-1 md:grid-cols-4 gap-6 pt-6 border-t border-slate-100 dark:border-slate-800">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Address') }}</label>
|
||||
@ -250,10 +255,6 @@
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Card Reader No') }}</label>
|
||||
<input type="text" name="card_reader_no" value="{{ $machine->card_reader_no }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Key No') }}</label>
|
||||
<input type="text" name="key_no" value="{{ $machine->key_no }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@ -806,6 +806,15 @@
|
||||
placeholder="{{ __('Enter machine location') }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Key No') }}
|
||||
</label>
|
||||
<input type="text" name="key_no" class="luxury-input w-full"
|
||||
placeholder="{{ __('Enter key number') }}">
|
||||
</div>
|
||||
|
||||
<div class="relative focus-within:z-[60]">
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
@ -1385,6 +1394,12 @@
|
||||
<span class="text-sm font-black text-slate-700 dark:text-slate-300"
|
||||
x-text="currentMachine?.card_reader_no || '--'"></span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between p-2 border-b border-slate-50 dark:border-white/5">
|
||||
<span class="text-sm font-bold text-slate-500">{{ __('Key No') }}</span>
|
||||
<span class="text-sm font-black text-slate-700 dark:text-slate-300"
|
||||
x-text="currentMachine?.key_no || '--'"></span>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-3 p-3 mt-1 bg-slate-50 dark:bg-slate-800/40 rounded-xl border border-slate-100 dark:border-slate-700/50 relative">
|
||||
<div class="flex items-center justify-between">
|
||||
|
||||
@ -55,6 +55,8 @@
|
||||
{{ __('Owner') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Key No') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Card Reader') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">
|
||||
@ -96,6 +98,11 @@
|
||||
<td class="px-6 py-6">
|
||||
<x-status-badge :status="$status" size="sm" />
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-200">
|
||||
{{ $machine->key_no ?? '--' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<div class="text-sm font-bold text-slate-700 dark:text-slate-200">
|
||||
{{ $machine->card_reader_seconds ?? 0 }}s <span class="text-slate-300 dark:text-slate-700 mx-1.5">/</span> <span class="text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest">No.{{ $machine->card_reader_no ?? '--' }}</span>
|
||||
@ -126,7 +133,7 @@
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
@click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls'])))"
|
||||
@click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls', 'key_no'])))"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all"
|
||||
title="{{ __('View Details') }}">
|
||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@ -200,6 +207,10 @@
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Card Reader') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono">{{ $machine->card_reader_seconds ?? 0 }}s / No.{{ $machine->card_reader_no ?? '--' }}</p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Key No') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono">{{ $machine->key_no ?? '--' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Action Buttons --}}
|
||||
@ -229,7 +240,7 @@
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
@click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls'])))"
|
||||
@click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls', 'key_no'])))"
|
||||
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50"
|
||||
title="{{ __('Detail') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
|
||||
83
tests/Feature/Admin/BasicSettings/MachineSettingTest.php
Normal file
83
tests/Feature/Admin/BasicSettings/MachineSettingTest.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Admin\BasicSettings;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\System\User;
|
||||
use App\Models\Machine\Machine;
|
||||
use App\Models\Machine\MachineModel;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class MachineSettingTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected $admin;
|
||||
protected $machineModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// 建立擁有 menu.basic.machines 權限的系統管理員
|
||||
$this->admin = User::factory()->create(['company_id' => null]);
|
||||
$this->admin->givePermissionTo(Permission::create(['name' => 'menu.basic.machines']));
|
||||
|
||||
// 建立測試機台型號
|
||||
$this->machineModel = MachineModel::create([
|
||||
'name' => 'Test Model 01',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_system_admin_can_create_machine_with_key_no()
|
||||
{
|
||||
$payload = [
|
||||
'name' => 'New Test Machine',
|
||||
'serial_no' => 'TEST-SN-999',
|
||||
'machine_model_id' => $this->machineModel->id,
|
||||
'key_no' => 'KEY-999-ABC',
|
||||
'location' => 'Taipei Main Station',
|
||||
];
|
||||
|
||||
$response = $this->actingAs($this->admin)
|
||||
->post(route('admin.basic-settings.machines.store'), $payload);
|
||||
|
||||
$response->assertRedirect(route('admin.basic-settings.machines.index'));
|
||||
|
||||
$this->assertDatabaseHas('machines', [
|
||||
'name' => 'New Test Machine',
|
||||
'serial_no' => 'TEST-SN-999',
|
||||
'key_no' => 'KEY-999-ABC',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_system_admin_can_update_machine_key_no()
|
||||
{
|
||||
// 先建立一個預設機台
|
||||
$machine = Machine::factory()->create([
|
||||
'machine_model_id' => $this->machineModel->id,
|
||||
'key_no' => 'OLD-KEY-111',
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'name' => 'Updated Machine Name',
|
||||
'serial_no' => $machine->serial_no,
|
||||
'machine_model_id' => $this->machineModel->id,
|
||||
'card_reader_seconds' => 30,
|
||||
'payment_buffer_seconds' => 5,
|
||||
'invoice_status' => 0,
|
||||
'key_no' => 'NEW-KEY-888',
|
||||
];
|
||||
|
||||
$response = $this->actingAs($this->admin)
|
||||
->put(route('admin.basic-settings.machines.update', $machine), $payload);
|
||||
|
||||
$response->assertRedirect(route('admin.basic-settings.machines.index'));
|
||||
|
||||
$this->assertDatabaseHas('machines', [
|
||||
'id' => $machine->id,
|
||||
'key_no' => 'NEW-KEY-888',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user