# 财务Bot网银流水自助入库流程

> 适用：每月出纳导出银行/现金流水 → 按模板整理 xlsx → 财务Bot调用导入 API → 三校验 → 入库 → 现金流KPI联动 → 看板可见。
> 方案文档：`docs/网银流水导入方案-20260828.md` ｜ 后端代码：`backend/app/api/cash.py`（网银流水标准导入区）

---

## 一、流程总览

```
出纳导出网银流水(Excel)
      │
      ▼
按模板整理 xlsx（列：凭证日期/凭证号/科目编码/科目名称/借方金额/贷方金额/摘要）
      │
      ▼
① 下载模板  GET /api/cma/cash/import/template        （模板缺失时参考）
② 导入       POST /api/cma/cash/import/vouchers       （multipart file + entity_id）
      │
      ▼
三校验：① 借贷平衡(容差0.01)  ② 期间合计  ③ 结转行识别
      │
      ▼
入库：voucher_details（凭证明细）+ import_logs（导入日志）
      │
      ▼
现金流联动：现金余额自动更新 + EXT_现金类KPI + F_CASH_SAFETY 现金安全垫KPI
      │
      ▼
看板可见：GET /api/cma/cash/balance、/api/cma/cash/dashboard
```

---

## 二、模板字段说明

模板下载：`GET /api/cma/cash/import/template`（需登录态，返回 xlsx 附件）。

| 列名 | 字段 | 必填 | 说明 |
|------|------|------|------|
| 凭证日期 | voucher_date | ✅ | 日期格式（Excel日期/文本均可，如 2026-08-01） |
| 凭证号 | voucher_no | ✅ | 如 `记-001` |
| 科目编码 | subject_code | ✅ | 如 `1002`、`1001`、`1122`；**1001/1002 开头视为货币资金科目**（现金流联动依据） |
| 科目名称 | subject_name | ✅ | 如 `银行存款-工行` |
| 借方金额 | debit_amount | 条件 | 数字，可含千分位逗号；与贷方二选一 |
| 贷方金额 | credit_amount | 条件 | 数字，可含千分位逗号；与借方二选一 |
| 摘要 | summary | 条件 | 含"结转"或科目名含"本年利润"/"结转" → 识别为结转行 |

> 列名兼容中英文别名（如 `date`/`voucher_date`、`借方`/`debit_amount` 等，不区分大小写），但建议严格使用模板列名。
> 模板为单示例行，导入前删除示例行或直接覆盖为真实流水。

---

## 三、curl 命令示例

### 1. 登录拿 token（admin/admin123，账套 entity_id=1 酣客）

```bash
TOKEN=$(curl -s -X POST http://127.0.0.1:8010/api/cma/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123","entity_id":1}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")

echo "$TOKEN"   # 响应字段含 token/entity_id/entity_name/user
```

### 2. 下载导入模板

```bash
curl -s -o 网银流水导入模板.xlsx \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8010/api/cma/cash/import/template

ls -la 网银流水导入模板.xlsx   # 应 >1000 字节
```

### 3. 导入网银流水（multipart 上传）

```bash
curl -s -X POST http://127.0.0.1:8010/api/cma/cash/import/vouchers \
  -H "Authorization: Bearer $TOKEN" \
  -F "entity_id=1" \
  -F "file=@/path/to/2026年08月网银流水.xlsx"
```

### 4. 看板核对

```bash
curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8010/api/cma/cash/balance?entity_id=1"
curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8010/api/cma/cash/dashboard?entity_id=1"
```

---

## 四、三校验规则（导入时自动执行）

| # | 规则 | 逻辑 | 不通过时 |
|---|------|------|----------|
| ① | **借贷平衡** | Σ借方 = Σ贷方，容差 **0.01** | 返回 `balance_check.passed=false`，errors 追加 `借贷不平衡: 借方合计X ≠ 贷方合计Y，差额Z`（仍入库其余行，部分成功模式） |
| ② | **期间合计** | 按 `period`（YYYY-MM）汇总借方/贷方合计，返回 `period_totals`，供对账 | 不阻断，仅展示 |
| ③ | **结转行识别** | 摘要含"结转" 或 科目名含"本年利润"/"结转" → `carry_forward=1`，**不参与现金流余额计算** | 不阻断，返回 `carry_forward_count` |

