# 客户维度监控报表实现计划

## 一、需求分析

### 1.1 新报表概述
基于现有日级报表 `GetDailyLabelStatsChineseAsync()` 的逻辑，创建一个**客户维度**的监控报表

### 1.2 报表核心维度
- **第一维度**：客户（CustomerId / CustomerCode / CustomerName）
- **第二维度**：日期（可选：按日期聚合）

### 1.3 新增数据字段
1. **客户标签率**：该客户的有标签订单数 / 总订单数
2. **分段统计**：各客户在16点前后的到仓和考核通过情况
3. **核心指标**：每个客户的换单成功率、完成率等

### 1.4 数据粒度
- **按客户日期统计**（粒度最细）：CustomerId + Date
- **按客户汇总**（粗粒度）：CustomerId（可选）

---

## 二、现有系统分析

### 2.1 现有表结构涉及
- `label_replace_requests` - 换单请求表，包含CustomerId、Label等
- `arrival_handover_forms` - 到货交接单表
- `label_scan_history` - 扫描历史表

### 2.2 现有SQL逻辑复用
从 `GetDailyLabelStatsChineseAsync()` 中复用：
- CustomerLabelRates CTE（客户标签率计算）
- ArrivalRequests CTE（考核时间逻辑）
- Daily24HCompletedOrders CTE（考核通过判断）
- 16点分段统计逻辑

---

## 三、新报表DTO定义

### 3.1 新建DTO类
**类名**：`CustomerDailyLabelStatsDto`

**字段清单**：
```csharp
// 基础信息
public int CustomerId { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string Date { get; set; }  // yyyy-MM-dd

// 客户标签率相关
public string CustomerLabelRate { get; set; }  // "xx.xx%"
public int TotalRequests { get; set; }  // 该客户该日期的总订单数
public int LabeledRequests { get; set; }  // 有标签的订单数

// 到仓分布
public int BeforeNoonArrivedCount { get; set; }  // 16点前到仓
public int AfternoonArrivedCount { get; set; }  // 16点后到仓

// 考核通过
public int BeforeNoonPassedCount { get; set; }  // 16点前考核通过
public int AfternoonPassedCount { get; set; }  // 16点后考核通过

// 核心指标
public int DailyNewReplaceCount { get; set; }  // 当日新增换单数
public int DailySuccessCount { get; set; }  // 当日换单成功数
public int DailyFailureCount { get; set; }  // 当日换单失败
public int DailyCompletedCount { get; set; }  // 当日完成数

// 完成率
public string DailyCompletionRate { get; set; }  // "xx.xx%"
public string Rate24Hour { get; set; }  // 24小时完成率

// 其他
public DateTime DataFetchTime { get; set; }  // 数据拉取时间
```

---

## 四、新Repository方法实现

### 4.1 新增方法签名
```csharp
/// <summary>
/// 获取客户维度的每日标签换单统计数据
/// </summary>
public async Task<List<CustomerDailyLabelStatsDto>> GetCustomerDailyLabelStatsAsync()
```

### 4.2 SQL结构设计

#### 步骤1：获取客户基础信息和标签率
```
CustomerInfo + CustomerLabelRates
├─ 客户ID、代码、名称
├─ 客户标签率 = 有标签订单数 / 总订单数
└─ 总订单数、有标签订单数
```

#### 步骤2：按客户日期分组的到仓统计
```
CustomerDailyArrival
├─ 按 CustomerId + DATE(ReceiptTime) 分组
├─ 16点前到仓数
└─ 16点后到仓数
```

#### 步骤3：按客户日期分组的考核通过统计
```
CustomerDailyBeforeNoonPassed
CustomerDailyAfternoonPassed
├─ 16点前考核通过数
└─ 16点后考核通过数
```

#### 步骤4：按客户日期分组的换单完成统计
```
CustomerDailyCompletion
├─ 当日新增换单数
├─ 当日成功数
├─ 当日失败数
└─ 当日完成数
```

#### 步骤5：最终聚合与输出
```
最终SELECT
├─ 关联CustomerInfo（客户信息）
├─ 关联CustomerDailyArrival（到仓分布）
├─ 关联CustomerDailyXxxPassed（考核通过）
├─ 关联CustomerDailyCompletion（换单完成）
├─ 计算完成率、24H率
└─ 输出所有字段
```

---

## 五、SQL查询框架

