# 零件管理 API

**模块**: Parts  
**接口数量**: 10  
**Base URL**: `/api/v1/parts`

> **✨ 升级说明**: 采用统一标识符设计，通过 `:partIdentifier` 智能识别 UUID/编号/条码，简化API使用

---

## 概述

零件管理模块提供零件的完整生命周期管理，包括创建、查询、更新、删除、批量导入等功能。

**核心功能**:
- ✅ 零件CRUD操作
- ✅ 智能标识符查询（UUID/编号/条码自动识别）
- ✅ 多维度查询（按编号、条码、分类等）
- ✅ 批量导入
- ✅ 库存统计
- ✅ 元数据管理（分类、工位、仓位）

**权限要求**: 
- 查询: `parts:read`
- 创建: `parts:create`
- 更新: `parts:update`
- 删除: `parts:delete`

**接口数量**: 10 个（通过统一标识符简化为 10 个，原 12 个）

---

## 路径参数命名规范

### 统一标识符：`:partIdentifier`

为保持 API 的一致性和可读性，零件管理 API 使用统一的 `partIdentifier` 参数。

**设计理念**：
- ✅ **统一概念**：所有零件相关路由使用 `:partIdentifier`
- ✅ **灵活查询**：支持多种查询方式（UUID/编号/条码）
- ✅ **降低认知成本**：前端只需记住一个规则
- ✅ **智能识别**：后端自动识别参数类型

**支持的查询方式**（按优先级）：
1. **UUID** (id) - 数据库主键
2. **零件编号** (partNumber) - 业务唯一标识
3. **条码** (qrCode/barcode) - 扫码场景

**示例**：
```bash
# 所有以下方式都有效，都是获取同一个零件的详情
GET /api/v1/parts/550e8400-e29b-41d4-a716-446655440000  # UUID
GET /api/v1/parts/P001                                  # 零件编号
GET /api/v1/parts/QR20251120001                         # 二维码
GET /api/v1/parts/BC123456789                           # 条码
```

### 识别优先级

后端在处理 `:partIdentifier` 参数时，按以下顺序尝试识别：

```typescript
async function resolvePartIdentifier(identifier: string): Promise<Part> {
  // 1. 尝试 UUID（最高优先级）
  if (isUUID(identifier)) {
    const part = await prisma.part.findUnique({ 
      where: { id: identifier, deletedAt: null } 
    });
    if (part) return part;
  }
  
  // 2. 尝试零件编号
  let part = await prisma.part.findUnique({ 
    where: { partNumber: identifier, deletedAt: null } 
  });
  if (part) return part;
  
  // 3. 尝试二维码或条码
  part = await prisma.part.findFirst({
    where: { 
      OR: [
        { qrCode: identifier },
        { barcode: identifier }
      ],
      deletedAt: null
    }
  });
  if (part) return part;
  
  // 4. 未找到
  throw new NotFoundException(`Part '${identifier}' not found`);
}
```

---

## 接口列表

### 1. 创建零件

**URL**: `POST /parts`  
**权限**: `parts:create`  

#### 请求参数

```json
{
  "partNumber": "P001",           // 必填，零件编号（唯一）
  "partName": "机械臂",           // 必填，零件名称
  "partNameCn": "机械臂组件",     // 可选，中文名称
  "unit": "pcs",                  // 可选，单位（默认: pcs）
  "source": "供应商A",            // 可选，来源
  "specifications": "规格说明",   // 可选，规格
  "remark": "备注信息",           // 可选，备注
  "station": "A01",               // 可选，工位
  "warehouseLocation": "W-A-01",  // 可选，仓位
  "currentStock": 100,            // 可选，当前库存（默认: 0）
  "minStock": 10,                 // 可选，最小库存（默认: 0）
  "maxStock": 500                 // 可选，最大库存
}
```

#### 响应示例

