Files
star-erp/.agents/skills/permission-management/SKILL.md

207 lines
6.7 KiB
Markdown
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.
---
name: 權限管理與實作規範
description: 為新功能實作權限控制的完整流程規範,包含後端 Seeder 設定、Middleware 路由保護與前端權限判斷。
---
# 權限管理與實作規範
本文件說明如何在新增功能時,一併實作完整的權限控制機制。專案採用 `spatie/laravel-permission` 套件進行權限管理。
---
## 1. 定義權限 (Backend Seeder)
所有權限皆定義於 `database/seeders/PermissionSeeder.php`
### 步驟:
1. 開啟 `database/seeders/PermissionSeeder.php`
2.`$permissions` 關聯陣列中新增功能對應的權限。
* **命名慣例**`{resource}.{action}`(例如:`system.view_logs`, `products.create`
* **格式**`'權限字串' => '中文動作名稱'`
* 常用動作:`view`, `create`, `edit`, `delete`, `approve`, `cancel`, `export`
3. 在下方「角色分配」區段,將新權限分配給適合的角色。
### 範例:
```php
// 1. 新增權限(注意:是 key => value 格式)
$permissions = [
// ... 現有權限
'utility_fees.view' => '檢視',
'utility_fees.create' => '建立',
'utility_fees.edit' => '編輯',
'utility_fees.delete' => '刪除',
];
// 2. 分配給角色
$admin->givePermissionTo([
// ... 現有權限
'utility_fees.view', 'utility_fees.create', 'utility_fees.edit', 'utility_fees.delete',
]);
```
### 現有角色定義:
| 角色 | 說明 | 權限範圍 |
|---|---|---|
| `super-admin` | 系統管理員 | 自動擁有所有權限(`Permission::all()` |
| `admin` | 一般管理員 | 大部分權限(除角色管理外) |
| `warehouse-manager` | 倉庫管理員 | 庫存、盤點、調撥、進貨、門市叫貨 |
| `purchaser` | 採購人員 | 商品檢視、採購單、退貨、供應商、進貨 |
| `viewer` | 檢視人員 | 僅限各模組的 `.view` 權限 |
---
## 2. 套用資料庫變更 (Multi-tenancy)
修改 Seeder 後,必須在**中央與所有租戶**同步執行。
```bash
# 對所有租戶執行 Seeder
./vendor/bin/sail php artisan tenants:seed --class=PermissionSeeder
```
> [!WARNING]
> 僅執行 `db:seed` 只會更新中央資料庫。務必使用 `tenants:seed` 確保所有租戶同步。
---
## 3. 路由保護 (Backend Middleware)
路由保護定義在各模組自己的 `app/Modules/{ModuleName}/Routes/web.php` 中。
> [!IMPORTANT]
> 路由檔在各模組內(如 `app/Modules/Finance/Routes/web.php`**不是**全域的 `routes/web.php`。
### 範例:
```php
// 單一權限保護
Route::middleware('permission:utility_fees.view')->group(function () {
Route::get('/utility-fees', [UtilityFeeController::class, 'index'])->name('utility-fees.index');
Route::get('/utility-fees/{utilityFee}', [UtilityFeeController::class, 'show'])->name('utility-fees.show');
});
// 巢狀權限群組
Route::middleware('permission:utility_fees.create')->group(function () {
Route::get('/utility-fees/create', [UtilityFeeController::class, 'create'])->name('utility-fees.create');
Route::post('/utility-fees', [UtilityFeeController::class, 'store'])->name('utility-fees.store');
});
// 單行 middleware
Route::delete('/utility-fees/{utilityFee}', [UtilityFeeController::class, 'destroy'])
->middleware('permission:utility_fees.delete')
->name('utility-fees.destroy');
```
---
## 4. 配置權限群組名稱 (Backend UI Config)
為了讓新權限在「角色與權限」管理介面中正確分組並顯示中文標題,需修改 Controller。
**位置**: `app/Modules/Core/Controllers/RoleController.php``getGroupedPermissions()`
```php
$groupDefinitions = [
'products' => '商品資料管理',
'warehouses' => '倉庫管理',
'inventory' => '庫存資料管理',
// ...
'utility_fees' => '公共事業費管理', // ✅ 新增此行
];
```
> [!NOTE]
> 未加入 `$groupDefinitions` 的權限群組仍會顯示,但標題會以原始 key英文呈現。
---
## 5. 前端權限判斷 (React)
### 5.1 方式一:`usePermission` Hook在邏輯中判斷
**位置**: `resources/js/hooks/usePermission.ts`
```tsx
import { usePermission } from "@/hooks/usePermission";
export default function ProductIndex() {
const { can, canAny, isSuperAdmin } = usePermission();
return (
<div>
{can('products.create') && <Button></Button>}
{canAny(['products.edit', 'products.delete']) && <ManageDropdown />}
</div>
);
}
```
#### Hook 完整介面:
| 方法 | 說明 |
|---|---|
| `can(permission)` | 檢查是否擁有**指定**權限 |
| `canAny(permissions[])` | 檢查是否擁有**任一**權限 |
| `canAll(permissions[])` | 檢查是否擁有**所有**權限 |
| `hasRole(role)` | 檢查是否擁有**指定**角色 |
| `hasAnyRole(roles[])` | 檢查是否擁有**任一**角色 |
| `hasAllRoles(roles[])` | 檢查是否擁有**所有**角色 |
| `isSuperAdmin()` | 是否為超級管理員 |
> 所有方法對 `super-admin` 角色自動回傳 `true`。
### 5.2 方式二:`<Can>` / `<HasRole>` / `<CanAll>` 元件(在 JSX 中包裹)
**位置**: `resources/js/Components/Permission/Can.tsx`
```tsx
import { Can, HasRole, CanAll } from '@/Components/Permission/Can';
// 單一權限
<Can permission="products.create">
<Button></Button>
</Can>
// 任一權限OR 邏輯)
<Can permission={['products.edit', 'products.delete']}>
<ManageDropdown />
</Can>
// 所有權限都必須有AND 邏輯)
<CanAll permissions={['products.edit', 'products.delete']}>
<Button></Button>
</CanAll>
// 角色判斷
<HasRole role="admin">
<Link href="/admin"></Link>
</HasRole>
// Fallback 支援
<Can permission="products.delete" fallback={<span className="text-gray-400"></span>}>
<Button variant="destructive"></Button>
</Can>
```
> [!IMPORTANT]
> UI 規範要求:所有可操作按鈕(新增、編輯、刪除)**必須**包裹 `<Can>` 元件或使用 `can()` 判斷。
> 詳見 [UI 統一規範](file:///home/mama/projects/star-erp/.agents/skills/ui-consistency/SKILL.md)。
---
## 6. 開發檢核清單 (Checklist)
### 後端
- [ ] `PermissionSeeder.php` 已新增權限字串(`'key' => '中文動作名稱'` 格式)。
- [ ] `PermissionSeeder.php` 已將新權限分配給 `admin` 及其他適用角色。
- [ ] 已執行 `./vendor/bin/sail php artisan tenants:seed --class=PermissionSeeder` 同步所有租戶。
- [ ] `RoleController.php``$groupDefinitions` 已新增權限群組中文名稱。
- [ ] 模組路由 (`app/Modules/{ModuleName}/Routes/web.php`) 已加上 `middleware('permission:...')` 保護。
### 前端
- [ ] 頁面按鈕已使用 `usePermission` Hook 或 `<Can>` 元件進行權限控制。
- [ ] 所有可操作按鈕都包裹於權限判斷中(符合 UI 統一規範)。