# ERP 上传处理管线（2026-08-07 新增）

## 完整流程

```
上传 .xlsx/.csv → 质检(run_quality_check) → 处理(process_uploaded_file) → 归档
                       │                            │
                 ① 类型识别                   ③ 智能月份检测
                 (ERP/飞书)                   (ZHBR前缀解析)
                 ② 列名校验                   ④ 按月份路由
                 (单据编号/价税合计)          ⑤ KPI更新+merge+deploy
```

## 月份检测逻辑

优先级：
1. 抽样前100行，从 单据编号 前缀提取月份：`ZHBR2607xxx` → `2026-07`
2. 文件名正则：`(2026\d{2})` → `2026-07`
3. 兜底：`datetime.now()` 当前月

## 按月份路由

| 场景 | 检测结果 | 写入目标 | 说明 |
|------|---------|---------|------|
| 当月文件 | `detected_month == current_month` | `kpi_progress.json` | 当前月 KPI |
| 历史月文件 | `detected_month != current_month` | `three_year_monthly.json` + `kpi_dashboard.json` | 历史数据校准 |

## 质检关键坑

### openpyxl `read_only=True` 陷阱
```python
# ❌ read_only=True → ReadOnlyCell → 遍历异常
wb = openpyxl.load_workbook(fp, data_only=True, read_only=True)
next(ws.iter_rows(min_row=1, max_row=1))[0]  # 'ReadOnlyCell' is not iterable

# ✅ values_only=True → 直接返回值
wb = openpyxl.load_workbook(fp, data_only=True)
headers = [str(c) for c in next(ws.iter_rows(min_row=1, max_row=1, values_only=True))]
```

### 合计行过滤
```python
bn = str(row.get(bn_col, '') or '').strip()
if not bn or bn == 'None' or '合计' in bn: continue
# ⚠️ str(None) = 'None'（字面值），必须显式判空+判'None'
```

### 预测订单过滤
`FO` 开头的单据编号是预测订单，金额为0或占位符。处理时须 `bn.startswith('FO')` 跳过。

## 服务重启
```bash
kill $(lsof -ti :8765); sleep 1
cd ~/Desktop/Hermes输出-工作类
python3 脚本/销售工作台服务.py 8765 &
```

## 相关文件
- 服务端: `脚本/销售工作台服务.py`
- 前端: `工具/index.html` → `renderDataMgmt()` + `processUpload()`
- API: `POST /api/upload`, `GET /api/uploads`, `POST /api/upload/process`
- 上传目录: `数据/上传/`
- 归档目录: `数据/上传/_已处理归档/`
- 日志: `数据/上传/upload_log.json`
