# 宝锐工作台 · 文件上传 + 数据管道

## 架构概览

```
上传文件 ──→ 质检(run_quality_check) ──→ 处理(process_uploaded_file) ──→ 合并部署 ──→ 归档
   │               │                           │                              │
   │         ① 识别类型(ERP/飞书)         ③ 智能月份检测              ⑤ merge_workbench_data.py
   │         ② 校验关键列                 ④ 路由到正确JSON              ⑥ cb_sync.sh → CloudBase
   │                                        → kpi_progress (当月)          ⑦ 移至 _已处理归档/
   │                                        → three_year_monthly (历史月)
   │                                        → kpi_dashboard (H2累计)
```

## 后端 API

### POST /api/upload
接收 multipart/form-data，保存文件到 `数据/上传/`，记录到 `upload_log.json`。

### POST /api/upload/process
三步流水线：
1. **质检** — 检测类型(erp_order/feishu_activity)、校验关键列(单据编号/价税合计)
2. **数据处理** — 智能月份检测 → 路由到正确JSON
3. **归档** — 成功则移至 `_已处理归档/`，失败保留原位置

### GET /api/uploads
返回上传记录列表（最近100条）。

## 智能月份路由

### 检测方法
```python
# 从 单据编号 前100条采样
month_counter = Counter()
for row in rows_list[:100]:
    bn = row.get('单据编号', '')
    if bn.startswith('ZHBR') and len(bn) >= 10:
        ym = '20' + bn[4:6] + '-' + bn[6:8]  # ZHBR2607xxx → 2026-07
        month_counter[ym] += 1
detected_month = month_counter.most_common(1)[0][0]

# 备用: 从文件名推断
m = re.search(r'(2026\d{2})', saved_name)
```

### 路由规则
| 检测月份 | 目标JSON | 说明 |
|---------|---------|------|
| = 当前月 | `kpi_progress.json` | 覆盖当月KPI数据 |
| < 当前月 | `three_year_monthly.json` + `kpi_dashboard.json` | 历史月校准，H2累加 |

## 常见陷阱

### 1. openpyxl read_only=True + iter_rows 陷阱
```python
# ❌ read_only=True + iter_rows 返回 ReadOnlyCell，无法正常迭代
wb = openpyxl.load_workbook(fp, data_only=True, read_only=True)
headers = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))[0]]  # 只有1个元素!

# ✅ 必须用 values_only=True，不用 read_only
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))]
```

### 2. str(None) = 'None' 陷阱
```python
# ❌ 合计行 单据编号 为 None，str(None) = 'None'（字面值），not bn 不过滤
bn = str(row.get(bn_col, '')).strip()
if not bn: continue  # 'None' ≠ '' → 合计行漏网!

# ✅ 必须显式排除 'None' 和 '合计'
bn = str(row.get(bn_col, '') or '').strip()
if not bn or bn == 'None' or '合计' in bn: continue
```

### 3. 合计行特征
- 第1列(合同日期) = "合计"
- 单据编号 = None
- 客户/销售员 = None
- 价税合计 = 全表汇总（需排除，否则双倍计数）

### 4. 服务器代码修改后必须重启
修改 `销售工作台服务.py` 后，必须 `kill $(lsof -ti :8765)` + 重新启动，否则运行的是旧代码。

### 5. CSV vs xlsx 处理
质检读xlsx需 `values_only=True`，处理需分别处理csv(DictReader)和xlsx(openpyxl)。

## 前端 Tab

### 数据管理 Tab (adminOnly)
- 拖拽上传区：支持 .xlsx/.xls/.csv/.docx/.pdf
- 上传记录列表：默认显示10条，超过时"展开剩余N条"
- ⚡处理按钮：仅对 ERP/飞书导出 类型显示
- toast 提示：3秒自动消失
