|
|
@@ -0,0 +1,468 @@
|
|
|
+# 报销管理 — Excel 批量导入功能 需求文档
|
|
|
+
|
|
|
+> 版本:v1.0
|
|
|
+> 日期:2026-10-28
|
|
|
+> 模块:财务管理(会计)→ 报销管理
|
|
|
+> 关联页面:`/cw/reimbursementApproval/info/InfoList`(列表页)、`/cw/reimbursementApproval/info/NewReimbursementForm`(表单页)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、需求概述
|
|
|
+
|
|
|
+在报销管理列表页(InfoList)新增「导入」功能,允许用户通过上传 Excel 文件批量创建报销单。导入生成的报销单为**暂存状态**,用户需在表单中逐条核对、上传数电发票 XML 后手动提交审批。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、功能范围
|
|
|
+
|
|
|
+### 2.1 支持的报销类型
|
|
|
+
|
|
|
+| sourceType 值 | 类型 | 明细表 |
|
|
|
+|---|---|---|
|
|
|
+| 1 | 项目报销 | `cw_reimbursement_detail_info` |
|
|
|
+| 3 | 报告报销 | `cw_reimbursement_detail_info_report` |
|
|
|
+| 4 | 其他报销 | `cw_reimbursement_detail_info_other` |
|
|
|
+
|
|
|
+仅支持以上三种,其他类型(合同报销、采购报销、人事报销等)不在本次范围内。
|
|
|
+
|
|
|
+### 2.2 权限控制
|
|
|
+
|
|
|
+- 前端按钮权限标识:`cw:reimbursement:info:import`
|
|
|
+- 后端 Controller 不加 `@RequiresPermissions`(与现有代码风格一致),权限由前端控制
|
|
|
+- 权限在系统权限管理页面配置,可分配给不同角色
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、Excel 模板规范
|
|
|
+
|
|
|
+### 3.1 模板结构
|
|
|
+
|
|
|
+- 单 Sheet,Sheet 名:`导入数据`
|
|
|
+- 第 1 行:列标题(蓝色底白字)
|
|
|
+- 第 2 行:列说明(浅蓝底灰字,必填列浅黄底)
|
|
|
+- 第 3-7 行:示例数据(淡绿底,A 列带红色 `[示例]` 标记)
|
|
|
+- 第 8 行起:分隔线 + 填写提示
|
|
|
+- 冻结前两行
|
|
|
+
|
|
|
+### 3.2 列定义(共 13 列)
|
|
|
+
|
|
|
+| 序号 | 列名 | 字段 | 必填 | 类型 | 说明 |
|
|
|
+|---|---|---|---|---|---|
|
|
|
+| A | 是否单独报销 | aloneFlag | 是 | 下拉:是/同上 | 「是」= 本行独立成单;「同上」= 归入上一行所属报销单 |
|
|
|
+| B | 报销类型 | sourceType | 是 | 下拉:项目报销/报告报销/其他报销 | 对应 sourceType 值 1/3/4 |
|
|
|
+| C | 报销人 | userName | 否 | 文本 | 不填默认导入操作人;代他人报销时填写对方姓名 |
|
|
|
+| D | 报销类别 | typeName | 是 | 下拉(引用类别列表 Sheet) | 只能选最末级节点,如"租房及其他杂费" |
|
|
|
+| E | 项目编号 | projectNumber | 条件 | 文本 | 项目报销和报告报销必填,精确匹配 `cw_project_records.project_number` |
|
|
|
+| F | 报销详情 | projectName | 条件 | 文本 | 仅其他报销填写,自由文本 |
|
|
|
+| G | 报告号 | reportNo | 条件 | 文本 | 仅报告报销填写,格式如:苏兴会基字[2026]1号 |
|
|
|
+| H | 非数电发票(费用) | ordinaryNumber | 否 | 数字 | 金额(元) |
|
|
|
+| I | 其他(费用) | subsidy | 否 | 数字 | 金额(元) |
|
|
|
+| J | 汇总 | numberCount | 是 | 数字 | 费用合计,可手填或系统自动计算 |
|
|
|
+| K | 非数电发票(张数) | ordinaryReceiptNumber | 否 | 整数 | 票据张数 |
|
|
|
+| L | 内容 | content | 否 | 文本 | 报销内容文字说明 |
|
|
|
+| M | 出差天数 | days | 否 | 数字 | |
|
|
|
+
|
|
|
+**注:** 数电发票费用(number)不在模板中,由后台根据上传的 XML 文件自动计算。
|
|
|
+
|
|
|
+### 3.3 数据验证(下拉)
|
|
|
+
|
|
|
+| 列 | 验证方式 | 选项 |
|
|
|
+|---|---|---|
|
|
|
+| A 是否单独报销 | 内嵌列表 | 是, 同上 |
|
|
|
+| B 报销类型 | 内嵌列表 | 项目报销, 报告报销, 其他报销 |
|
|
|
+| D 报销类别 | 引用 Sheet2 | `类别列表!$A$2:$A$N`(N = 末级节点总数) |
|
|
|
+
|
|
|
+所有下拉均设置 `allow_blank = true`,空行不触发校验错误。
|
|
|
+
|
|
|
+### 3.4 类别列表 Sheet
|
|
|
+
|
|
|
+- Sheet 名:`类别列表`
|
|
|
+- A 列:报销类别最末级节点名称
|
|
|
+- 数据来源:`cw_reimbursement_type_info` 表中无子节点的记录(`level` 为最大值的记录)
|
|
|
+- 模板下载时由后端动态生成,确保与系统数据一致
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、业务逻辑
|
|
|
+
|
|
|
+### 4.1 分组规则
|
|
|
+
|
|
|
+按「是否单独报销」列将 Excel 行分组:
|
|
|
+
|
|
|
+```
|
|
|
+行1: 是 → 组1 开始
|
|
|
+行2: 同上 → 归入组1
|
|
|
+行3: 同上 → 归入组1
|
|
|
+行4: 是 → 组2 开始
|
|
|
+行5: 是 → 组3 开始
|
|
|
+```
|
|
|
+
|
|
|
+- 每组生成**一张**报销单(`cw_reimbursement_info`)
|
|
|
+- 组内每行生成**一条**明细记录
|
|
|
+- 全空行(所有列均为空)自动跳过,不参与分组
|
|
|
+
|
|
|
+### 4.2 同组一致性校验
|
|
|
+
|
|
|
+同一组内的以下字段必须与组内第一行完全一致,否则整组标记为校验失败:
|
|
|
+
|
|
|
+| 校验项 | 说明 |
|
|
|
+|---|---|
|
|
|
+| 报销类型 | 组内所有行的报销类型必须相同 |
|
|
|
+| 项目编号 | 项目报销和报告报销的组内,项目编号必须相同 |
|
|
|
+
|
|
|
+### 4.3 数据匹配逻辑
|
|
|
+
|
|
|
+#### 4.3.1 报销类别匹配
|
|
|
+
|
|
|
+- 用 Excel 中的类别名称去 `cw_reimbursement_type_info` 表匹配
|
|
|
+- 匹配条件:`name = #{typeName}` AND `del_flag = 0` AND 该节点无子节点(末级节点)
|
|
|
+- 匹配成功 → 获取 `id`(作为 typeId)和 `name`(作为 typeName)
|
|
|
+- 匹配失败 → 该行标记错误:「报销类别「XXX」不存在或不是末级节点」
|
|
|
+
|
|
|
+#### 4.3.2 项目编号匹配
|
|
|
+
|
|
|
+- 用 Excel 中的项目编号去 `cw_project_records` 表精确匹配
|
|
|
+- 匹配条件:`project_number = #{projectNumber}` AND `del_flag = 0`
|
|
|
+- 匹配成功 → 获取 `id`(作为 projectId)和 `project_name`(作为 projectName)
|
|
|
+- 匹配失败 → 该行标记错误:「项目编号「XXX」不存在」
|
|
|
+
|
|
|
+#### 4.3.3 报告号匹配(仅报告报销)
|
|
|
+
|
|
|
+- 用项目编号匹配到的 `cw_project_records.id`,关联查询报告号列表
|
|
|
+- 关联链路:`cw_project_records.id → cw_project_report.project_id → cw_project_report.id → cw_project_report_new_line.report_id → cw_project_report_new_line.report_no`
|
|
|
+- SQL:
|
|
|
+```sql
|
|
|
+SELECT report_no
|
|
|
+FROM cw_project_report_new_line a
|
|
|
+LEFT JOIN cw_project_report b ON a.report_id = b.id
|
|
|
+WHERE a.del_flag = 0 AND b.project_id = #{projectId}
|
|
|
+```
|
|
|
+- Excel 中的报告号必须在查询结果列表中
|
|
|
+- 匹配失败 → 该行标记错误:「报告号「XXX」不属于项目「YYY」」
|
|
|
+
|
|
|
+#### 4.3.4 报销人匹配
|
|
|
+
|
|
|
+- 用 Excel 中的报销人姓名去 `sys_user` 表匹配
|
|
|
+- 匹配条件:`name = #{userName}` AND `del_flag = 0`(受租户拦截器控制,只查当前租户)
|
|
|
+- 匹配结果处理:
|
|
|
+ - **0 条** → 标记错误:「报销人「XXX」不存在」
|
|
|
+ - **1 条** → 获取 `id`(作为 userId)、`name`(作为 userName)、`office_id`(作为 deptId)、部门名称(作为 deptName)
|
|
|
+ - **2 条及以上**(同名) → 标记错误:「报销人「XXX」存在重名,请手动录入后调整」
|
|
|
+- 不填 → 使用当前登录用户的信息
|
|
|
+
|
|
|
+### 4.4 金额计算
|
|
|
+
|
|
|
+- 汇总(numberCount)= 数电发票(number)+ 非数电发票(ordinaryNumber)+ 其他(subsidy)
|
|
|
+- 导入时数电发票费用(number)固定为 0(XML 未上传)
|
|
|
+- 如果 Excel 中汇总列为空,系统自动计算:`0 + ordinaryNumber + subsidy`
|
|
|
+- 如果 Excel 中汇总列有值,以填写值为准,但会校验是否等于三项之和,不等则标记警告(不阻断)
|
|
|
+
|
|
|
+### 4.5 票据张数计算
|
|
|
+
|
|
|
+- 汇总张数(receiptNumberCount)= 数电发票张数(receiptNumber)+ 非数电发票张数(ordinaryReceiptNumber)
|
|
|
+- 导入时数电发票张数(receiptNumber)固定为 0
|
|
|
+- receiptNumberCount = 0 + ordinaryReceiptNumber
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、数据写入
|
|
|
+
|
|
|
+### 5.1 主表(cw_reimbursement_info)
|
|
|
+
|
|
|
+每组生成一条主记录:
|
|
|
+
|
|
|
+| 字段 | 值来源 |
|
|
|
+|---|---|
|
|
|
+| id | UUID 生成 |
|
|
|
+| no | 调用 `IWorkAttachmentApi.genSerialNum()` 自动生成,业务编码 `101` |
|
|
|
+| userId / userName | 当前登录用户(经办人),通过 `TokenProvider` + `IUserApi` 获取 |
|
|
|
+| department | 当前用户所属部门 |
|
|
|
+| reimDate | 当天日期(`new Date()`) |
|
|
|
+| sourceType | Excel 中的报销类型(1/3/4) |
|
|
|
+| type | 固定 `'1'`(暂存) |
|
|
|
+| businessFlag | 固定 `'1'`(会计) |
|
|
|
+| reimbursementType | 固定 `'0'`(普通报销) |
|
|
|
+| remarks | 空(模板中无此列) |
|
|
|
+| createBy / createTime | 系统自动填充 |
|
|
|
+
|
|
|
+### 5.2 明细表
|
|
|
+
|
|
|
+根据 sourceType 写入不同的明细表:
|
|
|
+
|
|
|
+**sourceType = 1 → `cw_reimbursement_detail_info`**
|
|
|
+
|
|
|
+| 字段 | 值来源 |
|
|
|
+|---|---|
|
|
|
+| infoId | 主表 id |
|
|
|
+| userId / userName | 报销人(Excel 或默认当前用户) |
|
|
|
+| deptId / deptName | 报销人所属部门 |
|
|
|
+| typeId / typeName | 报销类别匹配结果 |
|
|
|
+| projectId / projectName | 项目编号匹配结果 |
|
|
|
+| reportNumber | 空 |
|
|
|
+| number | 固定 `'0'` |
|
|
|
+| ordinaryNumber | Excel 值,转 String |
|
|
|
+| subsidy | Excel 值,转 String |
|
|
|
+| numberCount | Excel 值或自动计算,转 String |
|
|
|
+| receiptNumber | 固定 `0` |
|
|
|
+| ordinaryReceiptNumber | Excel 值 |
|
|
|
+| receiptNumberCount | 自动计算 |
|
|
|
+| content | Excel 值 |
|
|
|
+| days | Excel 值 |
|
|
|
+| uniqueId | `UUID.randomUUID().toString()` |
|
|
|
+| reimbursementType | 固定 `'0'` |
|
|
|
+
|
|
|
+**sourceType = 3 → `cw_reimbursement_detail_info_report`**
|
|
|
+
|
|
|
+与 sourceType=1 相同,额外字段:
|
|
|
+- `reportId`:报告号匹配到的 `cw_project_report_new_line.id`
|
|
|
+
|
|
|
+**sourceType = 4 → `cw_reimbursement_detail_info_other`**
|
|
|
+
|
|
|
+与 sourceType=1 相同,区别:
|
|
|
+- `projectName` 字段存储的是「报销详情」自由文本(不是项目名)
|
|
|
+- 无 `projectId`、`reportNumber`、`reportId`
|
|
|
+
|
|
|
+### 5.3 不触发的流程
|
|
|
+
|
|
|
+- **不创建** Flowable 流程实例
|
|
|
+- **不发送** 审批通知
|
|
|
+- **不调用** 现有 `save()` 方法(直接操作 Mapper 插入)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、校验与错误处理
|
|
|
+
|
|
|
+### 6.1 校验策略
|
|
|
+
|
|
|
+- **部分导入**:校验通过的组正常写入,校验失败的组跳过
|
|
|
+- **全部校验完成后**统一返回结果,不中途停止
|
|
|
+
|
|
|
+### 6.2 错误 Excel 生成
|
|
|
+
|
|
|
+校验失败时,后端生成一份新的 Excel 文件:
|
|
|
+
|
|
|
+- 包含原始数据的所有行
|
|
|
+- 错误行用**红色字体 + 红色底色**标注
|
|
|
+- 最后一列追加「错误原因」,说明具体错误(如:「第3行:项目编号「XXX」不存在」)
|
|
|
+- 文件临时存储在服务器,返回文件路径供前端下载
|
|
|
+- 用户修改后重新上传导入
|
|
|
+
|
|
|
+### 6.3 错误类型汇总
|
|
|
+
|
|
|
+| 错误类型 | 错误信息示例 |
|
|
|
+|---|---|
|
|
|
+| 报销类型非法 | 「第X行:报销类型必须为项目报销、报告报销或其他报销」 |
|
|
|
+| 报销类别不存在 | 「第X行:报销类别「XXX」不存在或不是末级节点」 |
|
|
|
+| 项目编号不存在 | 「第X行:项目编号「XXX」不存在」 |
|
|
|
+| 报告号不匹配 | 「第X行:报告号「XXX」不属于项目「YYY」」 |
|
|
|
+| 报销人不存在的 | 「第X行:报销人「XXX」不存在」 |
|
|
|
+| 报销人重名 | 「第X行:报销人「XXX」存在重名,请手动录入后调整」 |
|
|
|
+| 同组不一致 | 「第X行:同组内报销类型/项目编号与组内第一行不一致」 |
|
|
|
+| 报告报销缺项目编号 | 「第X行:报告报销必须填写项目编号」 |
|
|
|
+| 项目报销缺项目编号 | 「第X行:项目报销必须填写项目编号」 |
|
|
|
+| 其他报销缺报销详情 | 「第X行:其他报销必须填写报销详情」 |
|
|
|
+| 汇总金额不等 | 「第X行:汇总金额与各项之和不符(已按填写值写入)」 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、导入结果反馈
|
|
|
+
|
|
|
+### 7.1 前端弹窗
|
|
|
+
|
|
|
+导入完成后弹窗显示:
|
|
|
+
|
|
|
+```
|
|
|
+导入完成
|
|
|
+成功:X 条报销单(共 Y 条明细)
|
|
|
+失败:Z 条(共 W 行数据)
|
|
|
+
|
|
|
+[下载错误文件] [关闭]
|
|
|
+```
|
|
|
+
|
|
|
+- 全部成功 → 只显示成功信息,无「下载错误文件」按钮
|
|
|
+- 全部失败 → 只显示失败信息 + 「下载错误文件」按钮
|
|
|
+- 部分成功 → 同时显示成功和失败信息 + 「下载错误文件」按钮
|
|
|
+
|
|
|
+### 7.2 成功后操作
|
|
|
+
|
|
|
+- 关闭导入弹窗
|
|
|
+- 刷新列表页数据(调用现有 `refreshList()` 方法)
|
|
|
+- **不自动跳转**到表单编辑页,用户自行在列表中查找
|
|
|
+
|
|
|
+### 7.3 失败后操作
|
|
|
+
|
|
|
+- 点击「下载错误文件」→ 下载标注错误的 Excel
|
|
|
+- 用户修改后重新上传
|
|
|
+- 导入弹窗保持打开,可重复操作
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、接口设计
|
|
|
+
|
|
|
+### 8.1 下载导入模板
|
|
|
+
|
|
|
+```
|
|
|
+GET /finance-server/reimbursementApproval/info/importTemplate
|
|
|
+```
|
|
|
+
|
|
|
+- 返回:Excel 文件流(`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`)
|
|
|
+- 功能:动态生成模板,类别下拉列表从数据库实时查询
|
|
|
+
|
|
|
+### 8.2 导入 Excel
|
|
|
+
|
|
|
+```
|
|
|
+POST /finance-server/reimbursementApproval/info/importFile
|
|
|
+Content-Type: multipart/form-data
|
|
|
+参数:file(MultipartFile)
|
|
|
+```
|
|
|
+
|
|
|
+- 返回:`ReimbursementImportResult`
|
|
|
+```json
|
|
|
+{
|
|
|
+ "successCount": 3,
|
|
|
+ "failCount": 2,
|
|
|
+ "successDetailCount": 5,
|
|
|
+ "failDetailCount": 3,
|
|
|
+ "errorFilePath": "/tmp/import_error_xxx.xlsx",
|
|
|
+ "errorMessages": [
|
|
|
+ {"row": 3, "message": "项目编号「XXX」不存在"},
|
|
|
+ {"row": 5, "message": "报销人「XXX」存在重名"}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 8.3 下载错误文件
|
|
|
+
|
|
|
+```
|
|
|
+GET /finance-server/reimbursementApproval/info/downloadImportError?filePath=/tmp/import_error_xxx.xlsx
|
|
|
+```
|
|
|
+
|
|
|
+- 返回:Excel 文件流
|
|
|
+- 下载后删除服务器临时文件
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 九、前端改动
|
|
|
+
|
|
|
+### 9.1 InfoList.vue
|
|
|
+
|
|
|
+- 工具栏新增「导入」按钮
|
|
|
+ - 位置:在「新建」按钮右侧
|
|
|
+ - 权限:`v-if="hasPermission('cw:reimbursement:info:import')"`
|
|
|
+ - 图标:`el-icon-upload2`
|
|
|
+- 引入 `ImportDialog` 组件
|
|
|
+- 点击按钮 → 打开导入弹窗
|
|
|
+
|
|
|
+### 9.2 ImportDialog.vue(新增组件)
|
|
|
+
|
|
|
+路径:`src/views/cw/reimbursementApproval/info/ImportDialog.vue`
|
|
|
+
|
|
|
+功能:
|
|
|
+1. **下载模板**按钮 → 调用 `importTemplate` 接口,浏览器下载
|
|
|
+2. **文件上传**区域
|
|
|
+ - 组件:`el-upload`
|
|
|
+ - 限制:`.xlsx`、`.xls` 格式
|
|
|
+ - 大小限制:10MB
|
|
|
+ - 手动上传(非自动)
|
|
|
+3. **开始导入**按钮
|
|
|
+ - 校验是否已选择文件
|
|
|
+ - 调用 `importFile` 接口
|
|
|
+ - 显示 loading 状态
|
|
|
+4. **结果展示**
|
|
|
+ - 成功/失败数量
|
|
|
+ - 错误明细列表(可展开)
|
|
|
+ - 「下载错误文件」按钮(有失败时显示)
|
|
|
+5. **关闭**按钮 → 关闭弹窗,触发父组件刷新列表
|
|
|
+
|
|
|
+### 9.3 ReimbursementApprovalService.js
|
|
|
+
|
|
|
+新增 3 个方法:
|
|
|
+
|
|
|
+```javascript
|
|
|
+importTemplate() {
|
|
|
+ return request({
|
|
|
+ url: prefix + "/reimbursementApproval/info/importTemplate",
|
|
|
+ method: "get",
|
|
|
+ responseType: "blob",
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+importFile(data) {
|
|
|
+ return request({
|
|
|
+ url: prefix + "/reimbursementApproval/info/importFile",
|
|
|
+ method: "post",
|
|
|
+ data: data,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+downloadImportError(filePath) {
|
|
|
+ return request({
|
|
|
+ url: prefix + "/reimbursementApproval/info/downloadImportError",
|
|
|
+ method: "get",
|
|
|
+ params: { filePath: filePath },
|
|
|
+ responseType: "blob",
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十、后端改动清单
|
|
|
+
|
|
|
+### 10.1 新增文件
|
|
|
+
|
|
|
+| 文件 | 路径 | 说明 |
|
|
|
+|---|---|---|
|
|
|
+| `ReimbursementImportDto.java` | `service/dto/` | Excel 行数据映射,`@Data` + `@Excel` 注解 |
|
|
|
+| `ReimbursementImportResult.java` | `service/dto/` | 导入结果 DTO |
|
|
|
+| `ImportDialog.vue` | `views/cw/reimbursementApproval/info/` | 导入弹窗组件 |
|
|
|
+| `报销导入模板.xlsx` | `resources/templates/` | 静态模板文件(类别列表动态填充) |
|
|
|
+
|
|
|
+### 10.2 修改文件
|
|
|
+
|
|
|
+| 文件 | 改动内容 |
|
|
|
+|---|---|
|
|
|
+| `CwReimbursementInfoController.java` | 新增 3 个接口方法:`importTemplate()`、`importFile()`、`downloadImportError()` |
|
|
|
+| `CwReimbursementInfoService.java` | 新增导入核心方法:`importReimbursement()`、`generateErrorExcel()` |
|
|
|
+| `CwReimbursementInfoMapper.java` | 新增查询方法:`findCategoryByName()`、`findProjectByNumber()`、`findReportNosByProjectId()`、`findUserByName()` |
|
|
|
+| `CwReimbursementInfoMapper.xml` | 新增对应 SQL |
|
|
|
+| `InfoList.vue` | 新增导入按钮 + 引入 ImportDialog 组件 |
|
|
|
+| `ReimbursementApprovalService.js` | 新增 3 个 API 方法 |
|
|
|
+
|
|
|
+### 10.3 不动的文件
|
|
|
+
|
|
|
+| 文件 | 原因 |
|
|
|
+|---|---|
|
|
|
+| `NewReimbursementForm.vue` | 导入生成暂存单据后用户手动打开编辑,走现有流程 |
|
|
|
+| `ReimbursementComponentTest.vue` | 同上 |
|
|
|
+| `CwReimbursementInfoService.save()` | 导入直接操作 Mapper 插入,不走 save 方法 |
|
|
|
+| 工作流相关代码 | 导入不触发流程 |
|
|
|
+| 现有明细表结构 | 直接往现有表插数据,不改表结构 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十一、开发顺序
|
|
|
+
|
|
|
+1. **后端 DTO** → `ReimbursementImportDto.java`、`ReimbursementImportResult.java`
|
|
|
+2. **后端 Mapper** → 新增查询方法和 SQL(类别、项目、报告号、用户匹配)
|
|
|
+3. **后端 Service** → 导入核心逻辑(解析→分组→校验→写入→错误Excel)
|
|
|
+4. **后端 Controller** → 3 个接口
|
|
|
+5. **前端 API** → Service 新增 3 个方法
|
|
|
+6. **前端组件** → `ImportDialog.vue`
|
|
|
+7. **前端列表** → `InfoList.vue` 加按钮
|
|
|
+8. **模板文件** → 放到 resources 或动态生成
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十二、验收标准
|
|
|
+
|
|
|
+1. 列表页显示「导入」按钮,有权限控制
|
|
|
+2. 点击下载模板 → 浏览器下载 Excel 文件,类别下拉列表数据正确
|
|
|
+3. 上传 Excel → 正确解析并分组
|
|
|
+4. 校验通过的数据 → 成功写入数据库,生成暂存状态的报销单
|
|
|
+5. 校验失败的数据 → 返回错误信息,可下载标注错误的 Excel
|
|
|
+6. 部分成功部分失败 → 成功的写入,失败的返回错误
|
|
|
+7. 空行 → 自动跳过,不报错
|
|
|
+8. 导入成功后 → 列表页刷新,显示新导入的暂存单据
|
|
|
+9. 暂存单据 → 用户打开后可正常编辑、上传发票、提交审批
|
|
|
+10. 同组一致性 → 组内报销类型、项目编号不一致时正确报错
|