BIZ8-9 补强付款导出权限回归

这个提交包含在:
2026-05-13 21:05:53 +08:00
父节点 1c9d0dc612
当前提交 202a77207a
共修改 2 个文件,包含 54 行新增和 2 行删除
@@ -8,7 +8,7 @@ BIZ8-5,批量样票回归设计,"设计 OCR 批量样票回归,区分 mock、
BIZ8-6,OCR 异步终态体验,"优化或验证 OCR 异步处理中、成功、失败、pending 和重试的页面体验。","1) 异步状态可解释;2) 用户知道是否需要等待或重试;3) 失败不阻塞无关流程;4) 移动端不重叠。","流程级验证:上传发票 -> 观察 OCR pending/success/failure -> PC 和移动查看报销详情状态。",playwright|node|manual,DONE,DONE,DONE,server/src|client/src|docs/产品设计规则.md,"[""BIZ8-5""]",npx playwright test tests/e2e/r111-invoice-reimbursement-guard.spec.ts --workers=1 --reporter=list 通过 2 tests;覆盖 PC 发票上传后 pending 轮询并自动添加、企业微信移动端 pending 提示、识别失败发票显示可删除后重新上传、图片附件分离和移动布局指标;未改业务代码或页面代码。
BIZ8-7,重复票据例外流程,"设计重复票据发现后的例外处理流程,避免误拦截合法场景或放过风险。","1) 重复提示清楚;2) 人工例外有权限和记录;3) 不绕过财务复核;4) 审计可追溯。","流程级验证:提交重复票据 -> 检查拦截或例外路径 -> 财务复核 -> 查看审计记录。",node|playwright|manual,DONE,DONE,DONE,server/src|client/src|docs/报销发票附件规则.md,"[""BIZ8-2""]",npm test -- tests/unit/invoiceGovernance.dedup.test.js tests/unit/routes/invoicePool.upload.test.js 通过 2 files 11 tests,覆盖重复可用票据复用、他人或已使用票据阻断和可理解提示;一次性 Fastify 注入验证 /api/invoice-pool/:id/override-duplicate 无 expense.finance.override_duplicate 权限返回 403,有权限时写 is_duplicate_override、原单号、原因和 INVOICE_DUPLICATE_OVERRIDDEN 审计;未改业务代码。
BIZ8-8,财务台账扫读性,"提升财务台账的扫读性,优先解决状态、金额、申请人、项目和票据提示的扫描效率。","1) 财务能快速识别待处理项;2) 状态和金额不混乱;3) 权限不泄露;4) PC 和移动布局可用。","流程级验证:财务查看台账 -> 筛选或扫读待付款报销 -> 检查状态、金额、申请人和票据提示。",playwright|npm|manual,DONE,DONE,DONE,client/src|server/src|docs/产品设计规则.md,"[""BIZ8-1""]",完成:财务台账读取链路补 project select/map;关键字搜索支持项目编号和名称;AP 台账表格展示项目名称和项目编号;AP 导出财务台账段增加项目列;不改付款状态机 不改权限 不引入 ERP 不改移动入口;验证 prisma validate 通过;node --check financeLedger apOperations exports 通过;server unit 2 files 3 tests passed;client typecheck/build 通过;Playwright biz8-ap-ledger-project 1 passed。
BIZ8-9,付款和导出增强,"增强或验证财务付款标记和导出路径,保证权限、状态和审计一致。","1) 只有财务可执行付款或导出;2) 状态转换合法;3) 导出不泄露无权限数据;4) 操作可追溯。","流程级验证:财务付款或导出 -> 普通员工和管理员直连复核 -> 检查状态和权限。",node|bash|manual,TODO,TODO,TODO,server/src|client/src|server/scripts,"[""BIZ8-8""]",待批准方案:BIZ8-8 已完成 AP 财务台账项目列和导出项目列;BIZ8-9 剩余工作聚焦现有 AP export 权限 付款批次和报销付款状态一致性回归;不新增 ERP 实时集成 不新增付款状态机;如需直接付款按钮必须另给状态机和回滚方案。
BIZ8-9,付款和导出增强,"增强或验证财务付款标记和导出路径,保证权限、状态和审计一致。","1) 只有财务可执行付款或导出;2) 状态转换合法;3) 导出不泄露无权限数据;4) 操作可追溯。","流程级验证:财务付款或导出 -> 普通员工和管理员直连复核 -> 检查状态和权限。",node|bash|manual,DONE,DONE,DONE,server/src|client/src|server/scripts,"[""BIZ8-8""]",完成:仅补强 AP export 权限单测和测试错误契约 不改业务代码;覆盖财务导出成功并写 EXPORT_AP_OPERATIONS 审计 普通员工和 admin-only 无财务权限导出 403 且不写审计;付款批次报销流保持 reviewed 到 paid invoice archived ERP outbox notification audit followup 一致;验证 prisma validate 通过;node --check exports paymentBatches financeLedger 通过;npm test exports.apOperations paymentBatches.reimbursement financeLedger 3 files 8 tests passed;继续冻结直接付款按钮 ERP 实时集成和新付款状态机。
BIZ8-10,费用提示轻量规则,"设计轻量费用提示规则,作为填报辅助而非重预算控制。","1) 提示不阻断合法报销;2) 规则来源清楚;3) 不承诺预算控制;4) 用户可理解。","流程级验证:填写不同金额或类型报销 -> 检查费用提示 -> 提交和审批不被误阻断。",manual|playwright,DONE,DONE,DONE,docs/报销发票附件规则.md|client/src|server/src,"[""BIZ8-1""]",已在 docs/需求-报销发票OCR规约.md 增加 BIZ8 费用提示轻量规则,明确材料提示、金额提示、费用标准 warn、费用标准 block 和预算提示边界;npm test -- tests/unit/services/expenseStandards.check.test.js tests/unit/routes/reimbursements.expense-standards.test.js 通过 2 files 9 tests,覆盖 warn 不阻断、block 阻断和 manual_review 优先级;未改业务代码。
BIZ8-11,项目关联增强,"增强报销、采购或合同与项目轻管理的关联,保持轻量不扩展成重型项目管理。","1) 项目选择和展示清楚;2) 无项目时可解释;3) 项目权限不泄露;4) 不扩展复杂 WBS。","流程级验证:创建或选择项目 -> 提交报销/采购 -> 财务或负责人查看关联 -> 检查权限。",node|playwright|manual,TODO,TODO,TODO,docs/需求-项目轻管理规约.md|server/src|client/src,"[""BIZ8-1""]",待批准方案:报销和合同已有项目关联;采购模型缺 project_id 且项目详情页无采购摘要;批准后需 Prisma migration 给 purchase_requests 增 nullable project_id 和索引 relation;后端采购创建/更新/详情/列表补项目字段并校验项目可读和未归档;前端采购表单/详情/列表补项目选择与展示;项目详情补采购摘要;必须 reviewer 和权限回归;继续冻结复杂 WBS 预算控制和 ERP。
BIZ8-12,移动端业务入口评估,"评估报销、OCR、财务和项目轻量能力在移动端和企业微信入口的可用性。","1) 移动入口不重复;2) 企业微信入口回 OA;3) 财务敏感能力不误暴露;4) 新增入口进入移动矩阵。","流程级验证:员工和财务角色在移动端进入业务入口 -> 检查报销/OCR/项目状态和权限。",playwright|manual,TODO,TODO,TODO,client/src|docs/产品设计规则.md|docs/trialrun/真机公网测试清单.md,"[""BIZ8-8"",""BIZ8-11""]",真机验证不能被自动化替代。
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
8 BIZ8-6 OCR 异步终态体验 优化或验证 OCR 异步处理中、成功、失败、pending 和重试的页面体验。 1) 异步状态可解释;2) 用户知道是否需要等待或重试;3) 失败不阻塞无关流程;4) 移动端不重叠。 流程级验证:上传发票 -> 观察 OCR pending/success/failure -> PC 和移动查看报销详情状态。 playwright|node|manual DONE DONE DONE server/src|client/src|docs/产品设计规则.md ["BIZ8-5"] npx playwright test tests/e2e/r111-invoice-reimbursement-guard.spec.ts --workers=1 --reporter=list 通过 2 tests;覆盖 PC 发票上传后 pending 轮询并自动添加、企业微信移动端 pending 提示、识别失败发票显示可删除后重新上传、图片附件分离和移动布局指标;未改业务代码或页面代码。
9 BIZ8-7 重复票据例外流程 设计重复票据发现后的例外处理流程,避免误拦截合法场景或放过风险。 1) 重复提示清楚;2) 人工例外有权限和记录;3) 不绕过财务复核;4) 审计可追溯。 流程级验证:提交重复票据 -> 检查拦截或例外路径 -> 财务复核 -> 查看审计记录。 node|playwright|manual DONE DONE DONE server/src|client/src|docs/报销发票附件规则.md ["BIZ8-2"] npm test -- tests/unit/invoiceGovernance.dedup.test.js tests/unit/routes/invoicePool.upload.test.js 通过 2 files 11 tests,覆盖重复可用票据复用、他人或已使用票据阻断和可理解提示;一次性 Fastify 注入验证 /api/invoice-pool/:id/override-duplicate 无 expense.finance.override_duplicate 权限返回 403,有权限时写 is_duplicate_override、原单号、原因和 INVOICE_DUPLICATE_OVERRIDDEN 审计;未改业务代码。
10 BIZ8-8 财务台账扫读性 提升财务台账的扫读性,优先解决状态、金额、申请人、项目和票据提示的扫描效率。 1) 财务能快速识别待处理项;2) 状态和金额不混乱;3) 权限不泄露;4) PC 和移动布局可用。 流程级验证:财务查看台账 -> 筛选或扫读待付款报销 -> 检查状态、金额、申请人和票据提示。 playwright|npm|manual DONE DONE DONE client/src|server/src|docs/产品设计规则.md ["BIZ8-1"] 完成:财务台账读取链路补 project select/map;关键字搜索支持项目编号和名称;AP 台账表格展示项目名称和项目编号;AP 导出财务台账段增加项目列;不改付款状态机 不改权限 不引入 ERP 不改移动入口;验证 prisma validate 通过;node --check financeLedger apOperations exports 通过;server unit 2 files 3 tests passed;client typecheck/build 通过;Playwright biz8-ap-ledger-project 1 passed。
11 BIZ8-9 付款和导出增强 增强或验证财务付款标记和导出路径,保证权限、状态和审计一致。 1) 只有财务可执行付款或导出;2) 状态转换合法;3) 导出不泄露无权限数据;4) 操作可追溯。 流程级验证:财务付款或导出 -> 普通员工和管理员直连复核 -> 检查状态和权限。 node|bash|manual TODO DONE TODO DONE TODO DONE server/src|client/src|server/scripts ["BIZ8-8"] 待批准方案:BIZ8-8 已完成 AP 财务台账项目列和导出项目列;BIZ8-9 剩余工作聚焦现有 AP export 权限 付款批次和报销付款状态一致性回归;不新增 ERP 实时集成 不新增付款状态机;如需直接付款按钮必须另给状态机和回滚方案。 完成:仅补强 AP export 权限单测和测试错误契约 不改业务代码;覆盖财务导出成功并写 EXPORT_AP_OPERATIONS 审计 普通员工和 admin-only 无财务权限导出 403 且不写审计;付款批次报销流保持 reviewed 到 paid invoice archived ERP outbox notification audit followup 一致;验证 prisma validate 通过;node --check exports paymentBatches financeLedger 通过;npm test exports.apOperations paymentBatches.reimbursement financeLedger 3 files 8 tests passed;继续冻结直接付款按钮 ERP 实时集成和新付款状态机。
12 BIZ8-10 费用提示轻量规则 设计轻量费用提示规则,作为填报辅助而非重预算控制。 1) 提示不阻断合法报销;2) 规则来源清楚;3) 不承诺预算控制;4) 用户可理解。 流程级验证:填写不同金额或类型报销 -> 检查费用提示 -> 提交和审批不被误阻断。 manual|playwright DONE DONE DONE docs/报销发票附件规则.md|client/src|server/src ["BIZ8-1"] 已在 docs/需求-报销发票OCR规约.md 增加 BIZ8 费用提示轻量规则,明确材料提示、金额提示、费用标准 warn、费用标准 block 和预算提示边界;npm test -- tests/unit/services/expenseStandards.check.test.js tests/unit/routes/reimbursements.expense-standards.test.js 通过 2 files 9 tests,覆盖 warn 不阻断、block 阻断和 manual_review 优先级;未改业务代码。
13 BIZ8-11 项目关联增强 增强报销、采购或合同与项目轻管理的关联,保持轻量不扩展成重型项目管理。 1) 项目选择和展示清楚;2) 无项目时可解释;3) 项目权限不泄露;4) 不扩展复杂 WBS。 流程级验证:创建或选择项目 -> 提交报销/采购 -> 财务或负责人查看关联 -> 检查权限。 node|playwright|manual TODO TODO TODO docs/需求-项目轻管理规约.md|server/src|client/src ["BIZ8-1"] 待批准方案:报销和合同已有项目关联;采购模型缺 project_id 且项目详情页无采购摘要;批准后需 Prisma migration 给 purchase_requests 增 nullable project_id 和索引 relation;后端采购创建/更新/详情/列表补项目字段并校验项目可读和未归档;前端采购表单/详情/列表补项目选择与展示;项目详情补采购摘要;必须 reviewer 和权限回归;继续冻结复杂 WBS 预算控制和 ERP。
14 BIZ8-12 移动端业务入口评估 评估报销、OCR、财务和项目轻量能力在移动端和企业微信入口的可用性。 1) 移动入口不重复;2) 企业微信入口回 OA;3) 财务敏感能力不误暴露;4) 新增入口进入移动矩阵。 流程级验证:员工和财务角色在移动端进入业务入口 -> 检查报销/OCR/项目状态和权限。 playwright|manual TODO TODO TODO client/src|docs/产品设计规则.md|docs/trialrun/真机公网测试清单.md ["BIZ8-8","BIZ8-11"] 真机验证不能被自动化替代。
@@ -1,6 +1,7 @@
const Fastify = require("fastify");
const exportsRoutes = require("../../../src/routes/exports");
const { AppError } = require("../../../src/utils/AppError");
const ACTOR_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
@@ -140,7 +141,12 @@ async function buildApp(options = {}) {
request.employee = actor;
const permissionCodes = actor.permissionCodes ?? [];
if (!permissionCodes.includes("*") && !permissionCodes.includes(permissionCode)) {
throw new Error(`unexpected permission check: ${permissionCode}`);
throw new AppError({
code: "PERM_001",
message: "无权限",
statusCode: 403,
details: { permission: permissionCode },
});
}
});
app.decorate("config", {
@@ -177,6 +183,26 @@ async function buildApp(options = {}) {
},
});
app.setErrorHandler((error, request, reply) => {
if (error instanceof AppError) {
return reply.status(error.statusCode).send({
requestId: request.id,
error: {
code: error.code,
message: error.message,
details: error.details ?? {},
},
});
}
return reply.status(500).send({
requestId: request.id,
error: {
code: "SYS_001",
message: error.message,
},
});
});
await app.register(exportsRoutes, { prefix: "/api" });
return {
app,
@@ -306,4 +332,30 @@ describe("ap operations export route", () => {
await app.close();
});
it.each([
["employee", ["expense.view_own"]],
["admin-only", ["system.users"]],
])("rejects ap operations export for %s without finance permissions", async (_roleName, permissionCodes) => {
const { app, auditCreate } = await buildApp({
actor: {
id: ACTOR_ID,
permissionCodes,
},
});
const response = await app.inject({
method: "POST",
url: "/api/exports/ap-operations",
payload: {
format: "csv",
},
});
expect(response.statusCode).toBe(403);
expect(response.json().error.code).toBe("PERM_001");
expect(auditCreate).not.toHaveBeenCalled();
await app.close();
});
});