# USPS 自动集包接口规格说明

## 1. 需求概述

根据 USPS自动集包需求文档，实现 USPS 尾程包裹的自动集包功能。当用户在系统中创建 USPS 袋牌后，系统自动筛选符合条件的包裹并逐个关联到该袋牌。

**核心特点**：
- 前端使用 WinForm，需要实时反馈关联进度
- 采用异步任务 + 进度查询的设计模式
- 每次关联间隔 2-4 秒随机延迟，模拟人工操作

---

## 2. 接口设计

### 2.1 启动自动集包任务

**接口路径**：`POST /api/bagtag/auto-pack/start`

**请求参数**：

| 参数名 | 类型 | 必选 | 描述 |
|--------|------|------|------|
| tagNumber | string | 是 | USPS 袋牌号 |
| creator | string | 否 | 操作人，默认为 "system" |

**请求示例**：
```json
{
  "tagNumber": "USPS202604031200010001",
  "creator": "admin"
}
```

**响应结构**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "taskId": "task_20260403120001_abc123",
    "tagNumber": "USPS202604031200010001",
    "status": "processing",
    "totalCount": 50,
    "message": "自动集包任务已启动"
  }
}
```

**错误响应**：

```json
{
  "code": 1001,
  "message": "Bag tag not found or not in opened status",
  "data": null
}
```

---

### 2.2 查询自动集包进度

**接口路径**：`GET /api/bagtag/auto-pack/progress/{taskId}`

**路径参数**：

| 参数名 | 类型 | 必选 | 描述 |
|--------|------|------|------|
| taskId | string | 是 | 任务ID |

**响应结构**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "taskId": "task_20260403120001_abc123",
    "tagNumber": "USPS202604031200010001",
    "status": "processing",
    "totalCount": 50,
    "processedCount": 25,
    "successCount": 24,
    "failedCount": 1,
    "currentWaybill": "9201234567890123456789",
    "progress": 50,
    "message": "正在处理第 25/50 个包裹",
    "startTime": "2026-04-03T12:00:01Z",
    "estimatedEndTime": "2026-04-03T12:03:30Z",
    "failedItems": [
      {
        "waybillNumber": "9201234567890123456788",
        "errorCode": 10035,
        "errorMessage": "Waybill is already associated with another bag tag"
      }
    ]
  }
}
```

**状态说明**：

| 状态值 | 说明 |
|--------|------|
| pending | 等待处理 |
| processing | 处理中 |
| completed | 已完成 |
| failed | 失败/异常终止 |
| cancelled | 已取消 |

---

### 2.3 取消自动集包任务

**接口路径**：`POST /api/bagtag/auto-pack/cancel/{taskId}`

**路径参数**：

| 参数名 | 类型 | 必选 | 描述 |
|--------|------|------|------|
| taskId | string | 是 | 任务ID |

**响应结构**：

```json
{
  "code": 0,
  "message": "Task cancelled successfully",
  "data": {
    "taskId": "task_20260403120001_abc123",
    "status": "cancelled",
    "processedCount": 25,
    "successCount": 24,
    "failedCount": 1
  }
}
```

---

### 2.4 获取任务结果

**接口路径**：`GET /api/bagtag/auto-pack/result/{taskId}`

**路径参数**：

| 参数名 | 类型 | 必选 | 描述 |
|--------|------|------|------|
| taskId | string | 是 | 任务ID |

