文件
hyb-oa/.trellis/tasks/08-04-reimbursement-small-team-form-validation/design.md
T
wangsli 3aebfc5e04
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
chore(reimbursement-small-team-form-validation): 恢复任务继续部署
2026-08-04 23:48:03 +08:00

4.9 KiB

小团队报销填报与校验完善:技术设计

1. Design boundary

本任务在现有报销详情和预检链路内增量实现,不新增数据库表,不建设通用规则引擎,不改变审批、付款和资产状态机。

核心数据流:

明细/发票/附件数据库事实
  -> 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

在现有预检响应中追加向后兼容字段:

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 硬件三类合成记录,不使用真实票据内容。