[FEAT] 實作機台物理刪除功能與資料庫結構優化

1. 於機台設定頁面新增刪除功能,並限制僅系統管理員 (Super Admin) 可操作。
2. 移除機台模型 (Machine) 的 SoftDeletes Trait,將刪除邏輯改為物理刪除。
3. 建立並執行 Migration 移除 machines 資料表的 deleted_at 欄位。
4. 清除資料庫中原有的 51 筆機台軟刪除紀錄。
5. 更新繁中、英文、日文語系檔,加入機台刪除成功與確認視窗之翻譯。
6. 更新 B000 管理員登入 API 之回應格式,統一包含 success 與 code 欄位。
7. 同步更新 API 技術規格文件 (SKILL.md) 與 api-docs.php 配置。
8. 微調商品新增/編輯頁面之 UI 元件 z-index 層級。
This commit is contained in:
sky121113 2026-05-06 15:47:58 +08:00
parent 5f102fbd7a
commit d740fc5ab8
13 changed files with 103 additions and 12 deletions

View File

@ -45,13 +45,13 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
| ip | String | 否 | 機台本地 IP | 192.168.1.100 |
- **Response Body:**
> [!IMPORTANT]
> 為了相容 Java APP 現有邏輯,這裡嚴格規定成功必須回傳字串 Success。
| 參數 | 類型 | 說明 | 範例 |
| :--- | :--- | :--- | :--- |
| success | Boolean | 請求是否成功 | true |
| code | Integer | 業務狀態碼 | 200 |
| message | String | 驗證結果 (Success 或 Failed) | Success |
| token | String | **臨時身份認證 Token** (可選,供未來擴充使用) | 1|abcdefg... |
| token | String | **臨時身份認證 Token** (可選,供未來擴充使用) | 1\|abcdefg... |
---

View File

@ -375,6 +375,21 @@ class MachineSettingController extends AdminController
]);
}
/**
* 刪除機台 (僅限系統管理員)
*/
public function destroy(Machine $machine): RedirectResponse
{
if (!auth()->user()->isSystemAdmin()) {
abort(403);
}
$machine->delete();
return redirect()->route('admin.basic-settings.machines.index')
->with('success', __('Machine deleted successfully.'));
}
/**
* 公開機台分布地圖
*/

View File

@ -51,7 +51,11 @@ class MachineAuthController extends Controller
'login'
);
return response()->json(['message' => 'Failed']);
return response()->json([
'success' => false,
'code' => 401,
'message' => 'Failed'
], 401);
}
// 5. RBAC 權限驗證 (遵循多租戶與機台授權規範)
@ -83,7 +87,11 @@ class MachineAuthController extends Controller
'login'
);
return response()->json(['message' => 'Forbidden']);
return response()->json([
'success' => false,
'code' => 403,
'message' => 'Forbidden'
], 403);
}
// 6. 驗證完美通過!
@ -103,6 +111,8 @@ class MachineAuthController extends Controller
);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Success',
'token' => $user->createToken('technician-setup', ['*'], now()->addHours(8))->plainTextToken
]);

View File