**响应结构**（任务完成后）：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "taskId": "task_20260403120001_abc123",
    "tagNumber": "USPS202604031200010001",
    "status": "completed",
    "totalCount": 50,
    "successCount": 48,
    "failedCount": 2,
    "startTime": "2026-04-03T12:00:01Z",
    "endTime": "2026-04-03T12:03:45Z",
    "duration": 224,
    "failedItems": [
      {
        "waybillNumber": "9201234567890123456788",
        "errorCode": 10035,
        "errorMessage": "Waybill is already associated with another bag tag"
      },
      {
        "waybillNumber": "9201234567890123456787",
        "errorCode": 10033,
        "errorMessage": "Channel does not match"
      }
    ]
  }
}
```

---

## 3. 业务逻辑

### 3.1 筛选条件

系统仅自动关联同时满足以下条件的包裹：

1. **尾程渠道为 USPS**
   - 通过运单号识别渠道（92/93/94/95 开头，或 420+邮编+92/93/94/95）

2. **当前尚未被集包**
   - 未关联到任何袋牌（bag_tag_waybills 表中不存在）

3. **已完成换单**
   - label_replace_requests 表中存在对应记录
   - 换单状态为 "Y"（正常换单）

4. **换单时间在前 3 天内（84小时）**
   - 以创建袋牌时间为基准
   - CreatedAt >= 当前时间 - 84小时

### 3.2 处理流程

```
┌─────────────────────────────────────────────────────────────┐
│  1. 接收启动请求 (POST /api/bagtag/auto-pack/start)          │
│     - 验证袋牌存在且状态为 Opened                            │
│     - 生成唯一任务ID                                         │
│     - 查询符合条件的包裹总数                                 │
│     - 返回任务ID给前端                                       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  2. 异步执行自动集包任务                                     │
│     - 创建后台任务                                           │
│     - 逐个关联包裹                                           │
│     - 每单间隔 2-4 秒随机延迟                                │
│     - 实时更新任务进度状态                                   │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  3. 前端轮询进度 (GET /api/bagtag/auto-pack/progress/{id})   │
│     - 建议轮询间隔：1-2 秒                                   │
│     - 显示进度条、当前处理单号、成功/失败数量                │
│     - 可实时取消任务                                         │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  4. 任务完成                                                 │
│     - 返回最终结果                                           │
│     - 记录操作日志                                           │
└─────────────────────────────────────────────────────────────┘
```

### 3.3 关联处理逻辑

对于每个符合条件的包裹：

1. 调用 `AssociateWaybillAsync` 方法进行关联
2. 记录关联结果（成功/失败）
3. 更新任务进度状态
4. 生成 2-4 秒随机延迟
5. 处理下一个包裹

### 3.4 任务状态管理

任务状态存储在内存缓存中（ICacheService），包含：

```csharp
public class AutoPackTaskStatus
{
    public string TaskId { get; set; }
    public string TagNumber { get; set; }
    public string Status { get; set; } // pending/processing/completed/failed/cancelled
    public int TotalCount { get; set; }
    public int ProcessedCount { get; set; }
    public int SuccessCount { get; set; }
    public int FailedCount { get; set; }
    public string CurrentWaybill { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime? EndTime { get; set; }
    public List<FailedItem> FailedItems { get; set; }
    public CancellationTokenSource CancellationTokenSource { get; set; }
}
```

---

## 4. 技术实现

### 4.1 新增模型

**AutoPackTaskStatus**（任务状态模型）
- 位置：`MDL/Models/AutoPackTaskStatus.cs`

**AutoPackRequest**（启动请求模型）
- 位置：`MDL/Models/BagTagRequest.cs`（扩展）

### 4.2 接口定义

**IBagTagService** 扩展：
```csharp
// 启动自动集包任务
Task<AutoPackTaskResult> StartAutoPackAsync(string tagNumber, string creator);

// 查询任务进度
Task<AutoPackTaskStatus> GetAutoPackProgressAsync(string taskId);

// 取消任务
Task<bool> CancelAutoPackAsync(string taskId);

// 获取任务结果
Task<AutoPackTaskStatus> GetAutoPackResultAsync(string taskId);
```

**IBagTagRepository** 扩展：
```csharp
// 查询符合条件的 USPS 包裹
Task<List<string>> GetEligibleUspsWaybillsAsync(string tagNumber, DateTime cutoffTime);

// 获取符合条件的包裹数量
Task<int> GetEligibleUspsWaybillCountAsync(string tagNumber, DateTime cutoffTime);
```

### 4.3 控制器实现

在 `BagTagController` 中添加：
- `POST /api/bagtag/auto-pack/start`
- `GET /api/bagtag/auto-pack/progress/{taskId}`
- `POST /api/bagtag/auto-pack/cancel/{taskId}`
- `GET /api/bagtag/auto-pack/result/{taskId}`

---

## 5. 数据库查询

### 5.1 查询符合条件的包裹

```sql
SELECT DISTINCT 
    l.FinalMileTrackingNumber
FROM label_replace_requests l
LEFT JOIN bag_tag_waybills b ON l.FinalMileTrackingNumber = b.FinalMileTrackingNumber
WHERE 
    l.ReplaceStatus = 'Y'
    AND l.CreatedAt >= @CutoffTime
    AND l.FinalMileTrackingNumber IS NOT NULL
    AND l.FinalMileTrackingNumber != ''
    AND b.Id IS NULL  -- 未关联到任何袋牌
    AND (
        -- USPS 运单号格式：92/93/94/95 开头
        l.FinalMileTrackingNumber REGEXP '^(92|93|94|95)'
        -- 或 420+邮编+92/93/94/95 开头
        OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{5}(92|93|94|95)'
        OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{9}(92|93|94|95)'
    )
ORDER BY l.CreatedAt ASC
```

---

## 6. 错误码定义

| 错误码 | 说明 |
|--------|------|
| 0 | 成功 |
| 1001 | 袋牌不存在或状态不正确 |
| 1002 | 任务不存在 |
| 1003 | 任务已取消 |
| 1004 | 任务已完成 |
| 1005 | 无符合条件的包裹 |
| 10031 | 袋牌不存在 |
| 10032 | 袋牌未打开 |
| 10033 | 渠道不匹配 |
| 10035 | 运单已关联到其他袋牌 |
| 9999 | 系统错误 |

---

## 7. 前端集成建议

### 7.1 WinForm 调用流程

```csharp
// 1. 启动自动集包
var response = await httpClient.PostAsJsonAsync("/api/bagtag/auto-pack/start", 
    new { tagNumber = "USPS202604031200010001", creator = "admin" });
var result = await response.Content.ReadFromJsonAsync<ApiResponse<AutoPackResult>>();
var taskId = result.Data.TaskId;

// 2. 轮询进度
timer = new Timer(async _ => {
    var progressResponse = await httpClient.GetAsync($"/api/bagtag/auto-pack/progress/{taskId}");
    var progress = await progressResponse.Content.ReadFromJsonAsync<ApiResponse<AutoPackProgress>>();
    
    // 更新UI：进度条、当前单号、成功/失败数
    UpdateUI(progress.Data);
    
    if (progress.Data.Status == "completed" || progress.Data.Status == "failed")
    {
        timer.Stop();
        ShowResult(progress.Data);
    }
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));

// 3. 取消任务（用户点击取消按钮）
await httpClient.PostAsync($"/api/bagtag/auto-pack/cancel/{taskId}", null);
```

### 7.2 UI 展示建议

- **进度条**：显示 `processedCount / totalCount` 百分比
- **当前处理**：显示 `currentWaybill` 单号
- **统计信息**：成功数、失败数、剩余数
- **预计完成时间**：根据当前速度计算
- **失败列表**： expandable 面板显示失败明细

---

## 8. 性能考虑

1. **异步处理**：使用 `Task.Run` 或后台服务执行集包任务
2. **缓存进度**：使用 `ICacheService` 存储任务状态，避免数据库压力
3. **批量查询**：一次性查询所有符合条件的包裹，避免多次数据库访问
4. **延迟控制**：2-4 秒随机延迟，避免对系统造成过大压力

---

## 9. 安全考虑

1. **袋牌状态验证**：确保袋牌处于 Opened 状态才能自动集包
2. **渠道匹配**：自动识别运单号渠道，确保与袋牌渠道一致
3. **重复关联检查**：避免将已关联的包裹再次关联
4. **任务超时**：设置任务最大执行时间（如 30 分钟），超时自动终止

---

## 10. 日志记录

每个包裹关联操作记录订单日志：

```csharp
await _orderLogService.RecordOrderLogAsync(
    neutralWaybillNumber: string.Empty,
    finalMileTrackingNumber: waybillNumber,
    operationType: OrderLogOperationType.PACK,
    operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
    operationDescription: $"自动集包到袋牌 {tagNumber}: {(success ? "成功" : errorMessage)}",
    @operator: "system_auto"
);
```