### 5.1 基础框架结构
```sql
WITH
-- 步骤1：客户信息和标签率
CustomerInfo AS (
    SELECT CustomerId, CustomerCode, CustomerName, 
           标签率计算...
),

-- 步骤2：客户日期的到仓分布
CustomerDailyArrival AS (
    SELECT CustomerId, DATE(ReceiptTime) AS 日期,
           COUNT(CASE WHEN HOUR(ReceiptTime) < 16...)
           COUNT(CASE WHEN HOUR(ReceiptTime) >= 16...)
),

-- 步骤3：客户日期的16点前考核通过
CustomerDailyBeforeNoonPassed AS (
    SELECT CustomerId, 日期, COUNT(...)
),

-- 步骤4：客户日期的16点后考核通过
CustomerDailyAfternoonPassed AS (
    SELECT CustomerId, 日期, COUNT(...)
),

-- 步骤5：客户日期的换单完成统计
CustomerDailyCompletion AS (
    SELECT CustomerId, 日期, 
           COUNT(新增), COUNT(成功), COUNT(失败), COUNT(完成)
),

-- 步骤6：最终聚合
最终SELECT
```

---

## 六、关键SQL逻辑

### 6.1 客户标签率计算
```sql
CustomerLabelRate = 
  (SELECT COUNT(DISTINCT id) 
   FROM label_replace_requests l 
   WHERE l.CustomerId = ci.CustomerId AND l.Label IS NOT NULL AND l.Label != '')
  /
  (SELECT COUNT(DISTINCT id) 
   FROM label_replace_requests l 
   WHERE l.CustomerId = ci.CustomerId)
  * 100
```

### 6.2 按客户分组的到仓统计
```sql
-- 需要关联 ArrivalRequests 以获取 CustomerId
-- 按 CustomerId + DATE(到货时间) 分组
-- 统计16点前后到仓数量（去重）
```

### 6.3 按客户分组的考核通过统计
```sql
-- 复用Daily24HCompletedOrders的逻辑
-- 额外按 CustomerId 分组
-- 分别统计16点前和16点后的通过数
```

---

## 七、实现步骤

### 步骤1：创建新DTO类
**文件**：`d:\EPproject\LabelReplaceServer\src\MDL\DTOs\CustomerDailyLabelStatsDto.cs`
- 定义所有字段
- 添加SugarColumn注解

### 步骤2：在Repository中新增方法
**文件**：`d:\EPproject\LabelReplaceServer\src\DAL\Repositories\LabelReplaceRepository.cs`
- 新增 `GetCustomerDailyLabelStatsAsync()` 方法
- 实现完整SQL查询
- 添加reader映射逻辑

### 步骤3：创建对应的Service方法（可选）
**文件**：`BLL/Services/` 中的相关Service
- 封装数据处理逻辑
- 提供业务层接口

### 步骤4：创建Controller端点（可选）
**文件**：`CONTROLLER/Controllers/` 中的相关Controller
- 新增API端点
- 处理请求参数（如客户ID、日期范围）

### 步骤5：验证与测试
- SQL语法检查
- 编译无错误
- 数据准确性验证

---

## 八、关键技术点

### 8.1 去重问题
- 客户标签率需要用 `COUNT(DISTINCT l.Id)` 避免重复计算
- 到仓分布需要用 `COUNT(DISTINCT ar.RequestId)` 避免重复
- 完成数需要用 `COUNT(DISTINCT NeutralWaybillNumber)` 去重

### 8.2 时区处理
- 所有时间比较保持一致的UTC-5时区
- 到仓时间：`a.ReceiptTime`（已是UTC-5）
- 完成时间：`CONVERT_TZ(oss.首次成功时间, '+00:00', '-05:00')`

### 8.3 考核时间逻辑复用
- 从现有SQL中复用CustomerLabelRates CTE
- 从现有SQL中复用考核时间计算逻辑
- 从现有SQL中复用Daily24HCompletedOrders判断逻辑

### 8.4 性能优化
- 添加索引：`label_replace_requests(CustomerId)`
- 避免多次相同的子查询
- 使用CTE提高查询可读性

---

## 九、输出样例

| CustomerId | CustomerCode | CustomerName | Date | CustomerLabelRate | BeforeNoonArrived | ... |
|------------|--------------|--------------|------|------------------|-------------------|-----|
| 1 | CUST001 | 客户A | 2026-05-16 | 95.50% | 10 | ... |
| 1 | CUST001 | 客户A | 2026-05-17 | 95.50% | 8 | ... |
| 2 | CUST002 | 客户B | 2026-05-16 | 78.30% | 15 | ... |

---

## 十、风险与考虑

### 10.1 潜在风险
1. **数据一致性**：客户信息是否会变更？
2. **性能**：大量客户下的查询性能？
3. **时间范围**：查询是否需要日期范围参数？

### 10.2 扩展考虑
- 支持日期范围查询参数
- 支持按客户ID筛选
- 支持按标签率范围筛选
- 支持导出到Excel

---

## 十一、预期交付物

1. ✅ `CustomerDailyLabelStatsDto.cs` - DTO类
2. ✅ Repository新方法 - `GetCustomerDailyLabelStatsAsync()`
3. ✅ 完整SQL查询脚本
4. ✅ 编译无错误
5. ✅ 实现总结文档