@ -10,7 +10,6 @@ use App\Traits\TenantScoped;
class Machine extends Model
{
use HasFactory, TenantScoped;
use \Illuminate\Database\Eloquent\SoftDeletes;
protected static function booted()
{

View File

@ -40,6 +40,16 @@ return [
],
],
'response_parameters' => [
'success' => [
'type' => 'boolean',
'description' => '請求是否成功',
'example' => true
],
'code' => [
'type' => 'integer',
'description' => '業務狀態碼',
'example' => 200
],
'message' => [
'type' => 'string',
'description' => '回應訊息',
@ -57,6 +67,8 @@ return [
'ip' => '192.168.1.100'
],
'response' => [
'success' => true,
'code' => 200,
'message' => 'Success',
'token' => '1|abcdefg...'
],

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('machines', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('machines', function (Blueprint $table) {
$table->softDeletes();
});
}
};

View File

@ -138,6 +138,7 @@
"Are you sure you want to remove this assignment?": "Are you sure you want to remove this assignment?",
"Are you sure you want to send this command?": "Are you sure you want to send this command?",
"Are you sure you want to start delivery?": "Are you sure you want to start delivery?",
"Are you sure you want to delete this machine? This action cannot be undone.": "Are you sure you want to delete this machine? This action cannot be undone.",
"Are you sure?": "Are you sure?",
"Assign": "Assign",
"Assign Advertisement": "Assign Advertisement",
@ -715,6 +716,7 @@
"Machine to Warehouse": "Machine to Warehouse",
"Machine to warehouse returns": "Machine to warehouse returns",
"Machine updated successfully.": "Machine updated successfully.",
"Machine deleted successfully.": "Machine deleted successfully.",
"Machines": "Machines",
"Machines Online": "Machines Online",
"Main": "Main",

View File

@ -138,6 +138,7 @@
"Are you sure you want to remove this assignment?": "Are you sure you want to remove this assignment?",
"Are you sure you want to send this command?": "Are you sure you want to send this command?",
"Are you sure you want to start delivery?": "配送を開始しますか?",
"Are you sure you want to delete this machine? This action cannot be undone.": "この機台を削除してもよろしいですか?この操作は取り消せません。",
"Are you sure?": "Are you sure?",
"Assign": "Assign",
"Assign Advertisement": "Assign Advertisement",
@ -714,6 +715,7 @@
"Machine to Warehouse": "機台から倉庫",
"Machine to warehouse returns": "Machine to warehouse returns",
"Machine updated successfully.": "Machine updated successfully.",
"Machine deleted successfully.": "機台が正常に削除されました。",
"Machines": "Machines",
"Machines Online": "Machines Online",
"Main": "総倉庫",

View File

@ -142,7 +142,8 @@
"Are you sure you want to resolve all recent issues for this machine?": "您確定要排除此機台目前所有的異常紀錄嗎?這將會恢復機台狀態為正常。",
"Are you sure you want to send this command?": "確定要發送此指令嗎?",
"Are you sure you want to start delivery?": "確定要開始配送嗎?",
"Are you sure?": "確定要執行此操作嗎?",
"Are you sure you want to delete this machine? This action cannot be undone.": "您確定要刪除此機台嗎?此操作將無法復原。",
"Are you sure?": "您確定嗎?",
"Assign": "分配所屬機台",
"Assign Advertisement": "投放廣告",
"Assign Machines": "分配機台",
@ -815,7 +816,8 @@
"Machine to Warehouse": "機台對倉庫",
"Machine to warehouse returns": "機台退回倉庫",
"Machine updated successfully.": "機台更新成功。",
"Machines": "機台列表",
"Machine deleted successfully.": "機台已成功刪除。",
"Machines": "機台管理",
"Machines Online": "在線機台數",
"Main": "總倉",
"Main Warehouses": "總倉數量",

View File

@ -134,6 +134,16 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
@if(auth()->user()->isSystemAdmin())
<button type="button"
@click="confirmDelete('{{ route('admin.basic-settings.machines.destroy', $machine) }}')"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 hover:bg-rose-500/5 dark:hover:bg-rose-500/10 border border-transparent hover:border-rose-500/20 transition-all"
title="{{ __('Delete') }}">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</button>
@endif
</div>
</td>
</tr>
@ -226,6 +236,16 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
@if(auth()->user()->isSystemAdmin())
<button type="button"
@click="confirmDelete('{{ route('admin.basic-settings.machines.destroy', $machine) }}')"
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 text-xs hover:bg-rose-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
{{ __('Delete') }}
</button>
@endif
</div>
</div>
@empty

View File

@ -47,7 +47,7 @@
<!-- Side Column (Status & Company) -->
<aside class="w-full lg:w-80 lg:sticky top-24 z-10 space-y-8 text-slate-800 dark:text-white">
<!-- Product Image -->
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[70]" style="animation-delay: 50ms">
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[60]" style="animation-delay: 50ms">
<div class="space-y-6">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Product Image') }}</label>
@ -81,7 +81,7 @@
</div>
</div>
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[60]" style="animation-delay: 100ms">
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[70]" style="animation-delay: 100ms">
<div class="space-y-6">
<div class="space-y-3">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Active Status') }}</label>

View File

@ -61,7 +61,7 @@
<!-- Side Column (Status & Company) -->
<aside class="w-full lg:w-80 lg:sticky top-24 z-10 space-y-8">
<!-- Product Image -->
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[70]" style="animation-delay: 50ms">
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[60]" style="animation-delay: 50ms">
<div class="space-y-6">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Product Image') }}</label>
@ -98,7 +98,7 @@
</div>
<!-- Status & Company -->
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[60]" style="animation-delay: 100ms">
<div class="luxury-card p-8 rounded-[2.5rem] animate-luxury-in relative z-[70]" style="animation-delay: 100ms">
<div class="space-y-6">
<div class="space-y-3">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Active Status') }}</label>

View File

@ -239,6 +239,7 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
Route::get('/{machine}/edit', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'edit'])->name('edit');
Route::put('/{machine}', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'update'])->name('update');
Route::post('/', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'store'])->name('store');
Route::delete('/{machine}', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'destroy'])->name('destroy');
Route::post('/{machine}/regenerate-token', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'regenerateToken'])->name('regenerate-token');
Route::patch('/{machine}/update-system-settings', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'updateSystemSettings'])->name('update-system-settings');