feat(reimbursement-small-team-form-validation): 完善小团队报销填报校验
这个提交包含在:
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,112 @@
|
||||
# 小团队报销填报与校验完善:技术设计
|
||||
|
||||
## 1. Design boundary
|
||||
|
||||
本任务在现有报销详情和预检链路内增量实现,不新增数据库表,不建设通用规则引擎,不改变审批、付款和资产状态机。
|
||||
|
||||
核心数据流:
|
||||
|
||||
```text
|
||||
明细/发票/附件数据库事实
|
||||
-> reimbursement precheck 聚合
|
||||
-> GET detail / POST precheck API
|
||||
-> PC 与手机详情呈现
|
||||
-> submit 事务重新汇总
|
||||
-> 现有审批规则、预算与工作流
|
||||
```
|
||||
|
||||
服务端负责金额、日期和证据状态的权威判断;前端只负责输入约束、可理解呈现和刷新。
|
||||
|
||||
## 2. Contracts
|
||||
|
||||
### 2.1 Runtime configuration
|
||||
|
||||
- 在 `server/src/config.js` 增加 `reimbursementMaxAgeDays`。
|
||||
- 环境变量:`REIMBURSEMENT_MAX_AGE_DAYS`。
|
||||
- 默认:`365`;允许:`180 | 365`;非法值在启动时失败,不静默回退。
|
||||
- 配置只影响警告阈值,不改变数据、审批或付款状态。
|
||||
|
||||
### 2.2 Reimbursement precheck extension
|
||||
|
||||
在现有预检响应中追加向后兼容字段:
|
||||
|
||||
```ts
|
||||
type ReimbursementEvidenceSummary = {
|
||||
invoiceCount: number;
|
||||
attachmentCount: number;
|
||||
items: Array<{
|
||||
itemId: string;
|
||||
linkedInvoiceCount: number;
|
||||
invoiceState: "linked" | "unlinked";
|
||||
}>;
|
||||
};
|
||||
|
||||
type ReimbursementTimelinessCheck = {
|
||||
itemId: string;
|
||||
expenseDate: string;
|
||||
ageDays: number;
|
||||
maxAgeDays: 180 | 365;
|
||||
overdue: boolean;
|
||||
crossCalendarYear: boolean;
|
||||
};
|
||||
|
||||
type ReimbursementAssetHint = {
|
||||
required: boolean;
|
||||
itemCount: number;
|
||||
};
|
||||
```
|
||||
|
||||
- 新字段缺失时旧客户端继续使用既有预检字段。
|
||||
- 超期、跨年度、明细未关联发票和资产候选通过现有 `warnings`/`nextActions` 合并,并保留结构化摘要供 UI 定位。
|
||||
- 明细未关联发票是警告而非新阻断,因为当前存在整单发票、OCR 未完成和合法无票差旅补助等兼容场景。
|
||||
|
||||
### 2.3 Amount validation
|
||||
|
||||
- 手工 item POST/PATCH 将 `amount=0` 视为无效。
|
||||
- 模板/差旅创建仍可内部写入零金额草稿,不经过手工 item POST 门禁。
|
||||
- submit 在同一事务和行锁内读取所有明细:无明细或任一金额 `<= 0` 时返回定位明确的校验错误。
|
||||
- 校验通过后调用现有重算逻辑,将返回值写回本次提交上下文;后续预算和审批规则只使用该值。
|
||||
- 不依赖前端合计,不接受客户端提交整单总额。
|
||||
|
||||
### 2.4 Header editing
|
||||
|
||||
- 扩展前端 API 为通用 `updateReimbursement`,旅行摘要函数可继续包装或迁移到该函数,避免重复 HTTP 合同。
|
||||
- PC 与手机复用小型编辑块:报销日期、报销事由、保存、取消。
|
||||
- 保存成功后刷新 detail、list 和 precheck 查询;使用现有未保存编辑保护模式,避免刷新丢失。
|
||||
|
||||
### 2.5 Totals and evidence UI
|
||||
|
||||
- PC 表格 footer 显示条数与 `detail.totalAmount`;手机列表尾显示相同事实。
|
||||
- 概况标签统一为“报销总额”。打印页保留现有合计行。
|
||||
- 证据提示在明细与材料区使用同一预检摘要,不在前端重新推导业务规则。
|
||||
- 当 OCR off 或发票金额为空时,不显示误导性的“发票金额差额”;本任务只展示数量和关联状态。
|
||||
|
||||
### 2.6 Asset reminder
|
||||
|
||||
- 服务端基于明细费用类型 `category=assets` 生成结构化提示。
|
||||
- UI 只显示候选数量、人工核对说明和授权后的资产模块入口。
|
||||
- 不调用资产创建 API,不保存报销侧资产状态。
|
||||
|
||||
## 3. Compatibility and migration
|
||||
|
||||
- 无 Prisma schema 或数据库迁移。
|
||||
- API 仅新增可选响应字段并收紧非法普通报销提交,现有合法报销兼容。
|
||||
- 历史草稿中的零金额明细可以继续查看和修改,但必须修正后才能提交。
|
||||
- 已提交、已通过和已付款记录保持只读,不回填时效或资产状态。
|
||||
- 现有附件仍为整单材料,现有发票绑定、OCR、查重和连续预览不变。
|
||||
|
||||
## 4. Risks and mitigations
|
||||
|
||||
- **日期偏移**:使用既有 date-only 工具和 `APP_TIMEZONE`,测试阈值当天、闰年和跨年边界。
|
||||
- **预检字段重复计算**:在服务端一次加载报销证据上下文,避免 PC/手机各自实现规则。
|
||||
- **模板回归**:只在手工明细接口和提交阶段收紧正金额,保留模板草稿零金额。
|
||||
- **既有未绑定发票**:只警告不阻断,保持现有整单至少一张发票门禁。
|
||||
- **资产权限泄露**:资产入口按现有权限决定,预检不返回资产卡或敏感字段。
|
||||
- **提交总额竞争**:继续使用报销行锁,并在同一事务内重算后再做预算和审批规则选择。
|
||||
|
||||
## 5. Rollout and rollback
|
||||
|
||||
- 部署前明确 `REIMBURSEMENT_MAX_AGE_DAYS`;缺省为 365。
|
||||
- 先部署服务端兼容字段和校验,再部署前端展示;旧前端可继续工作。
|
||||
- 回滚前端不会破坏服务端数据;回滚服务端仅移除新提示和正金额收紧,无迁移回滚。
|
||||
- 公网验收覆盖普通办公用品、宽带/技术服务、IT 硬件三类合成记录,不使用真实票据内容。
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,62 @@
|
||||
# 小团队报销填报与校验完善:实施计划
|
||||
|
||||
## 1. Backend contracts and focused tests
|
||||
|
||||
- [ ] 在配置层增加并测试 `REIMBURSEMENT_MAX_AGE_DAYS=180|365`,默认 365,非法配置启动失败。
|
||||
- [ ] 在报销预检服务增加证据摘要、时效检查和资产提示,复用 date-only/金额工具。
|
||||
- [ ] 为默认期限、180 天、阈值边界、跨年度、未关联发票、整单附件和 `assets` 类别补单测。
|
||||
|
||||
## 2. Authoritative amount validation
|
||||
|
||||
- [ ] 收紧手工 item POST/PATCH:金额必须大于零,返回可定位错误。
|
||||
- [ ] submit 行锁事务读取明细,拒绝空明细和非正金额明细。
|
||||
- [ ] submit 在预算与审批规则前重新汇总并使用权威总额。
|
||||
- [ ] 补充空单、零金额模板草稿、修改后提交、陈旧总额修复和审批金额选择回归测试。
|
||||
|
||||
## 3. Shared frontend API and header editing
|
||||
|
||||
- [ ] 扩展 reimbursement API 类型与通用 PATCH 函数。
|
||||
- [ ] PC 草稿/驳回详情增加报销事由和日期编辑。
|
||||
- [ ] 手机草稿/驳回详情增加适合小屏的同等编辑能力。
|
||||
- [ ] 保存后刷新 detail/list/precheck,并验证非编辑状态和无权限用户只读。
|
||||
|
||||
## 4. Totals, evidence and asset hints
|
||||
|
||||
- [ ] PC 明细表尾增加条数与报销总额,概况标签改为“报销总额”。
|
||||
- [ ] 手机明细列表尾增加条数与报销总额。
|
||||
- [ ] PC/手机展示明细发票关联状态、整单附件数、时效警告和资产人工建卡提醒。
|
||||
- [ ] 资产入口沿用现有权限;打印页合计不回归。
|
||||
|
||||
## 5. Validation and review gates
|
||||
|
||||
- [ ] `cd server && npm run prisma:validate`
|
||||
- [ ] `cd server && node --check src/config.js && node --check src/services/feeControl.js && node --check src/routes/reimbursements.js`
|
||||
- [ ] 运行本任务新增的后端 Vitest 及现有 reimbursement detail/create/expense-standard 聚焦回归。
|
||||
- [ ] `cd client && npm run typecheck && npm run build`
|
||||
- [ ] 运行 PC 与 mobile/wecom 报销详情 E2E,覆盖合计、编辑、时效、证据和资产提醒。
|
||||
- [ ] 运行 `node scripts/qa/check-process-contract.mjs --baseline --json`。
|
||||
- [ ] 运行 `node scripts/qa/audit-docs.mjs --check-registry` 和 `node scripts/qa/check-trellis-governance.mjs`。
|
||||
- [ ] `git diff --check`
|
||||
- [ ] 因提交校验和跨层预检发生变化,实施后进行一次只读 review,重点检查金额竞争、模板兼容和权限边界。
|
||||
|
||||
## 6. Documentation, deployment and rollback
|
||||
|
||||
- [ ] 仅在形成稳定规则时更新现有报销事实源或 Trellis spec,不新建平行文档。
|
||||
- [ ] 记录环境配置默认值、生产取值和回滚方式,不记录 Secret 或真实票据。
|
||||
- [ ] 用户批准后启动任务;实现、检查、必要 spec 更新完成后使用中文 commit,并引用任务名。
|
||||
- [ ] 若需用户线上测试,完成部署、公网健康检查和合成数据关键路径冒烟后再通知。
|
||||
|
||||
## Rollback points
|
||||
|
||||
- 配置/预检字段为第一回滚点:可独立移除,不影响数据库。
|
||||
- 正金额提交门禁为第二回滚点:若发现合法流程被误阻断,先回滚该提交校验,不绕过发票/权限门禁。
|
||||
- 前端编辑与提示为第三回滚点:可回滚 UI,服务端新增字段保持向后兼容。
|
||||
|
||||
## 执行记录(2026-08-04)
|
||||
|
||||
- 服务端聚焦回归 9 个文件、86 个用例全部通过;Prisma 与所改 Node 文件语法检查通过。
|
||||
- 前端 `typecheck`、生产构建通过;R102 合成 API 浏览器回归覆盖 PC/手机合计、事由日期编辑和材料提示,原 5 个用例通过,新增/修正用例单独复跑通过。
|
||||
- 任务、环境契约、流程合同、文档注册、Trellis 治理、敏感内容和 `git diff --check` 均通过;文档注册保留既有 22 个未注册文件 warning,无新增错误。
|
||||
- 全量后端 1228 个用例中 1224 个通过;3 个既有付款 BIFF8 导出用例因本机未安装 `server/requirements/payment-order-biff8.txt` 固定 Python 依赖失败,另 1 个本任务引起的环境键计数已修正并复跑通过。
|
||||
- 已完成主会话只读复核,修正“已完成报销不显示资产提醒”和“超期且跨年只显示一个提示”两处跨层遗漏。
|
||||
- 未执行生产部署、生产配置写入或公网数据操作;如需线上测试,须按项目规则另行取得明确部署授权。
|
||||
@@ -0,0 +1,96 @@
|
||||
# 小团队报销填报与校验完善
|
||||
|
||||
## Goal
|
||||
|
||||
面向公司内部 5–10 人使用场景,在不扩展为通用费控产品的前提下,完善普通报销填报、提交预检和证据提示,使员工容易填写、审批人看得懂、财务能够核对、付款后能够追溯。
|
||||
|
||||
## Background and confirmed facts
|
||||
|
||||
- 系统已有报销单、明细、PDF 发票、补充附件、审批、付款批次和资产台账主链路。
|
||||
- `ReimbursementForm.total_amount` 已由明细新增、修改、删除操作自动重算;打印页已有明细合计,但 PC 和手机详情的明细区域没有就地合计。
|
||||
- 普通报销 PATCH 已支持修改 `reimbursementDate` 和 `description`,前端目前没有通用的事由/日期编辑入口。
|
||||
- 当前提交只校验总金额非负和整单至少一张发票,不能在服务端可靠阻止空明细、零金额明细或陈旧总额进入审批。
|
||||
- 发票已有可选明细关联;补充附件当前属于整单。本任务不引入发票多对多关系或附件明细关联。
|
||||
- 费用类型已有 `assets` 类别;资产事实源要求“资产候选 + 管理员人工建卡”,不得因报销提交、审批或付款自动生成资产卡。
|
||||
- 本轮讨论已决定不增加“历史费用补报”标识。票据新旧由费用日期、发票日期和提交日期推导。
|
||||
|
||||
## Scope
|
||||
|
||||
- 报销详情 PC、手机的明细合计和普通报销事由/日期编辑。
|
||||
- 报销明细正金额校验、提交时权威总额重算。
|
||||
- 180/365 天票据时效配置及超期、跨年度非阻断提示。
|
||||
- 明细发票关联、整单附件数量的材料完整性提示。
|
||||
- `assets` 类别的人工资产建卡提醒和授权入口。
|
||||
- 所需的前后端类型、聚焦测试、现有事实源/spec 更新及部署配置说明。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 明细合计就地展示
|
||||
|
||||
- PC 报销明细表尾显示明细数量和报销合计。
|
||||
- 手机报销明细卡片列表底部显示明细数量和报销合计。
|
||||
- 概况区将含糊的“金额”统一为“报销总额”或“合计金额”。
|
||||
- 展示使用服务端返回的 `totalAmount`,不增加可编辑总额字段。
|
||||
|
||||
### R2 普通报销正金额与提交一致性
|
||||
|
||||
- 员工手工新增或修改普通报销明细时,金额必须大于零。
|
||||
- 提交时服务端必须确认至少一条明细,且不存在零金额或负金额明细。
|
||||
- 提交事务内重新按明细汇总并持久化总金额,审批规则、预算检查和工作流快照使用该权威金额。
|
||||
- 差旅申请或模板创建阶段产生的零金额占位明细仍可保存为草稿,但补齐正金额前不得提交。
|
||||
- 错误返回可定位到明细或总金额,前端保留已填内容并给出中文提示。
|
||||
|
||||
### R3 普通报销事由和报销日期编辑
|
||||
|
||||
- 草稿和驳回状态的参与人可在 PC、手机详情编辑“报销事由”和“报销日期”。
|
||||
- 复用现有报销 PATCH 和权限/状态约束,不新增独立保存接口。
|
||||
- 事由不是“历史费用”分类;员工按真实用途填写,例如设备采购、办公网络或办公用品采购。
|
||||
|
||||
### R4 票据时效提示
|
||||
|
||||
- 服务端配置 `REIMBURSEMENT_MAX_AGE_DAYS`,默认 `365`,只允许 `180` 或 `365`,部署时可调整。
|
||||
- 以每条明细的费用发生日期为主计算距当前业务日期的天数;发票日期只作为已有材料摘要,不替代费用日期。
|
||||
- 超过配置期限或费用日期与提交日期跨自然年度时,在预检中产生中文警告,但本任务不自动阻断提交。
|
||||
- 页面说明实际天数、当前期限和需要财务确认的下一步,不出现“历史费用补报”标签。
|
||||
- 日期计算使用 `APP_TIMEZONE` 和 date-only 语义,不因 UTC 转换偏移一天。
|
||||
|
||||
### R5 明细材料完整性提示
|
||||
|
||||
- 预检返回整单发票数、整单补充附件数,以及每条明细已关联发票数。
|
||||
- PC、手机详情明确区分“已关联发票”“待关联发票”和“整单补充材料”。
|
||||
- 普通员工文案不显示 `invoice pool`、OCR 内部码、UUID 或数据库字段。
|
||||
- 本任务保持现有“整单至少一张发票”提交门禁;缺少某条明细关联发票先作为可确认警告,不新建费用材料政策中心。
|
||||
- 本任务不改变 PDF 发票、补充附件、付款回单和资产材料的既有上传边界。
|
||||
|
||||
### R6 设备资产登记提醒
|
||||
|
||||
- 报销包含 `assets` 类别明细时,PC、手机详情和提交预检显示“可能形成资产,报销完成后由管理员核对并手动建卡”的非阻断提醒。
|
||||
- 不自动创建资产卡、不自动判断会计固定资产、不复制资产建卡表单到报销页。
|
||||
- 有资产台账查看权限的用户可从提醒进入资产模块;其他用户只看到下一步说明。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC1:PC 和手机的报销明细区域均显示与服务端 `totalAmount` 一致的明细数量和合计;新增、修改、删除后自动刷新。
|
||||
- [ ] AC2:手工新增/修改 `0` 金额明细被服务端拒绝;空明细或含非正金额明细的报销无法提交;模板零金额草稿仍可创建和编辑。
|
||||
- [ ] AC3:提交事务重新汇总权威总额,审批规则和后续提交结果不使用陈旧总额。
|
||||
- [ ] AC4:草稿/驳回报销可在 PC 和手机编辑报销事由和日期;其他状态不可编辑,权限边界不放宽。
|
||||
- [ ] AC5:默认 365 天配置有效,可切换为 180 天;超期和跨年度产生可理解警告但不阻断,date-only 边界无偏移。
|
||||
- [ ] AC6:预检及详情能显示每条明细的发票关联状态和整单附件数;现有整单发票门禁、OCR、查重和附件权限不回归。
|
||||
- [ ] AC7:`assets` 类别触发人工资产登记提醒,不产生任何资产卡写入;无权限用户不会获得资产数据或管理入口。
|
||||
- [ ] AC8:PC、手机和打印页使用一致的金额语义;普通用户页面不出现内部技术术语。
|
||||
- [ ] AC9:相关后端测试、前端 typecheck/build、PC/移动报销 E2E、Trellis 治理和 `git diff --check` 全部通过。
|
||||
- [ ] AC10:若改动面向公网用户,部署、公网冒烟和关键页面检查通过后才告知可以测试。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- “历史费用补报”字段、标识、筛选或独立流程。
|
||||
- 通用费用政策中心、复杂预算、银企直连、自动付款、自动记账或完整电子会计档案系统。
|
||||
- 发票与明细多对多、补充附件关联具体明细、发票验真能力扩展。
|
||||
- 自动创建资产卡、折旧/残值/会计科目判断。
|
||||
- 改造采购流程、付款状态机、审批角色或权限模型。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- Backend:Prisma validate、`node --check`、报销详情/创建/费用标准相关 Vitest,并补充本任务聚焦用例。
|
||||
- Frontend:`npm run typecheck`、`npm run build`、PC 和 mobile/wecom 报销详情 E2E。
|
||||
- Process:Trellis task validate、流程合同、治理、文档注册审计和 `git diff --check`。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "reimbursement-small-team-form-validation",
|
||||
"name": "reimbursement-small-team-form-validation",
|
||||
"title": "小团队报销填报与校验完善",
|
||||
"description": "完善小团队内部报销的明细合计、正金额校验、事由编辑、票据时效提示、材料完整性提示和资产登记提醒。",
|
||||
"status": "in_progress",
|
||||
"dev_type": "feature",
|
||||
"scope": "server/client/docs/tests",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-04",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户