Files
shanxia2.0/docs/superpowers/specs/2026-08-20-crm-invoice-paste-recognize-design.md
T
2026-08-21 09:35:11 +08:00

145 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CRM 客户开票信息 · 粘贴快速识别 · 设计说明
> Demo:`docs/demo-crm-invoice-paste-recognize.html`(方案 A)
> 日期:2026-08-20
> 状态:已实现(待联调验收)
## 1. 背景与目标
客户管理详情弹窗右侧「开票信息」新增/编辑时,业务常从微信/邮件粘贴整段「开票资料」文本,再手工逐项填入,效率低且易错。
**目标:** 在新增/编辑弹窗顶部提供「粘贴识别」区,用**纯前端规则**解析常见开票资料文本,并将结果填入对应表单字段;用户可再微调后保存。不依赖后端、不调用大模型。
**非目标:**
- 不新增数据库字段(银行行号、邮编、邮寄地址等仍无独立列)
- 不接阿里云 / OCR / 大模型
- 不改开票信息保存 API 与权限
## 2. 已确认决策
| 项 | 结论 |
|---|---|
| 入口布局 | 方案 A:新增/编辑弹窗表单顶部「粘贴识别」区 |
| 识别方式 | 纯前端规则(标签别名 + 行解析),零成本、离线 |
| 回填策略 | **识别后覆盖对应字段**(已有值一并覆盖) |
| 无表单字段的信息 | 写入 `remark`(银行行号、邮编、发票邮寄信息等) |
| 备注合并 | 识别产生的备注内容**追加到 remark 末尾**;若与已有内容重复则不重复追加 |
| 范围 | 仅 `web-admin` 客户详情 → `InvoiceInfo.vue`;**以及**客户新增弹窗 `CustomerFormDialog.vue` 底部开票信息(二级粘贴弹窗) |
## 3. 交互设计
组件:`web-admin/src/views/crm/customer/components/InvoiceInfo.vue`
在现有 `el-form` 顶部、`.form-grid-2` 字段区之前增加粘贴区:
```
el-dialog「添加/修改开票信息」
└─ el-form
├─ 粘贴识别面板(虚线边框 + primary-bg)
│ ├─ 标题「粘贴识别」+ 简短提示
│ ├─ el-input type=textarea(粘贴原文)
│ ├─ [清空] [识别并填入]
│ └─ 结果提示文案(成功/部分成功/失败)
└─ 原有字段栅格(单位名称、税号…)
```
行为:
1. 打开新增/编辑弹窗时,粘贴区默认**空**(不强制预填样例,避免误覆盖业务数据)。
2. 点击「识别并填入」→ 调用解析工具 → **覆盖**已映射字段;未映射信息写入/追加 `remark`。
3. 点击「清空」仅清空粘贴框与提示,**不**清空表单字段。
4. 关闭弹窗时重置粘贴区与表单(沿用现有 `reset`)。
5. 识别失败或零字段:`ElMessage.warning`,不改表单。
6. 部分成功:照常覆盖已识别字段,并用消息列出未识别的关键项(如缺单位名称)。
视觉对齐 Fresh Mint:主色 `#10b981` / `#059669` / `#ecfdf5`;按钮带图标(DocumentCopy / Delete / Close / Check 等既有习惯)。
## 4. 解析规则
新增工具:`web-admin/src/utils/crm-invoice-paste-recognize.ts`
参考既有模式:`web-admin/src/utils/pl-paste-recognize.ts`(纯函数、导出样例常量、返回 `warnings`)。
### 4.1 输入输出
```ts
interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
/** 无独立字段的附加信息,供写入 remark */
remarkParts: string[]
warnings: string[]
/** 成功写入表单的字段数(不含 remarkParts) */
filledCount: number
}
```
### 4.2 标签别名 → 字段
| 字段 | 标签别名(行首,支持全/半角冒号、标签内空格) |
|---|---|
| companyName | 公司名称、单位名称、单位名、名称 |
| taxNo | 纳税人识别号、纳税识别号、统一社会信用代码、税号 |
| companyAddress | 单位地址、地址(见 4.3 邮寄段排除) |
| phoneFax | 电话/传真、电话传真、传真、电话 |
| bankName | 银行开户行、开户银行、开户行 |
| bankAccount | 银行账号、账号、帐号(去除内部空格) |
| legalRepresentative | 法定代表人、法人代表、法人 |
| agent | 代理人、委托代理人 |
「地址及电话」单独规则:整行取值后,若末尾匹配固话/手机则拆到 `phoneFax`,其余为 `companyAddress`。
### 4.3 段落与冲突
- 若存在「发票邮寄地址」标题,其后块内的「姓名 / 电话 / 地址」**不**写入 `companyName` / `phoneFax` / `companyAddress`,而是汇总进 `remarkParts`(如 `邮寄:汪璇 / 188… / 地址…`)。
- 「开票资料」等总标题行忽略。
- 银行行号 / 联行号 / 行号 → `remarkParts`:`银行行号:…`(去空格)。
- 邮编 → `remarkParts`:`邮编:…`。
### 4.4 验收样例(必须通过)
**样例 1(安庆山峡):** 公司名称、税号、开户行、帐号、地址、电话/传真正确回填;银行行号、邮编、邮寄信息进 remark。
**样例 2(江峰生物):** 单位名、税号、地址及电话拆分、开户行、带空格账号去空格、电话正确;银行行号进 remark。
单测:`web-admin/src/utils/crm-invoice-paste-recognize.test.ts`(与现有 vitest 风格一致,如 `pl-cert-slots.test.ts`)。
## 5. 数据流
```
用户粘贴文本
→ parseCrmInvoicePaste(text)
→ 覆盖 form 对应键(仅当解析值非空)
→ remark:将 remarkParts 用「;」拼接后追加到现有 remark
→ ElMessage 提示 filledCount / warnings
→ 用户点确定 → 现有 /web/crm/invoice-info/add|update(不变)
```
覆盖规则细节:某字段解析值为空字符串时,**不**清空该表单字段(只覆盖「识别到了」的字段)。
## 6. 风险与后续
| 风险 | 应对 |
|---|---|
| 新标签写法未覆盖 | 补别名;UI 提示可手动改 |
| 无标签散文文本 | 识别失败,手动填写 |
| 长期需行号等独立字段 | 另开变更加列;本设计不扩表 |
可选后续(本变更不做):规则失败时降级调大模型。
## 7. 实现清单(实现阶段)
1. 新增 `crm-invoice-paste-recognize.ts` + 单测(含两段业务样例)
2. 改 `InvoiceInfo.vue`:顶部粘贴区 + 识别回填
3. 保留/更新 Demo:`docs/demo-crm-invoice-paste-recognize.html`(与线上规则一致即可)
4. 更新 graphify(web-admin)
不改 backend、不改 SQL、不改 OpenSpec 基线(纯前端体验增强)。