```json
{
  "success": true,
  "data": {
    "id": "uuid",
    "partNumber": "P001",
    "partName": "机械臂",
    "currentStock": 100,
    "minStock": 10,
    "createdAt": "2025-11-20T10:30:00.000Z"
  },
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 2. 查询零件列表

**URL**: `GET /parts`  
**权限**: `parts:read`

#### Query参数

| 参数 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| `page` | number | 否 | 页码（默认: 1）|
| `limit` | number | 否 | 每页数量（默认: 20）|
| `search` | string | 否 | 搜索关键词（零件编号、名称）|
| `category` | string | 否 | 分类筛选 |
| `station` | string | 否 | 工位筛选 |
| `warehouseLocation` | string | 否 | 仓位筛选 |
| `stockStatus` | string | 否 | 库存状态筛选: `NORMAL`\|`LOW`\|`OUT_OF_STOCK` |

#### 响应示例

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "uuid",
        "partNumber": "P001",
        "partName": "机械臂",
        "currentStock": 100,
        "minStock": 10,
        "status": "ACTIVE",
        "stockStatus": "NORMAL"
      }
    ],
    "total": 100,
    "page": 1,
    "limit": 20,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  },
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 3. 获取零件详情

通过 UUID、零件编号或条码获取零件详情。

**URL**: `GET /parts/:partIdentifier`  
**权限**: `parts:read`

#### Path 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| partIdentifier | string | 零件标识符（支持 UUID/零件编号/二维码/条码） |

#### 查询方式示例

```bash
# 通过 UUID 查询
GET /parts/550e8400-e29b-41d4-a716-446655440000

# 通过零件编号查询
GET /parts/P001

# 通过二维码查询
GET /parts/QR20251120001

# 通过条码查询
GET /parts/BC123456789
```

#### 响应示例

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "partNumber": "P001",
    "partName": "机械臂",
    "partNameCn": "机械臂组件",
    "unit": "pcs",
    "source": "供应商A",
    "specifications": "规格说明",
    "station": "A01",
    "warehouseLocation": "W-A-01",
    "currentStock": 100,
    "minStock": 10,
    "maxStock": 500,
    "qrCode": "QR20251120001",
    "barcode": "BC123456789",
    "imageUrl": "https://example.com/parts/P001.jpg",
    "status": "ACTIVE",
    "stockStatus": "NORMAL",
    "createdAt": "2025-11-20T10:30:00.000Z",
    "updatedAt": "2025-11-20T10:30:00.000Z",
    "createdBy": "user-uuid",
    "updatedBy": "user-uuid"
  },
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

**说明**：
- 后端会自动识别 `partIdentifier` 的类型
- UUID格式优先匹配数据库ID
- 其他格式按 零件编号 → 二维码/条码 顺序查询
- 常用于扫码场景，无需指定查询类型

---

### 4. 更新零件

**URL**: `PUT /parts/:partIdentifier`  
**权限**: `parts:update`

#### Path 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| partIdentifier | string | 零件标识符（支持 UUID/零件编号/二维码/条码） |

#### 请求参数

所有字段可选，提供需要更新的字段即可。

```json
{
  "partName": "新名称",
  "currentStock": 150,
  "minStock": 20
}
```

#### 响应示例

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "partNumber": "P001",
    "partName": "新名称",
    "currentStock": 150,
    "minStock": 20,
    "updatedAt": "2025-11-20T10:35:00.000Z"
  },
  "message": "零件更新成功",
  "timestamp": "2025-11-20T10:35:00.000Z"
}
```

---

### 5. 删除零件

**URL**: `DELETE /parts/:partIdentifier`  
**权限**: `parts:delete`

#### Path 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| partIdentifier | string | 零件标识符（支持 UUID/零件编号/二维码/条码） |

#### 响应

```
HTTP 204 No Content
```

**注意**: 
- 软删除，不会真正删除数据
- 有库存的零件无法删除
- 有关联标签的零件无法删除

---

### 6. 批量导入零件

**URL**: `POST /parts/bulk-import`  
**权限**: `parts:create`

#### 请求参数

```json
{
  "parts": [
    {
      "partNumber": "P001",
      "partName": "零件1",
      "unit": "pcs"
    },
    {
      "partNumber": "P002",
      "partName": "零件2",
      "unit": "pcs"
    }
  ],
  "updateExisting": false  // 是否更新已存在的零件
}
```

#### 响应示例

