# 零件库存权限配置指南

## 📋 问题说明

用户访问零件库存概览页面时遇到 **403 Forbidden** 错误：

```
AxiosError: Request failed with status code 403
at async loadStats (src/app/parts/page.tsx:32:24)
```

## 🔍 原因分析

### 后端权限要求

后端的 `/parts/stats/inventory` 接口需要 `parts:read` 权限：

```typescript
// backend/src/parts/parts.controller.ts
@Get('stats/inventory')
@RequirePermissions('parts:read')  // ⚠️ 需要此权限
async getInventoryStats(
  @Query('station') station?: string,
  @Query('warehouseLocation') warehouseLocation?: string,
  @Query('category') category?: string,
) {
  return this.partsService.getInventoryStats({
    station,
    warehouseLocation,
    category,
  });
}
```

### 权限检查流程

```
用户访问页面
    ↓
调用 partsApi.getStats()
    ↓
请求 GET /parts/stats/inventory
    ↓
后端检查权限
    ↓
如果没有 parts:read 权限
    ↓
返回 403 Forbidden ❌
```

## ✅ 解决方案

### 方案 1: 前端优雅降级（已实现）✨

**特点**：即使没有权限，页面也能正常显示，只是数据显示为 0

**实现**：

```typescript
// frontend/src/app/parts/page.tsx
const loadStats = async () => {
  try {
    setLoading(true);
    setError(null);
    const response = await partsApi.getStats();
    setStats(response.data);
  } catch (error: any) {
    console.error('Failed to load stats:', error);
    if (error.response?.status === 403) {
      // 显示友好提示
      setError(t.parts.noPermissionToViewStats);
      // 使用默认值
      setStats({
        totalParts: 0,
        activeParts: 0,
        lowStockParts: 0,
        outOfStockParts: 0,
      });
    } else {
      setError(t.common.error);
    }
  } finally {
    setLoading(false);
  }
};
```

**效果**：

```
┌─────────────────────────────────────────────────────┐
│ ⚠️  您没有权限查看统计数据，请联系管理员分配权限      │
└─────────────────────────────────────────────────────┘

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 总零件数      │ │ 在用零件      │ │ 低库存零件    │
│      0       │ │      0       │ │      0       │
└──────────────┘ └──────────────┘ └──────────────┘
```

### 方案 2: 分配权限给用户 🔐

#### 2.1 查看当前用户权限

```sql
-- 查看用户及其角色
SELECT 
  u.username,
  u.email,
  r.name as role_name
FROM platform_iam."User" u
LEFT JOIN platform_iam."_UserRole" ur ON u.id = ur."userId"
LEFT JOIN platform_iam."Role" r ON ur."roleId" = r.id
WHERE u.username = '你的用户名';
```

#### 2.2 创建零件管理权限

```sql
-- 创建零件相关权限
INSERT INTO platform_iam."Permission" (id, resource, action, description)
VALUES 
  (gen_random_uuid(), 'parts', 'read', '查看零件信息'),
  (gen_random_uuid(), 'parts', 'create', '创建零件'),
  (gen_random_uuid(), 'parts', 'update', '编辑零件'),
  (gen_random_uuid(), 'parts', 'delete', '删除零件'),
  (gen_random_uuid(), 'inventory', 'read', '查看库存'),
  (gen_random_uuid(), 'inventory', 'write', '操作库存');
```

#### 2.3 分配权限给角色

```sql
-- 获取权限ID
SELECT id, resource, action FROM platform_iam."Permission"
WHERE resource IN ('parts', 'inventory');

-- 分配给管理员角色
INSERT INTO platform_iam."_RolePermission" ("roleId", "permissionId")
VALUES 
  ('管理员角色ID', '零件读取权限ID'),
  ('管理员角色ID', '零件创建权限ID'),
  ('管理员角色ID', '零件更新权限ID'),
  ('管理员角色ID', '库存读取权限ID'),
  ('管理员角色ID', '库存写入权限ID');
```

#### 2.4 通过管理界面分配

1. 登录管理后台
2. 进入 **组织架构** → **角色管理**
3. 选择要分配权限的角色
4. 在权限列表中勾选：
   - ✅ 零件:读取 (parts:read)
   - ✅ 零件:创建 (parts:create)
   - ✅ 零件:更新 (parts:update)
   - ✅ 库存:读取 (inventory:read)
   - ✅ 库存:写入 (inventory:write)
5. 保存

### 方案 3: 临时放宽权限（不推荐）⚠️

**仅用于开发测试**：

```typescript
// backend/src/parts/parts.controller.ts
@Get('stats/inventory')
// @RequirePermissions('parts:read')  // 临时注释掉
async getInventoryStats(
  @Query('station') station?: string,
  @Query('warehouseLocation') warehouseLocation?: string,
  @Query('category') category?: string,
) {
  return this.partsService.getInventoryStats({
    station,
    warehouseLocation,
    category,
  });
}
```

