文件
hyb-oa/.trellis/tasks/08-02-corporate-bank-account-payment-order/design.md
T

11 KiB
原始文件 Blame 文件历史

企业银行账户与付款制单设计

1. 边界与交付顺序

本任务在 OA 内建立付款准备能力,不连接银行 API、不提交付款、不代替网银复核。交付按同一任务内的三个层次顺序完成:

  1. 银行机构与公司付款账户主数据。
  2. 员工收款信息默认脱敏、规范化银行标识和付款批次冻结快照。
  3. 已复核批次的 BIFF8 制单导出、使用状态与审计。

三层共享同一迁移和权限设计,拆成独立任务会使中间版本产生无法制单或无法建批次的短暂合同,因此本轮保留为一个复杂 Trellis task,按提交批次分层验证。

2. 数据模型

2.1 BankInstitution

新增银行机构主数据:

  • id UUID
  • code 唯一、不可变的规范化代码;首版至少存在 CCB
  • name 展示名称
  • enabled、archived_at
  • created_at/by、updated_at/by

银行机构由 payment.account.manage 管理。员工收款资料与公司付款账户都引用同一主数据,不把银行名称字符串当身份键。已被引用的代码不能修改或物理删除;停用只阻止新绑定。

2.2 PaymentAccount

新增独立付款账户表并关联 LegalEntity:

  • id、legal_entity_id、bank_institution_id
  • account_name
  • account_number_encrypted
  • enabled、archived_at
  • created_at/by、updated_at/by

PostgreSQL 使用部分唯一索引保证同一法人主体最多一条 enabled=true AND archived_at IS NULL 的账户;服务端先做自然中文校验,数据库索引负责并发兜底。

已启用账户不原地覆盖账号。变更操作在同一事务内归档旧记录并创建新记录,保留历史身份;停用只改变旧记录状态。完整账号沿用现有字段加密服务,普通 DTO只计算脱敏尾号。

2.3 Employee 与 PaymentBatch

  • Employee 增加可空 bank_institution_id。存量数据不通过自由文本自动猜测;由 HR/admin 在 UI 中确认。
  • PaymentBatch 增加可空 legal_entity_id、payment_account_id、内部付款账户快照、制单次数、最近制单时间和操作者。
  • 报销付款项的 snapshot_json.payee 增加内部密文和银行机构代码;既有姓名、银行显示名和脱敏尾号继续保留。

付款账户快照仅存内部使用字段:账户记录 ID、法人主体 ID、账户名称、银行机构代码/名称、原始密文和脱敏尾号。API 映射层只返回名称、银行名称和脱敏尾号。

迁移全部新增为可空,禁止回填历史完整账号。历史批次因此保持可查看、可人工付款,但制单明确失败关闭。

3. 权限与威胁模型

新增权限:

权限 角色 用途
payment.account.manage finance_mgr、admin/* 银行机构与公司付款账户新增、替换、停用
payment.account.read finance、finance_mgr、admin/* 公司账户脱敏列表/详情
payment.account.read_full finance、finance_mgr、admin/* 填理由查看公司完整账号
payment.order.export 明确具有 finance 或 finance_mgr 业务角色者 已复核批次制单;单有 admin/* 不作为财务业务角色代办

employee.bank.manage 不授予财务或 system_admin。员工完整收款账号查看继续使用独立读取权限,但现有 GET 默认改为脱敏;完整值通过带原因的 POST 动作获取并先写审计。

公司付款账户维护防的是误配置、错误来源账户和缺少及时知情,不新增双人审批。真实受益人欺诈边界由“HR/admin 维护员工收款资料”和“finance/finance_mgr 制单”分离控制。

4. 主数据 API 与页面

4.1 公司付款账户

新增专用路由而不复用 Feature Flag:

  • 脱敏列出银行机构与付款账户。
  • 创建首个账户、替换当前账户、停用当前账户。
  • POST 完整账号查看动作,body 必须有 reason。

管理入口放在 PC 管理 -> 基础资料/财务配置 -> 企业银行账户。默认卡片只显示法人主体、账户名称、银行名称、脱敏尾号和状态;完整账号查看、替换、停用均使用明确动作与后果确认。移动端不提供配置入口。

账户新增、替换或停用在事务内写脱敏审计,并为所有 active admin 与 CEO 创建 OA 通知;通知不带完整账号、密文或本地路径,也不要求 CEO 具备账户主数据页面权限。

4.2 员工收款信息

人员档案增加收款信息区:

  • HR/admin 可选银行机构并录入账号。
  • finance/finance_mgr 只能看银行名称和脱敏尾号。
  • 员工本人在个人资料看到自己的银行名称和脱敏尾号。
  • 授权角色查看完整账号时填写原因;服务端先审计后返回,前端不会把完整值缓存到长期状态或普通查询缓存。

5. 批次创建与冻结快照

对 entityType=reimbursement 且银行渠道的新批次:

  1. 在同一事务锁定所选报销。
  2. 解析每张报销所有人的 legal_entity_id,要求非空且全批次一致。
  3. 查询该法人主体唯一启用的建行付款账户。
  4. 要求每位收款人有 CCB 银行机构与可用账号密文。
  5. 直接复制员工现有密文,不解密后重新加密;只为脱敏尾号做一次受控解密。
  6. 冻结付款账户和每一项收款信息后创建批次。

缺法人主体、混合法人主体、缺付款账户、非建行、缺账号或解密失败时,创建失败并一次性返回自然中文处理建议。其他渠道及非报销实体沿用原流程,但不具备银行制单能力。

API 映射函数必须显式挑选安全字段。测试扫描所有普通批次/API/AP 响应,断言不存在 v1: 密文、完整账号或原始 snapshot_json。

6. 制单服务与接口

6.1 资格校验

POST 制单接口接收 { reason },仅允许明确 finance/finance_mgr 业务角色、payment.order.export 权限和 reviewed 状态。它必须拒绝:

  • 历史/不完整快照
  • 非报销或非银行渠道
  • 公司或任一员工不是 CCB
  • 缺完整密文、账号解密失败
  • 缺金额、业务编号或付款账户
  • 其他四种批次状态

校验聚合所有业务阻塞项后统一返回;权限失败仍立即拒绝,不泄露批次内容。

6.2 确定性行与字段

项目按 entity_code ASC, id ASC 显式排序。唯一 ID 仅作同业务编号异常重复时的兜底,不进入银行文件。

  • 序号:从 1 开始的字符串。
  • 付款方账号/名称:付款账户快照。
  • 行别代码:固定字符串 01。
  • 收款方账号/名称、开户行:收款快照。
  • 联行号:空字符串。
  • 客户流水号:单一 buildCustomerReference(batchNo, rowIndex) 函数生成,写入前校验长度、字符集和批内唯一。
  • 金额:初版数值单元格 + 两位小数格式。
  • 用途:报销款。
  • 备注:报销单编号。
  • 短信标志:字符串 0;手机号与附加信息留空。

禁止部分成功:任意一项失败时不生成文件、不更新制单状态、不写成功导出审计。

6.3 BIFF8 与下载

  • 使用 SheetJS 官方精确 0.20.3 tarball并锁定 package-lock 完整性。
  • 运行时代码只调用生成 API,不解析上传文件或用户提供的工作簿。
  • 显式构建七个字符串字段的 { t: "s", v: value } 单元格;金额使用数值类型和两位小数格式。
  • 工作簿按顺序创建 Sheet1、Sheet2、Sheet3,Sheet1 固定 15 列表头。
  • 内存生成 bookType: "biff8" buffer,先完成审计和制单计数事务,再设置 application/vnd.ms-excel、安全附件名、nosniff、private, no-store 等响应头并发送。
  • 不写对象存储或服务器持久目录。网络中断后的已记录导出按保守口径视为已生成,用户可填写新原因重导。

7. 页面动作与进度

付款批次 DTO 返回脱敏付款账户摘要、paymentOrderExportCount、最近导出时间/人和 canExportPaymentOrder/阻塞原因。

  • 新快照 + reviewed + 未导出:顶部主动作 下载银行制单;回单区仍可直接上传,人工路径不被阻断。
  • 已导出且无回单:主动作 上传银行回单,重新下载制单 放入 更多。
  • 重导确认固定展示:重新下载不会作废之前的文件。如果上一份已在网银导入,请勿重复提交。
  • 已有回单:主动作 登记付款完成。
  • 旧批次或资料不完整:不显示可执行制单动作,展示具体原因;原人工付款动作保持。
  • 详情显示 未生成制单 或 已生成 N 次;处理进度按是否导出显示已批准的两种文案。

整体详情页结构统一仍由后续独立任务负责,本任务只在当前操作组和处理进度数据中接入制单状态,不顺带重构页面骨架。

8. 审计、通知与隐私

审计动作至少覆盖:付款账户创建/替换/停用、公司完整账号查看、员工完整账号查看、银行制单首次导出/重导和失败阻断摘要。审计 extra 只存业务 ID、法人主体、批次号、笔数、合计金额、原因、导出次数和结果代码;不存完整账号、密文、工作簿 buffer 或原始快照。

普通日志只记录固定错误码、批次 ID/业务号和数量。依赖或解密错误返回自然中文业务错误,禁止裸 Error 使页面白屏,也禁止把底层加密异常文本返回客户端。

9. 验证与依赖门禁

  • Node 单元/路由测试覆盖权限矩阵、单一启用账户并发约束、通知、审计、批次快照、历史拒绝、CCB-only、确定性排序、重导和隐私负向断言。
  • 使用合成数据生成 .xls,由 CI 明确安装的 xlrd==2.0.2 独立读回三个工作表、15 列、七个字符串字段、金额、中文和重复导出一致性。
  • 用合成非建行项证明整批失败且没有文件、制单计数或成功审计。
  • Excel/WPS 人工打开验证中文、长字符串和金额;企业网银只有存在明确“仅校验”步骤时才使用虚构建行数据做最终确认。
  • 安装依赖后运行 npm audit/许可证与 lockfile 审查;如官方精确 tarball不可重复获取或引入不可接受告警,暂停而非换用 registry 旧版。

10. 迁移、上线与回滚

  • 迁移只新增表、可空关系、可空快照和制单统计字段,不修改或回填存量账号/批次。
  • 部署后先由 admin 建立 CCB 主数据与公司付款账户,再由 HR/admin 为员工确认 CCB 机构;这两类生产写入必须另行批准,完整账号不经过聊天或仓库。
  • 配置完成前暂停创建新的银行渠道付款批次,避免产生无法制单的新历史快照;已有杨俊批次继续人工付款。
  • 公网 /ready、角色/隐私只读检查和一条合成/受控流程通过后才通知财务使用制单。
  • 代码回滚保留新增表和可空字段,不删除已产生的审计、通知或制单统计;旧应用忽略新字段并继续人工付款。