```json
{
  "success": true,
  "data": {
    "total": 2,
    "created": 2,
    "updated": 0,
    "failed": 0,
    "errors": []
  },
  "message": "批量导入完成",
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 7. 获取库存统计

**URL**: `GET /parts/stats/inventory`  
**权限**: `parts:read`

#### Query参数

- `station`: string (可选) - 按工位统计
- `warehouseLocation`: string (可选) - 按仓位统计  
- `category`: string (可选) - 按分类统计

#### 响应示例

```json
{
  "success": true,
  "data": {
    "totalParts": 150,
    "totalStock": 5000,
    "normalStock": 120,
    "lowStock": 25,
    "outOfStock": 5,
    "byStation": {
      "A01": 30,
      "A02": 40
    }
  },
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 8. 获取所有分类

**URL**: `GET /parts/meta/categories`  
**权限**: `parts:read`

#### 响应示例

```json
{
  "success": true,
  "data": [
    "机械件",
    "电子件",
    "耗材"
  ],
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 9. 获取所有工位

**URL**: `GET /parts/meta/stations`  
**权限**: `parts:read`

#### 响应示例

```json
{
  "success": true,
  "data": [
    "A01", "A02", "B01", "B02"
  ],
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

### 10. 获取所有仓位

**URL**: `GET /parts/meta/warehouse-locations`  
**权限**: `parts:read`

#### 响应示例

```json
{
  "success": true,
  "data": [
    "W-A-01", "W-A-02", "W-B-01"
  ],
  "timestamp": "2025-11-20T10:30:00.000Z"
}
```

---

## 数据模型

### Part对象

```typescript
interface Part {
  // 基础信息
  id: string;                    // UUID
  partNumber: string;            // 零件编号（唯一）
  partName: string;              // 零件名称
  partNameCn?: string;           // 中文名称
  unit: string;                  // 单位（默认: pcs）
  source?: string;               // 来源/供应商
  specifications?: string;       // 规格说明
  remark?: string;               // 备注
  
  // 位置信息
  station?: string;              // 工位
  warehouseLocation?: string;    // 仓位
  
  // 库存信息
  currentStock: number;          // 当前库存
  minStock: number;              // 最小库存（触发低库存告警）
  maxStock?: number;             // 最大库存
  
  // 标识码
  qrCode?: string;               // 二维码（系统生成）
  barcode?: string;              // 条码
  imageUrl?: string;             // 图片URL
  
  // 状态
  status: PartStatus;            // 零件状态（ACTIVE/INACTIVE）
  stockStatus: StockStatus;      // 库存状态（计算字段，自动生成）
  
  // 审计字段
  createdAt: string;             // 创建时间 (ISO 8601)
  updatedAt: string;             // 更新时间 (ISO 8601)
  createdBy?: string;            // 创建人ID
  updatedBy?: string;            // 更新人ID
  deletedAt?: string;            // 删除时间（软删除，仅内部使用）
  
  // 关联关系（详情查询时包含）
  labels?: Label[];              // 关联的标签
  stockAlerts?: StockAlert[];    // 库存告警
  groups?: PartGroup[];          // 所属分组
}
```

### 零件状态枚举（数据库字段）

```typescript
enum PartStatus {
  ACTIVE = 'ACTIVE',       // 启用（可用）
  INACTIVE = 'INACTIVE',   // 停用（不可用）
}
```

**说明**: 
- `status` 表示零件本身的启用/停用状态
- 库存状态是动态计算的，不存储在数据库中

### 库存状态（计算字段）

`stockStatus` 是由后端根据 `currentStock` 和 `minStock` 动态计算的字段，在所有返回零件信息的接口中自动提供。

```typescript
enum StockStatus {
  NORMAL = 'NORMAL',           // currentStock > minStock
  LOW = 'LOW',                 // 0 < currentStock <= minStock
  OUT_OF_STOCK = 'OUT_OF_STOCK' // currentStock <= 0
}
```

**计算逻辑**:
```typescript
function getStockStatus(part: Part): StockStatus {
  if (part.currentStock <= 0) return StockStatus.OUT_OF_STOCK;
  if (part.currentStock <= part.minStock) return StockStatus.LOW;
  return StockStatus.NORMAL;
}
```

**使用场景**:
- ✅ 列表展示：直接显示库存状态标签
- ✅ 库存预警：筛选 `LOW` 或 `OUT_OF_STOCK` 状态
- ✅ 仪表盘：统计各状态零件数量
- ✅ 无需前端计算：后端统一提供，保证一致性

---

## 使用示例

### JavaScript示例

```javascript
import apiClient from '@/lib/api-client';

// 创建零件
const createPart = async () => {
  const part = await apiClient.post('/parts', {
    partNumber: 'P001',
    partName: '机械臂',
    unit: 'pcs',
    minStock: 10,
    currentStock: 100
  });
  console.log('创建成功:', part);
};

// 查询零件列表（带筛选和分页）
const getParts = async () => {
  const result = await apiClient.get('/parts', {
    params: {
      page: 1,
      limit: 20,
      search: '机械',
      stockStatus: 'LOW'  // 筛选库存偏低的零件
    }
  });
  console.log('零件列表:', result.items);
  // 可以直接使用 stockStatus 字段
  result.items.forEach(part => {
    console.log(`${part.partName}: ${part.stockStatus}`);
  });
};

// 通过零件编号查询（使用统一标识符）
const getPartByNumber = async (partNumber) => {
  const part = await apiClient.get(`/parts/${partNumber}`);  // 自动识别为编号
  console.log('零件详情:', part);
};

// 通过UUID查询
const getPartById = async (id) => {
  const part = await apiClient.get(`/parts/${id}`);  // 自动识别为UUID
  console.log('零件详情:', part);
};

// 通过扫码查询
const getPartByCode = async (code) => {
  const part = await apiClient.get(`/parts/${code}`);  // 自动识别为条码
  console.log('零件详情:', part);
};

// 更新零件（支持多种标识符）
const updatePart = async (identifier) => {
  const updated = await apiClient.put(`/parts/${identifier}`, {
    minStock: 20,
    maxStock: 300
  });
  console.log('更新成功:', updated);
};

// 批量导入
const bulkImport = async (parts) => {
  const result = await apiClient.post('/parts/bulk-import', {
    parts,
    updateExisting: false
  });
  console.log('导入结果:', result);
};
```

---

## 业务规则

1. **零件编号唯一性**:
   - 系统级唯一
   - 创建后不可修改
   - 建议使用有意义的编码规则

2. **库存状态自动判断**:
   - `currentStock > minStock`: NORMAL
   - `currentStock <= minStock && currentStock > 0`: LOW
   - `currentStock <= 0`: OUT_OF_STOCK

3. **删除限制**:
   - 有库存的零件无法删除
   - 有关联标签的零件无法删除
   - 建议使用软删除（标记为不可用）

4. **批量导入**:
   - 支持创建和更新
   - 失败的记录会返回错误信息
   - 事务性操作，全部成功或全部失败

---

## 常见错误

### 错误响应格式

所有错误响应遵循统一格式：

```json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "错误描述",
    "details": "详细信息（可选）"
  },
  "timestamp": "2025-11-20T10:30:00.000Z",
  "path": "/api/v1/parts",
  "method": "POST",
  "statusCode": 400
}
```

### 错误码列表

| HTTP状态码 | 错误码 | 说明 | 解决方案 |
|-----------|-------|------|---------|
| 400 | `VALIDATION_ERROR` | 请求参数验证失败 | 检查请求参数格式 |
| 401 | `UNAUTHORIZED` | 未授权 | 提供有效的认证令牌 |
| 403 | `FORBIDDEN` | 无权限 | 确认用户有 `parts:*` 权限 |
| 404 | `PART_NOT_FOUND` | 零件不存在 | 检查零件标识符是否正确 |
| 409 | `PART_NUMBER_EXISTS` | 零件编号已存在 | 使用唯一的零件编号 |
| 409 | `PART_HAS_STOCK` | 零件有库存无法删除 | 先清空库存或使用软删除 |
| 409 | `PART_HAS_LABELS` | 零件有关联标签无法删除 | 先删除关联标签 |
| 500 | `INTERNAL_SERVER_ERROR` | 服务器内部错误 | 联系管理员 |

### 错误示例

**验证错误 (400)**
```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数验证失败",
    "errors": [
      {
        "field": "partNumber",
        "message": "零件编号不能为空",
        "constraint": "isNotEmpty"
      },
      {
        "field": "minStock",
        "message": "最小库存必须大于等于0",
        "constraint": "min"
      }
    ]
  },
  "timestamp": "2025-11-20T10:30:00.000Z",
  "path": "/api/v1/parts",
  "method": "POST",
  "statusCode": 400
}
```

**零件不存在 (404)**
```json
{
  "success": false,
  "error": {
    "code": "PART_NOT_FOUND",
    "message": "Part 'P999' not found"
  },
  "timestamp": "2025-11-20T10:30:00.000Z",
  "path": "/api/v1/parts/P999",
  "method": "GET",
  "statusCode": 404
}
```

**零件编号冲突 (409)**
```json
{
  "success": false,
  "error": {
    "code": "PART_NUMBER_EXISTS",
    "message": "Part with number P001 already exists"
  },
  "timestamp": "2025-11-20T10:30:00.000Z",
  "path": "/api/v1/parts",
  "method": "POST",
  "statusCode": 409
}
```

**权限不足 (403)**
```json
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "无权限访问此资源",
    "details": "需要权限: parts:create"
  },
  "timestamp": "2025-11-20T10:30:00.000Z",
  "path": "/api/v1/parts",
  "method": "POST",
  "statusCode": 403
}
```

---

## 相关文档

- [库存管理 API](./INVENTORY.md)
- [标签管理 API](./LABELS.md)
- [Excel导入导出 API](./EXCEL.md)
- [零件分组 API](./PART_GROUPS.md)
- [Parts Quick Start](../../PARTS_QUICKSTART.md)
- [Parts User Guide](../../PARTS_USER_GUIDE.md)

---

**返回**: [API索引](../../API_INDEX.md)

