# 企业银行账户与付款制单设计 ## 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`、角色/隐私只读检查和一条合成/受控流程通过后才通知财务使用制单。 - 代码回滚保留新增表和可空字段,不删除已产生的审计、通知或制单统计;旧应用忽略新字段并继续人工付款。