> 逐行校验（失败行记录 errors，不阻断整体）：凭证日期为空/无法解析、凭证号为空、科目编码为空、科目名称为空、金额非数字、金额为负、借贷同时为0 → 该行跳过，其余行照常入库（**部分成功模式**）。

---

## 五、入库内容

- **voucher_details 表**（凭证明细）：voucher_no / voucher_date / subject_code / subject_name / debit_amount / credit_amount / summary / carry_forward / period / batch（batch = 文件名_时间戳）
- **import_logs 表**（导入日志）：filename / batch / total_rows / success_rows / failed_rows / errors（明细JSON）/ period / import_type="vouchers" / created_by="finance-bot"

返回体示例：

```json
{
  "success": true,
  "total": 120, "success_rows": 118, "failed_rows": 2,
  "errors": [{"row": 5, "field": "voucher_date", "reason": "日期无法解析: xxx"}],
  "balance_check": {"passed": true, "debit_total": 123456.78, "credit_total": 123456.78, "diff": 0.0, "tolerance": 0.01},
  "period_totals": {"2026-08": {"debit_total": 123456.78, "credit_total": 123456.78}},
  "carry_forward_count": 2,
  "cash_balance": 88.88,
  "kpi_updates": [{"kpi_code": "EXT_069", "kpi_name": "库存现金", "period": "2026-08", "value": ...}, ...],
  "batch": "2026年08月网银流水_20260828153000",
  "entity_id": 1
}
```

---

## 六、现金流联动（导入成功自动触发）

1. **现金余额自动更新**：汇总 1001/1002 开头科目（排除结转行）`Σ借 - Σ贷` 得货币资金期末余额（元）→ `set_current_cash_balance`（万元）→ `GET /api/cma/cash/balance` 的 `current_cash` 实时反映
2. **EXT_现金类KPI**：名称含"现金"/"货币资金"的 active KPI（排除 F_CASH_SAFETY）写入/更新 `kpi_values`（source_type="ledger"、data_status="verified"、remark 记录批次与明细口径）——库存现金类→1001余额，银行类→1002余额，其余→货币资金总额
3. **F_CASH_SAFETY 现金安全垫KPI**（万元）：不存在则自动创建（formula=货币资金余额-短期借款）；短期借款取 EXT_139 最新实际值（单位元÷10000），无数据时安全垫=货币资金余额

---

## 七、常见错误处理

| 现象 | 原因 | 处理 |
|------|------|------|
| 返回 `借贷不平衡: 借方合计X ≠ 贷方合计Y`（balance_check.passed=false） | 流水有遗漏/金额录错 | 核对 Excel 借贷金额，修正后重新导入（余额按最新批次重算，重复导入不叠加） |
| HTTP 400 `缺少必要列: ...` | 列名与模板不一致（如"日期"未被别名命中、列名带空格/全半角差异） | 对照模板列名重命名表头，或使用 `_VOUCHER_COL_ALIASES` 支持的别名 |
| HTTP 400 `无法读取Excel文件` | 文件损坏/非xlsx/加密 | 用 WPS/Excel 另存为 .xlsx 后再传 |
| HTTP 400 `Excel文件为空（无数据行）` | 工作表无数据 | 删除空sheet或填入数据 |
| errors 含 `日期无法解析` | 凭证日期为文本/格式异常 | 改成标准日期格式（如 2026-08-01） |
| errors 含 `金额不能为负` / `借贷金额不能同时为0` | 数据录入问题 | 修正对应行（失败行不会入库） |
| errors 含 `凭证号为空` / `科目编码为空` / `科目名称为空` | 缺单元格 | 补全后重导 |
| HTTP 401 | token 失效/未登录 | 重新登录拿 token；注意登录必须带 entity_id |
| HTTP 403 | 账号未授权该 entity_id | 联系管理员在 user_entities 授权 |
| 模板下载 404 | 模板文件缺失 | 检查 `backend/scripts/templates/网银流水导入模板.xlsx` 是否存在，或运行 `backend/scripts/gen_voucher_import_template.py` 重新生成 |

---

## 八、注意事项

- 导入是**增量写入**：同一文件重复导入会生成多条记录（batch 不同），余额按最新 batch 重算；对账以 `batch` 为粒度
- 结转行不参与现金流余额计算，但会计入借贷平衡校验
- 现金流联动失败不阻断入库（已入库数据保留，KPI 联动失败记 error 日志），可联系全栈Bot排查 `journalctl -u cma-backend`