⚠️ **注意**：这会移除所有权限检查，仅用于开发调试！

## 🎯 推荐的权限配置

### 零件管理员角色

```
零件管理员 (Parts Manager)
├─ parts:read      ✅ 查看零件
├─ parts:create    ✅ 创建零件
├─ parts:update    ✅ 编辑零件
├─ parts:delete    ✅ 删除零件
├─ inventory:read  ✅ 查看库存
└─ inventory:write ✅ 操作库存
```

### 零件操作员角色

```
零件操作员 (Parts Operator)
├─ parts:read      ✅ 查看零件
├─ inventory:read  ✅ 查看库存
└─ inventory:write ✅ 操作库存
```

### 零件查看员角色

```
零件查看员 (Parts Viewer)
├─ parts:read      ✅ 查看零件
└─ inventory:read  ✅ 查看库存
```

## 📝 需要权限的 API 端点

### 零件管理 (Parts)

| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/parts` | GET | `parts:read` | 列表查询 |
| `/parts/:id` | GET | `parts:read` | 详情查询 |
| `/parts/stats/inventory` | GET | `parts:read` | 统计数据 ⚠️ |
| `/parts/meta/*` | GET | `parts:read` | 元数据 |
| `/parts` | POST | `parts:create` | 创建零件 |
| `/parts/:id` | PUT | `parts:update` | 更新零件 |
| `/parts/:id` | DELETE | `parts:delete` | 删除零件 |
| `/parts/bulk-import` | POST | `parts:create` | 批量导入 |

### 库存管理 (Inventory)

| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/inventory/logs` | GET | `inventory:read` | 操作日志 |
| `/inventory/history/:id` | GET | `inventory:read` | 历史记录 |
| `/inventory/stats` | GET | `inventory:read` | 统计数据 |
| `/inventory/check-in` | POST | `inventory:write` | 入库 |
| `/inventory/check-out` | POST | `inventory:write` | 出库 |
| `/inventory/adjust` | POST | `inventory:write` | 调整 |
| `/inventory/transfer` | POST | `inventory:write` | 转移 |

### 标签管理 (Labels)

| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/labels/*` | GET | `parts:read` | 查询标签 |
| `/labels/generate` | POST | `parts:create` | 生成标签 |
| `/labels/print` | POST | `parts:update` | 打印标签 |

### 告警管理 (Alerts)

| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/alerts` | GET | `inventory:read` | 查询告警 |
| `/alerts/acknowledge` | POST | `inventory:write` | 确认告警 |
| `/alerts/resolve` | POST | `inventory:write` | 解决告警 |

## 🚀 快速配置脚本

创建一个数据库脚本来快速配置权限：

```sql
-- 1. 创建零件管理权限
WITH parts_permissions AS (
  INSERT INTO platform_iam."Permission" (id, resource, action, description)
  VALUES 
    (gen_random_uuid(), 'parts', 'read', '查看零件信息'),
    (gen_random_uuid(), 'parts', 'create', '创建零件'),
    (gen_random_uuid(), 'parts', 'update', '编辑零件'),
    (gen_random_uuid(), 'parts', 'delete', '删除零件'),
    (gen_random_uuid(), 'inventory', 'read', '查看库存'),
    (gen_random_uuid(), 'inventory', 'write', '操作库存')
  ON CONFLICT (resource, action) DO NOTHING
  RETURNING id, resource, action
)
SELECT * FROM parts_permissions;

-- 2. 创建零件管理员角色
INSERT INTO platform_iam."Role" (id, name, description)
VALUES (gen_random_uuid(), '零件管理员', '负责零件和库存的全面管理')
ON CONFLICT (name) DO NOTHING;

-- 3. 获取角色和权限ID，然后分配
-- (需要替换为实际的ID)
```

## 💡 常见问题

### Q1: 为什么我有管理员角色还是没权限？

**A**: 管理员角色可能没有分配 `parts:read` 权限。需要检查：

```sql
-- 检查角色拥有的权限
SELECT p.resource, p.action, p.description
FROM platform_iam."Permission" p
JOIN platform_iam."_RolePermission" rp ON p.id = rp."permissionId"
JOIN platform_iam."Role" r ON rp."roleId" = r.id
WHERE r.name = '管理员';
```

### Q2: 页面显示 0 是正常的吗？

**A**: 如果看到黄色提示框说明没有权限，显示 0 是优雅降级的结果。需要联系管理员分配权限。

### Q3: 如何临时测试功能？

**A**: 在开发环境可以临时注释掉 `@RequirePermissions` 装饰器，但生产环境必须保留权限检查！

## 🎉 总结

1. ✅ **前端已实现优雅降级**：没有权限时显示友好提示
2. 🔐 **推荐方案**：分配正确的权限给用户角色
3. ⚠️ **安全第一**：不要在生产环境移除权限检查
4. 📚 **参考文档**：查看 `PERMISSION_SYSTEM_GUIDE.md` 了解更多

