日期: 2026-08-18
状态: 已实现(待联调验收)
仓库: shanxia2.0(backend/+web-admin/)
入口页:http://localhost:3006/#/bx/reimburse/apply
界面模式: P01 标准列表扩展(工具栏 + 导入弹窗);参考 Fresh Mint 工具栏按钮规则
实现路径: 方案 A — 后端生成模板 + 后端导入
报销申请页已支持新增 / 编辑 / 删除。用户希望增加批量导入,并特别要求:
现状难点:
| # | 决策 | 说明 |
|---|---|---|
| D1 | 实现路径 A | 后端 openpyxl 动态生成模板(隐藏选项表 + DataValidation);后端解析导入;前端只做下载/上传与结果展示 |
| D2 | 行模型 B | Excel 一行 = 一条明细;相同「分组号」合成一张报销单 |
| D3 | 单头一致性 | 同组每一行都须填写申请人 / 报销日期 / 标题(标题可空),且必须完全一致;不一致则整组失败 |
| D4 | 空分组号 | 允许为空;空则该行单独成一张单 |
| D5 | 仅草稿 | 导入成功写入「待提交」草稿;不自动提交审批 |
| D6 | 标题可空 | 同组标题都空 → 按现有规则自动生成;若填写则同组必须相同 |
| D7 | 费用项范围 | 下拉含当前租户全部启用节点(含一级父节点),与页面 check-strictly 一致 |
| D8 | 部分成功 | 合法分组写入;失败组/行返回错误清单;不因单组失败回滚整文件 |
| D9 | 不做发票/附件 | v1 不导入关联发票与附件;用户可事后编辑草稿补票 |
| D10 | 模板含说明+示例 | 「填写说明」工作表 + 数据表内示例行(导入时跳过) |
在报销申请列表提供「下载模板」「批量导入」,使用户能通过带下拉的 Excel 批量创建多张(含多明细)草稿报销单。
#/bx/reimburse/apply 工具栏按钮 + 导入结果弹窗create_with_details / resolve_applicant 对齐INDIRECT)bx-mobile / 小程序导入| 工作表 | 可见性 | 用途 |
|---|---|---|
报销导入 |
可见 | 用户填写;含表头、示例行、数据验证下拉 |
填写说明 |
可见 | 规则说明、列说明、常见错误 |
_选项_费用项目 |
隐藏 | A 列:下拉展示文案;B 列:id(内部) |
_选项_申请人 |
隐藏 | A 列:下拉展示文案;B 列:userId(内部) |
生成时按当前登录用户与租户实时查询选项,不使用静态 xlsx 文件。
报销导入 列定义| 列名 | 必填 | 说明 |
|---|---|---|
| 分组号 | 否 | 相同非空值合成一张单;空 = 该行单独成单 |
| 申请人 | 是 | 下拉;展示 昵称(工号)(见 2.4) |
| 报销日期 | 是 | YYYY-MM-DD;同组必须一致 |
| 报销标题 | 否 | 同组都空则自动生成;填写则同组必须相同 |
| 费用项目 | 是 | 下拉;全路径,分隔符 / |
| 金额(元) | 是 | 数值且 > 0 |
| 报销事由 | 是 | 对应明细 remark |
| 发生时间 | 否 | YYYY-MM-DD;对应明细 occur_date |
DataValidation(type="list"),formula1 引用隐藏表列区域(如 =_选项_费用项目!$A$2:$A$N)。showErrorMessage=True,提示「请从下拉列表选择」。费用项目
/(半角斜杠两侧各一空格),例如 差旅费 / 市内交通;一级节点仅本级名称。status=启用 的节点进入选项表。(编码:xxx) 或短 id 后缀保证唯一;导入匹配同一套规则。申请人
{nickName}({userNo});userNo 为空时用稳定后缀(如用户 id 后 6 位)保证唯一。bx:reimburseApply:changeApplicant 或 super_admin:选项 = 本租户全部有效用户。ReimburseService.resolve_applicant(无权限强制本人)。填写说明 工作表(要点,实现时可微调文案):
_选项_* 工作表。报销导入 示例行(建议 2~3 行,浅灰底 +「示例」标识,导入跳过):
| 分组号 | 申请人 | 报销日期 | 报销标题 | 费用项目 | 金额 | 报销事由 | 发生时间 |
|---|---|---|---|---|---|---|---|
| G1 | (当前用户展示名) | (今日) | (选项表第一项路径) | 50.00 | 【示例】地铁通勤,请删除本行 | ||
| G1 | (同上) | (同上) | (选项表第二项或同第一项) | 300.00 | 【示例】住宿,请删除本行 | (昨日) | |
| (同上) | (同上) | (任选项) | 20.00 | 【示例】空分组号单独成单 |
示例行识别规则(按优先级):
__EXAMPLE__ / 约定标记;或【示例】 开头;或推荐采用 事由前缀 【示例】 + 行底色,解析简单、用户可见。
上传 xlsx
→ 读「报销导入」表,校验表头
→ 跳过空行、示例行
→ 按分组号分桶(空分组号 → 每行独立桶)
→ 对每个分组:
校验单头一致(申请人/日期/标题)
解析申请人、费用项、金额、事由、发生时间
任一行失败 → 整组失败,不写库
全部通过 → create_with_details(draft)
→ 汇总 success / errors 返回
对分组内所有数据行:
申请人、报销日期 字符串 trim 后必须完全一致。报销标题:要么全部为空(自动生成),要么全部非空且文本完全一致;不允许组内「有的空有的不空」。同组申请人不一致 / 同组报销标题不能部分为空。| 字段 | 规则 |
|---|---|
| 申请人 | 选项匹配成功;再 resolve_applicant |
| 报销日期 | 可解析为日期;建议规范为 YYYY-MM-DD |
| 费用项目 | 选项匹配且仍为启用 |
| 金额 | 数值且 > 0 |
| 报销事由 | 非空 trim |
| 发生时间 | 空或合法日期 |
ReimburseService.create_with_details(及 API 层 _build_details_data 同类逻辑)。invoice_ids / invoice_links。create_by = 当前操作人;applicant_* = 解析后的申请人。errors。整文件无法解析(非 xlsx、缺必要表头)→ HTTP 400。
可解析时业务成功响应(示意):
{
"successGroups": 2,
"successRows": 3,
"failGroups": 1,
"failRows": 2,
"created": [
{ "groupNo": "G1", "applyId": "...", "applyCode": "..." }
],
"errors": [
{ "groupNo": "G2", "rows": [5, 6], "message": "同组申请人不一致" },
{ "groupNo": "", "rows": [8], "message": "费用项目「xxx」不存在或已停用" }
]
}
rows 为 Excel 行号(含表头时从 1 计,与用户所见一致;实现时在说明中写明)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/reimburses/import-template |
下载模板(StreamingResponse xlsx) |
| POST | /api/reimburses/import |
multipart 文件字段(如 file) |
get_bx_current_user)。bx:reimburseApply:changeApplicant。落点建议:
backend/app/api/bx/reimburse.pybackend/app/services/bx/reimburse_import.py(或同目录等价命名)backend/app/utils/excel_data_validation.py(隐藏表 + list 验证),供后续其它导入复用页面:web-admin/src/views/bx/reimburse/apply/index.vue
工具栏(「新增报销申请」之后,中性描边 btn-ghost):
导入弹窗:
.xlsx / .xlsAPI 封装:web-admin/src/api/bx(或现有 reimburseApi)增加 downloadImportTemplate、importExcel。
不做:用 ArtExcelImport 前端解析后循环 create。
UI 约束:无遮罩模糊;按钮带图标;符合 Fresh Mint「仅首主按钮用主色」。
| 风险 | 对策 |
|---|---|
| 下拉被绕过 | 服务端精确匹配当前库 |
| 昵称重名 | 昵称(工号) 唯一展示 |
| 费用项同名 | 全路径;冲突加后缀 |
| 旧模板 | 说明要求重新下载;导入以当前主数据为准 |
| 无权限改申请人 | resolve_applicant 强制本人 |
| 示例入库 | 【示例】 前缀跳过 |
| WPS/在线表丢失验证 | 说明推荐桌面版;服务端仍校验 |
| 层 | 路径 |
|---|---|
| API | backend/app/api/bx/reimburse.py |
| Service | backend/app/services/bx/reimburse_import.py |
| Excel 工具 | backend/app/utils/(DataValidation 辅助) |
| 前端页 | web-admin/src/views/bx/reimburse/apply/index.vue |
| 前端 API | web-admin/src/api/bx(reimburse 相关) |
下一步:经用户确认本 spec 后,使用 writing-plans 产出实现计划(docs/superpowers/plans/)。