A7 收口权限导航与备用模块护栏

这个提交包含在:
2026-05-10 11:46:04 +08:00
父节点 d6ee25fa96
当前提交 a18d4770ee
共修改 5 个文件,包含 65 行新增和 7 行删除
@@ -36,13 +36,13 @@ export const NAV_ITEMS: NavItem[] = [
{ to: "/system/feature-flags", label: "功能开关", icon: "toggle_on", anyOf: ["system.settings"] }, { to: "/system/feature-flags", label: "功能开关", icon: "toggle_on", anyOf: ["system.settings"] },
{ to: "/audit-logs", label: "审计日志", icon: "manage_search", anyOf: ["system.audit.view"] }, { to: "/audit-logs", label: "审计日志", icon: "manage_search", anyOf: ["system.audit.view"] },
{ to: "/workflow/tasks", label: "审批待办", icon: "fact_check", anyOf: ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"] }, { to: "/workflow/tasks", label: "审批待办", icon: "fact_check", anyOf: ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"] },
{ to: "/contracts-registry", label: "合同台账", icon: "description", anyOf: ["project.view_own", "project.view_all", "project.manage", "system.settings"] }, { to: "/contracts-registry", label: "合同台账", icon: "description", anyOf: ["project.view_all", "project.manage", "system.settings"] },
{ to: "/purchase-requests", label: "采购申请", icon: "shopping_cart", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] }, { to: "/purchase-requests", label: "采购申请", icon: "shopping_cart", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] },
{ to: "/travel-requests", label: "差旅申请", icon: "flight", anyOf: ["travel.create", "travel.view_own", "travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] }, { to: "/travel-requests", label: "差旅申请", icon: "flight", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] },
{ to: "/budgets", label: "预算管理", icon: "account_balance", anyOf: ["budget.view", "budget.manage"] }, { to: "/budgets", label: "预算管理", icon: "account_balance", anyOf: ["budget.view", "budget.manage"] },
{ to: "/payment-batches", label: "支付批次", icon: "payments", anyOf: ["payment.batch.manage", "risk.review.manage"] }, { to: "/payment-batches", label: "支付批次", icon: "payments", anyOf: ["payment.batch.manage", "risk.review.manage"] },
{ to: "/ap-operations", label: "AP 运营", icon: "account_balance_wallet", anyOf: ["payment.batch.manage", "risk.review.manage"] }, { to: "/ap-operations", label: "AP 运营", icon: "account_balance_wallet", anyOf: ["payment.batch.manage", "risk.review.manage"] },
{ to: "/invoice-pool", label: "票据中心", icon: "receipt_long", anyOf: ["invoice.upload", "invoice.view_own", "invoice.manage"] }, { to: "/invoice-pool", label: "票据中心", icon: "receipt_long", anyOf: ["invoice.manage"] },
{ to: "/inventory-checks", label: "资产盘点", icon: "inventory", anyOf: ["asset.inventory"] }, { to: "/inventory-checks", label: "资产盘点", icon: "inventory", anyOf: ["asset.inventory"] },
{ to: "/system/cutover", label: "切换控制台", icon: "swap_calls", anyOf: ["system.settings"] }, { to: "/system/cutover", label: "切换控制台", icon: "swap_calls", anyOf: ["system.settings"] },
{ to: "/system/integrations/erp", label: "外部系统桥接", icon: "hub", anyOf: ["integration.manage"] }, { to: "/system/integrations/erp", label: "外部系统桥接", icon: "hub", anyOf: ["integration.manage"] },
@@ -5,5 +5,5 @@ A3,报销发票OCR主链路收口,"收口上传、OCR 状态、查重、绑定
A4,企业微信入口绑定通知回OA,"收口企业微信 config、登录/绑定、移动落点、通知 deeplink 和脱敏消息,企业微信只作为入口、身份、通知和打卡原始采集边界。","1) 企业微信手机入口默认进入 /mobile;2) 已绑定用户直达,未绑定可回退账号密码;3) 停用员工不得从企业微信进入业务系统;4) 通知内容脱敏且 deeplink 回 OA;5) 通知失败不阻断主业务。","流程级验证:模拟企业微信 UA 访问入口 -> 已绑定进入移动工作台 -> 未绑定回退账号密码 -> 通知 deeplink 打开 OA 审批页;命令:cd client && npm run test:e2e -- wecom-entry.spec.ts wecom-first-bind.spec.ts r124-mobile-entry-matrix.spec.ts;后端补 node --check src/routes/wecomAuth.js。","npm|playwright|node|manual",DONE,DONE,DONE,"server/src/routes/wecomAuth.js|server/src/services/notifications/*|client/src/lib/wecomEntry.ts|client/src/lib/runtimeContext.ts|client/src/lib/notificationLinks.ts","[""A1"",""A2""]","A4 已完成:企业微信入口测试默认 /mobile;通知 deeplink 限定为 OA 相对路径并补回归;未接企业微信原生审批、飞书或打卡同步;真实企业微信凭据/真机 live smoke 留到 A8,缺配置时 SKIP。" A4,企业微信入口绑定通知回OA,"收口企业微信 config、登录/绑定、移动落点、通知 deeplink 和脱敏消息,企业微信只作为入口、身份、通知和打卡原始采集边界。","1) 企业微信手机入口默认进入 /mobile;2) 已绑定用户直达,未绑定可回退账号密码;3) 停用员工不得从企业微信进入业务系统;4) 通知内容脱敏且 deeplink 回 OA;5) 通知失败不阻断主业务。","流程级验证:模拟企业微信 UA 访问入口 -> 已绑定进入移动工作台 -> 未绑定回退账号密码 -> 通知 deeplink 打开 OA 审批页;命令:cd client && npm run test:e2e -- wecom-entry.spec.ts wecom-first-bind.spec.ts r124-mobile-entry-matrix.spec.ts;后端补 node --check src/routes/wecomAuth.js。","npm|playwright|node|manual",DONE,DONE,DONE,"server/src/routes/wecomAuth.js|server/src/services/notifications/*|client/src/lib/wecomEntry.ts|client/src/lib/runtimeContext.ts|client/src/lib/notificationLinks.ts","[""A1"",""A2""]","A4 已完成:企业微信入口测试默认 /mobile;通知 deeplink 限定为 OA 相对路径并补回归;未接企业微信原生审批、飞书或打卡同步;真实企业微信凭据/真机 live smoke 留到 A8,缺配置时 SKIP。"
A5,请假补卡外出加班审批与考勤口径,"优先收口现有请假审批、团队状态、日历展示和考勤解释边界;首轮不做企业微信打卡 API 同步,不重造定位、人脸、Wi-Fi 或排班。","1) 请假审批状态正确;2) 已批准请假影响团队状态或日历展示;3) 补卡、外出、加班若现有实现不足,先记录缺口或做最小入口收口;4) 文案不引导员工去企业微信原生审批。","流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历体现结果 -> 驳回/撤回后状态正确;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts;命令:cd server && node --check src/routes/leave.js。","npm|playwright|node|manual",DONE,DONE,DONE,"server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx","[""A1"",""A2""]","A5 已完成:未接企业微信打卡同步,未新增定位、人脸、Wi-Fi 或排班;E2E 修正为适配 A2 后的业务摘要/已处理状态展示,并使用远期动态日期避免历史回归数据冲突;通过 node --check、client typecheck/build、leave-flow、leave-team-calendar 和视觉截图 output/playwright/a5-leave-attendance。" A5,请假补卡外出加班审批与考勤口径,"优先收口现有请假审批、团队状态、日历展示和考勤解释边界;首轮不做企业微信打卡 API 同步,不重造定位、人脸、Wi-Fi 或排班。","1) 请假审批状态正确;2) 已批准请假影响团队状态或日历展示;3) 补卡、外出、加班若现有实现不足,先记录缺口或做最小入口收口;4) 文案不引导员工去企业微信原生审批。","流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历体现结果 -> 驳回/撤回后状态正确;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts;命令:cd server && node --check src/routes/leave.js。","npm|playwright|node|manual",DONE,DONE,DONE,"server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx","[""A1"",""A2""]","A5 已完成:未接企业微信打卡同步,未新增定位、人脸、Wi-Fi 或排班;E2E 修正为适配 A2 后的业务摘要/已处理状态展示,并使用远期动态日期避免历史回归数据冲突;通过 node --check、client typecheck/build、leave-flow、leave-team-calendar 和视觉截图 output/playwright/a5-leave-attendance。"
A6,P1采购合同项目最小可用收口,"只做现有 P1 能力的最小可用检查和缺口修复,不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。","1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、记录编号、负责人和归档位置;3) 项目能关联关键事项并展示摘要;4) P2 能力不作为普通员工主路径。","流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts;合同台账执行人工或补 E2E。","bash|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx","[""A1"",""A2""]","A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出。验证通过:regression-procurement、client typecheck/build、procurement-flow、project-workbench、contractsRegistry 单测;未扩展预算/ERP/AI/复杂招采/重型项目管理。" A6,P1采购合同项目最小可用收口,"只做现有 P1 能力的最小可用检查和缺口修复,不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。","1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、记录编号、负责人和归档位置;3) 项目能关联关键事项并展示摘要;4) P2 能力不作为普通员工主路径。","流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts;合同台账执行人工或补 E2E。","bash|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx","[""A1"",""A2""]","A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出。验证通过:regression-procurement、client typecheck/build、procurement-flow、project-workbench、contractsRegistry 单测;未扩展预算/ERP/AI/复杂招采/重型项目管理。"
A7,权限导航与备用模块护栏,"按当前 MVP 路线整理普通员工、财务、主管、管理员的导航和权限可见性,避免备用模块干扰主线。","1) 普通员工不见预算、AP、ERP、AI、完整差旅等首批不开放主路径;2) 审批、报销、请假、企业微信移动入口清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。","流程级验证:分别用员工、主管、财务、管理员账号登录 -> 检查导航、入口和关键页面可访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts。","npm|playwright|manual",TODO,TODO,TODO,"client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|client/src/auth/permissions.ts|server/src/routes/systemWorkspace.js|server/src/scripts/seed-config.js","[""A1"",""A2""]","涉及权限和导航;不得只靠前端隐藏授权,服务端权限缺口需另拆或同步修复,回滚以 revert commit 为主。" A7,权限导航与备用模块护栏,"按当前 MVP 路线整理普通员工、财务、主管、管理员的导航和权限可见性,避免备用模块干扰主线。","1) 普通员工不见预算、AP、ERP、AI、完整差旅等首批不开放主路径;2) 审批、报销、请假、企业微信移动入口清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。","流程级验证:分别用员工、主管、财务、管理员账号登录 -> 检查导航、入口和关键页面可访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts。","npm|playwright|manual",DONE,DONE,DONE,"client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|client/src/auth/permissions.ts|server/src/routes/systemWorkspace.js|server/src/scripts/seed-config.js","[""A1"",""A2""]","A7 已完成:收紧服务端导航和前端兜底导航,普通员工不再通过导航看到完整差旅、票据中心、合同台账、预算/AP/ERP/仪表盘等备用主路径;采购仍保留为 P1 可用入口。验证通过:node --check workspaceSettings、workspaceSettings.navigation 单测、client typecheck/build、settings-governance、login-portal-ux、r110-mobile-pc-routing。"
A8,回归门禁与试运行证据收口,"汇总 A2-A7 的自动化、手工和 SKIP 条件,形成首轮开发完成门禁和是否进入部署试运行的判断依据。","1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 可判断是否进入部署和公网试运行;5) Issue CSV 状态按验证结果回填。","流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航的组合回归;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。","bash|npm|node|manual",TODO,TODO,TODO,"server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[""A2"",""A3"",""A4"",""A5"",""A6"",""A7""]","A8 是首轮开发完成门禁;未部署不得告知可公网或企业微信真机测试。" A8,回归门禁与试运行证据收口,"汇总 A2-A7 的自动化、手工和 SKIP 条件,形成首轮开发完成门禁和是否进入部署试运行的判断依据。","1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 可判断是否进入部署和公网试运行;5) Issue CSV 状态按验证结果回填。","流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航的组合回归;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。","bash|npm|node|manual",TODO,TODO,TODO,"server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[""A2"",""A3"",""A4"",""A5"",""A6"",""A7""]","A8 是首轮开发完成门禁;未部署不得告知可公网或企业微信真机测试。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
5 A4 企业微信入口绑定通知回OA 收口企业微信 config、登录/绑定、移动落点、通知 deeplink 和脱敏消息,企业微信只作为入口、身份、通知和打卡原始采集边界。 1) 企业微信手机入口默认进入 /mobile;2) 已绑定用户直达,未绑定可回退账号密码;3) 停用员工不得从企业微信进入业务系统;4) 通知内容脱敏且 deeplink 回 OA;5) 通知失败不阻断主业务。 流程级验证:模拟企业微信 UA 访问入口 -> 已绑定进入移动工作台 -> 未绑定回退账号密码 -> 通知 deeplink 打开 OA 审批页;命令:cd client && npm run test:e2e -- wecom-entry.spec.ts wecom-first-bind.spec.ts r124-mobile-entry-matrix.spec.ts;后端补 node --check src/routes/wecomAuth.js。 npm|playwright|node|manual DONE DONE DONE server/src/routes/wecomAuth.js|server/src/services/notifications/*|client/src/lib/wecomEntry.ts|client/src/lib/runtimeContext.ts|client/src/lib/notificationLinks.ts ["A1","A2"] A4 已完成:企业微信入口测试默认 /mobile;通知 deeplink 限定为 OA 相对路径并补回归;未接企业微信原生审批、飞书或打卡同步;真实企业微信凭据/真机 live smoke 留到 A8,缺配置时 SKIP。
6 A5 请假补卡外出加班审批与考勤口径 优先收口现有请假审批、团队状态、日历展示和考勤解释边界;首轮不做企业微信打卡 API 同步,不重造定位、人脸、Wi-Fi 或排班。 1) 请假审批状态正确;2) 已批准请假影响团队状态或日历展示;3) 补卡、外出、加班若现有实现不足,先记录缺口或做最小入口收口;4) 文案不引导员工去企业微信原生审批。 流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历体现结果 -> 驳回/撤回后状态正确;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts;命令:cd server && node --check src/routes/leave.js。 npm|playwright|node|manual DONE DONE DONE server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx ["A1","A2"] A5 已完成:未接企业微信打卡同步,未新增定位、人脸、Wi-Fi 或排班;E2E 修正为适配 A2 后的业务摘要/已处理状态展示,并使用远期动态日期避免历史回归数据冲突;通过 node --check、client typecheck/build、leave-flow、leave-team-calendar 和视觉截图 output/playwright/a5-leave-attendance。
7 A6 P1采购合同项目最小可用收口 只做现有 P1 能力的最小可用检查和缺口修复,不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。 1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、记录编号、负责人和归档位置;3) 项目能关联关键事项并展示摘要;4) P2 能力不作为普通员工主路径。 流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts;合同台账执行人工或补 E2E。 bash|npm|playwright|manual DONE DONE DONE server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx ["A1","A2"] A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出。验证通过:regression-procurement、client typecheck/build、procurement-flow、project-workbench、contractsRegistry 单测;未扩展预算/ERP/AI/复杂招采/重型项目管理。
8 A7 权限导航与备用模块护栏 按当前 MVP 路线整理普通员工、财务、主管、管理员的导航和权限可见性,避免备用模块干扰主线。 1) 普通员工不见预算、AP、ERP、AI、完整差旅等首批不开放主路径;2) 审批、报销、请假、企业微信移动入口清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。 流程级验证:分别用员工、主管、财务、管理员账号登录 -> 检查导航、入口和关键页面可访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts。 npm|playwright|manual TODO DONE TODO DONE TODO DONE client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|client/src/auth/permissions.ts|server/src/routes/systemWorkspace.js|server/src/scripts/seed-config.js ["A1","A2"] 涉及权限和导航;不得只靠前端隐藏授权,服务端权限缺口需另拆或同步修复,回滚以 revert commit 为主。 A7 已完成:收紧服务端导航和前端兜底导航,普通员工不再通过导航看到完整差旅、票据中心、合同台账、预算/AP/ERP/仪表盘等备用主路径;采购仍保留为 P1 可用入口。验证通过:node --check workspaceSettings、workspaceSettings.navigation 单测、client typecheck/build、settings-governance、login-portal-ux、r110-mobile-pc-routing。
9 A8 回归门禁与试运行证据收口 汇总 A2-A7 的自动化、手工和 SKIP 条件,形成首轮开发完成门禁和是否进入部署试运行的判断依据。 1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 可判断是否进入部署和公网试运行;5) Issue CSV 状态按验证结果回填。 流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航的组合回归;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。 bash|npm|node|manual TODO TODO TODO server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv ["A2","A3","A4","A5","A6","A7"] A8 是首轮开发完成门禁;未部署不得告知可公网或企业微信真机测试。
@@ -2,5 +2,5 @@ ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regr
B1,剩余主线收口批次基线,"为 A5-A8 建立本轮连续收口执行合同;主 CSV 仍作为 A5-A8 最终状态来源同步回填;本 Issue 不改业务代码。","1) 新 CSV 校验通过;2) 主 CSV 校验通过;3) 明确 A5-A8 执行顺序、提交边界、测试矩阵和 SKIP 规则;4) 不修改后端、前端或数据库。","流程级验证:确认当前主 CSV 中 A1-A4/A3 已 DONE 且 A5-A8 待执行;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv;命令:git diff --check。","python3|git|manual",DONE,DONE,DONE,"issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[]","本批次采用一次性规划、连续开发、分段提交、统一总回归;不部署;公网、企业微信真机、真实 OCR 缺配置时在 A8 记录 SKIP。" B1,剩余主线收口批次基线,"为 A5-A8 建立本轮连续收口执行合同;主 CSV 仍作为 A5-A8 最终状态来源同步回填;本 Issue 不改业务代码。","1) 新 CSV 校验通过;2) 主 CSV 校验通过;3) 明确 A5-A8 执行顺序、提交边界、测试矩阵和 SKIP 规则;4) 不修改后端、前端或数据库。","流程级验证:确认当前主 CSV 中 A1-A4/A3 已 DONE 且 A5-A8 待执行;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv;命令:git diff --check。","python3|git|manual",DONE,DONE,DONE,"issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[]","本批次采用一次性规划、连续开发、分段提交、统一总回归;不部署;公网、企业微信真机、真实 OCR 缺配置时在 A8 记录 SKIP。"
B2,A5请假考勤审批口径收口,"收口现有请假审批、移动请假、团队状态/日历展示和考勤解释文案;补卡、外出、加班若无运行时能力则记录缺口,不扩大为完整考勤系统。","1) 请假提交、审批、驳回或撤回状态正确;2) 已批准请假影响团队状态或日历展示;3) PC 和移动入口不引导企业微信原生审批;4) 不接企业微信打卡同步,不新增定位、人脸、Wi-Fi 或排班能力。","流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历展示结果 -> 驳回/撤回后状态正确;命令:cd server && node --check src/routes/leave.js;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts --workers=1;视觉检查 PC 请假、移动请假、团队状态或日历。","node|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx","[""B1""]","A5 已完成:未改业务状态机和数据库;E2E 修正为不依赖任务 UUID 主展示,日期改为远期动态日期以避免回归库历史数据冲突;同源 SERVE_CLIENT_DIST=true 本地服务下通过 leave-flow 与 leave-team-calendar;视觉截图 output/playwright/a5-leave-attendance;未接企业微信打卡 API。" B2,A5请假考勤审批口径收口,"收口现有请假审批、移动请假、团队状态/日历展示和考勤解释文案;补卡、外出、加班若无运行时能力则记录缺口,不扩大为完整考勤系统。","1) 请假提交、审批、驳回或撤回状态正确;2) 已批准请假影响团队状态或日历展示;3) PC 和移动入口不引导企业微信原生审批;4) 不接企业微信打卡同步,不新增定位、人脸、Wi-Fi 或排班能力。","流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历展示结果 -> 驳回/撤回后状态正确;命令:cd server && node --check src/routes/leave.js;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts --workers=1;视觉检查 PC 请假、移动请假、团队状态或日历。","node|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx","[""B1""]","A5 已完成:未改业务状态机和数据库;E2E 修正为不依赖任务 UUID 主展示,日期改为远期动态日期以避免回归库历史数据冲突;同源 SERVE_CLIENT_DIST=true 本地服务下通过 leave-flow 与 leave-team-calendar;视觉截图 output/playwright/a5-leave-attendance;未接企业微信打卡 API。"
B3,A6采购合同项目最小可用收口,"收口现有采购申请、合同台账和项目轻管理到最小可用;不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。","1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、展示编号、负责人和归档位置;3) 项目页能汇总成员、里程碑、任务、风险和业务关联摘要;4) P2 能力不作为普通员工主路径。","流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts --workers=1;合同台账如无 E2E 则执行人工步骤并写入 Notes。","bash|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx","[""B1"",""B2""]","A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出;合同涉密正文、敏感报价、客户敏感资料不进入公网 OA 主路径;未扩展预算/ERP/AI。" B3,A6采购合同项目最小可用收口,"收口现有采购申请、合同台账和项目轻管理到最小可用;不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。","1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、展示编号、负责人和归档位置;3) 项目页能汇总成员、里程碑、任务、风险和业务关联摘要;4) P2 能力不作为普通员工主路径。","流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts --workers=1;合同台账如无 E2E 则执行人工步骤并写入 Notes。","bash|npm|playwright|manual",DONE,DONE,DONE,"server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx","[""B1"",""B2""]","A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出;合同涉密正文、敏感报价、客户敏感资料不进入公网 OA 主路径;未扩展预算/ERP/AI。"
B4,A7权限导航与备用模块护栏,"按当前 MVP 路线收口普通员工、主管、财务、管理员的 PC/移动导航和权限可见性,避免备用模块干扰主线。","1) 普通员工不见预算、AP、ERP、AI、完整差旅等备用主路径;2) 审批、报销、请假、采购和项目入口按角色清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。","流程级验证:分别用员工、主管、财务、管理员登录 -> 检查 PC/移动导航、入口和关键页面访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts r110-mobile-pc-routing.spec.ts --workers=1;人工角色矩阵检查。","npm|playwright|manual",TODO,TODO,TODO,"client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|client/src/auth/permissions.ts|server/src/routes/systemWorkspace.js|server/src/scripts/seed-config.js","[""B1"",""B2"",""B3""]","同步回填主 CSV A7;前端隐藏不能替代服务端授权;若发现服务端权限缺口需修复或另拆高风险 Issue。" B4,A7权限导航与备用模块护栏,"按当前 MVP 路线收口普通员工、主管、财务、管理员的 PC/移动导航和权限可见性,避免备用模块干扰主线。","1) 普通员工不见预算、AP、ERP、AI、完整差旅等备用主路径;2) 审批、报销、请假、采购和项目入口按角色清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。","流程级验证:分别用员工、主管、财务、管理员登录 -> 检查 PC/移动导航、入口和关键页面访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts r110-mobile-pc-routing.spec.ts --workers=1;人工角色矩阵检查。","npm|playwright|manual",DONE,DONE,DONE,"client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|server/src/services/workspaceSettings.js|server/tests/unit/workspaceSettings.navigation.test.js|client/src/components/navigation/NavLinks.tsx","[""B1"",""B2"",""B3""]","A7 已完成:收紧服务端导航和前端兜底导航,普通员工不再通过导航看到完整差旅、票据中心、合同台账、预算/AP/ERP/仪表盘等备用主路径;采购仍保留为 P1 可用入口。验证通过:node --check workspaceSettings、workspaceSettings.navigation 单测、client typecheck/build、settings-governance、login-portal-ux、r110-mobile-pc-routing。"
B5,A8总回归门禁与试运行证据,"汇总 A2-A7 的自动化、手工和 SKIP 条件,完成首轮开发本地总回归门禁和是否进入部署试运行的判断依据。","1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 主 CSV 和本批次 CSV 状态按真实验证结果回填;5) 未部署不引导用户公网或企业微信真机测试。","流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航组合回归;命令:cd server && npm run prisma:validate;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。","bash|npm|node|manual",TODO,TODO,TODO,"server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[""B1"",""B2"",""B3"",""B4""]","同步回填主 CSV A8;A8 通过前不部署;公网、企业微信真机、真实 OCR 验证缺条件时只记录 SKIP。" B5,A8总回归门禁与试运行证据,"汇总 A2-A7 的自动化、手工和 SKIP 条件,完成首轮开发本地总回归门禁和是否进入部署试运行的判断依据。","1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 主 CSV 和本批次 CSV 状态按真实验证结果回填;5) 未部署不引导用户公网或企业微信真机测试。","流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航组合回归;命令:cd server && npm run prisma:validate;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。","bash|npm|node|manual",TODO,TODO,TODO,"server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv","[""B1"",""B2"",""B3"",""B4""]","同步回填主 CSV A8;A8 通过前不部署;公网、企业微信真机、真实 OCR 验证缺条件时只记录 SKIP。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 B1 剩余主线收口批次基线 为 A5-A8 建立本轮连续收口执行合同;主 CSV 仍作为 A5-A8 最终状态来源同步回填;本 Issue 不改业务代码。 1) 新 CSV 校验通过;2) 主 CSV 校验通过;3) 明确 A5-A8 执行顺序、提交边界、测试矩阵和 SKIP 规则;4) 不修改后端、前端或数据库。 流程级验证:确认当前主 CSV 中 A1-A4/A3 已 DONE 且 A5-A8 待执行;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv;命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv;命令:git diff --check。 python3|git|manual DONE DONE DONE issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv [] 本批次采用一次性规划、连续开发、分段提交、统一总回归;不部署;公网、企业微信真机、真实 OCR 缺配置时在 A8 记录 SKIP。
3 B2 A5请假考勤审批口径收口 收口现有请假审批、移动请假、团队状态/日历展示和考勤解释文案;补卡、外出、加班若无运行时能力则记录缺口,不扩大为完整考勤系统。 1) 请假提交、审批、驳回或撤回状态正确;2) 已批准请假影响团队状态或日历展示;3) PC 和移动入口不引导企业微信原生审批;4) 不接企业微信打卡同步,不新增定位、人脸、Wi-Fi 或排班能力。 流程级验证:员工提交请假 -> 主管/HR 在 OA 审批 -> 团队状态或日历展示结果 -> 驳回/撤回后状态正确;命令:cd server && node --check src/routes/leave.js;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- leave-flow.spec.ts leave-team-calendar.spec.ts --workers=1;视觉检查 PC 请假、移动请假、团队状态或日历。 node|npm|playwright|manual DONE DONE DONE server/src/routes/leave.js|server/src/services/leave.js|client/src/pages/LeaveRequestPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/TeamPresencePage.tsx ["B1"] A5 已完成:未改业务状态机和数据库;E2E 修正为不依赖任务 UUID 主展示,日期改为远期动态日期以避免回归库历史数据冲突;同源 SERVE_CLIENT_DIST=true 本地服务下通过 leave-flow 与 leave-team-calendar;视觉截图 output/playwright/a5-leave-attendance;未接企业微信打卡 API。
4 B3 A6采购合同项目最小可用收口 收口现有采购申请、合同台账和项目轻管理到最小可用;不扩展预算、支付、ERP、AI、复杂招采或重型项目管理。 1) 采购/费用可申请并进入 OA 审批;2) 合同台账可登记、查询、展示编号、负责人和归档位置;3) 项目页能汇总成员、里程碑、任务、风险和业务关联摘要;4) P2 能力不作为普通员工主路径。 流程级验证:创建采购申请 -> OA 审批 -> 项目页查看关联;登记合同台账 -> 查询合同摘要;项目页查看任务/里程碑/风险/业务关联;命令:cd server && ./scripts/regression-procurement.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- procurement-flow.spec.ts project-workbench.spec.ts --workers=1;合同台账如无 E2E 则执行人工步骤并写入 Notes。 bash|npm|playwright|manual DONE DONE DONE server/src/routes/purchaseRequests.js|server/src/routes/contractsRegistry.js|server/src/routes/projects.js|client/src/pages/PurchaseRequestsPage.tsx|client/src/pages/ContractsRegistryPage.tsx|client/src/pages/ProjectsPage.tsx|client/src/pages/ProjectDetailPage.tsx ["B1","B2"] A6 已完成:采购 E2E 修正为按审批任务 deeplink/href 定位,不依赖 UUID 主展示;项目 E2E 在 PROJECT_* 本地开关启用后通过;合同台账以 routes/contractsRegistry 与 exports/contractsRegistry 单测覆盖登记、查询、续约和导出;合同涉密正文、敏感报价、客户敏感资料不进入公网 OA 主路径;未扩展预算/ERP/AI。
5 B4 A7权限导航与备用模块护栏 按当前 MVP 路线收口普通员工、主管、财务、管理员的 PC/移动导航和权限可见性,避免备用模块干扰主线。 1) 普通员工不见预算、AP、ERP、AI、完整差旅等备用主路径;2) 审批、报销、请假、采购和项目入口按角色清晰可达;3) 财务、主管、管理员入口与权限边界符合 PRD;4) 服务端权限仍是最终授权依据。 流程级验证:分别用员工、主管、财务、管理员登录 -> 检查 PC/移动导航、入口和关键页面访问性 -> 普通员工不可访问备用模块主路径;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:cd client && npm run test:e2e -- settings-governance.spec.ts login-portal-ux.spec.ts r110-mobile-pc-routing.spec.ts --workers=1;人工角色矩阵检查。 npm|playwright|manual TODO DONE TODO DONE TODO DONE client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|client/src/auth/permissions.ts|server/src/routes/systemWorkspace.js|server/src/scripts/seed-config.js client/src/layouts/AppShellLayout.tsx|client/src/features/mobile/navigation.ts|server/src/services/workspaceSettings.js|server/tests/unit/workspaceSettings.navigation.test.js|client/src/components/navigation/NavLinks.tsx ["B1","B2","B3"] 同步回填主 CSV A7;前端隐藏不能替代服务端授权;若发现服务端权限缺口需修复或另拆高风险 Issue。 A7 已完成:收紧服务端导航和前端兜底导航,普通员工不再通过导航看到完整差旅、票据中心、合同台账、预算/AP/ERP/仪表盘等备用主路径;采购仍保留为 P1 可用入口。验证通过:node --check workspaceSettings、workspaceSettings.navigation 单测、client typecheck/build、settings-governance、login-portal-ux、r110-mobile-pc-routing。
6 B5 A8总回归门禁与试运行证据 汇总 A2-A7 的自动化、手工和 SKIP 条件,完成首轮开发本地总回归门禁和是否进入部署试运行的判断依据。 1) server/client 本地检查通过;2) 关键业务流回归结果可追溯;3) 公网、企业微信真机、真实 OCR 缺配置时明确 SKIP;4) 主 CSV 和本批次 CSV 状态按真实验证结果回填;5) 未部署不引导用户公网或企业微信真机测试。 流程级验证:执行统一审批、报销/OCR、企业微信入口、请假、采购/合同/项目和权限导航组合回归;命令:cd server && npm run prisma:validate;命令:cd server && ./scripts/regression-all.sh;命令:cd client && npm run typecheck;命令:cd client && npm run build;命令:bash scripts/qa/run-web-gates.sh smoke-local;命令:node scripts/qa/check-live-smoke-env.mjs。 bash|npm|node|manual TODO TODO TODO server/scripts/*|scripts/qa/*|client/tests/e2e/*|docs/trialrun-runbook.md|issues/2026-05-10_10-48-03-mvp-remaining-mainline-closeout.csv|issues/2026-05-09_20-57-19-mvp-mainline-dev-kickoff.csv ["B1","B2","B3","B4"] 同步回填主 CSV A8;A8 通过前不部署;公网、企业微信真机、真实 OCR 验证缺条件时只记录 SKIP。
+2 -2
查看文件
@@ -35,11 +35,11 @@ const NAVIGATION_REGISTRY = Object.freeze({
workflowTasks: { key: "workflowTasks", label: "审批待办", icon: "fact_check", href: "/workflow/tasks", anyOf: ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"] }, workflowTasks: { key: "workflowTasks", label: "审批待办", icon: "fact_check", href: "/workflow/tasks", anyOf: ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"] },
dashboard: { key: "dashboard", label: "仪表盘", icon: "dashboard", href: "/dashboard", anyOf: ["report.dashboard.view"] }, dashboard: { key: "dashboard", label: "仪表盘", icon: "dashboard", href: "/dashboard", anyOf: ["report.dashboard.view"] },
purchaseRequests: { key: "purchaseRequests", label: "采购申请", icon: "shopping_cart", href: "/purchase-requests", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] }, purchaseRequests: { key: "purchaseRequests", label: "采购申请", icon: "shopping_cart", href: "/purchase-requests", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] },
travelRequests: { key: "travelRequests", label: "差旅申请", icon: "flight", href: "/travel-requests", anyOf: ["travel.create", "travel.view_own", "travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] }, travelRequests: { key: "travelRequests", label: "差旅申请", icon: "flight", href: "/travel-requests", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] },
budgets: { key: "budgets", label: "预算管理", icon: "account_balance", href: "/budgets", anyOf: ["budget.view", "budget.manage"] }, budgets: { key: "budgets", label: "预算管理", icon: "account_balance", href: "/budgets", anyOf: ["budget.view", "budget.manage"] },
paymentBatches: { key: "paymentBatches", label: "支付批次", icon: "payments", href: "/payment-batches", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch }, paymentBatches: { key: "paymentBatches", label: "支付批次", icon: "payments", href: "/payment-batches", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
apOperations: { key: "apOperations", label: "AP 运营", icon: "account_balance_wallet", href: "/ap-operations", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch }, apOperations: { key: "apOperations", label: "AP 运营", icon: "account_balance_wallet", href: "/ap-operations", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
invoicePool: { key: "invoicePool", label: "票据中心", icon: "receipt_long", href: "/invoice-pool", anyOf: ["invoice.upload", "invoice.view_own", "invoice.manage"] }, invoicePool: { key: "invoicePool", label: "票据中心", icon: "receipt_long", href: "/invoice-pool", anyOf: ["invoice.manage"] },
inventoryChecks: { key: "inventoryChecks", label: "资产盘点", icon: "inventory", href: "/inventory-checks", anyOf: ["asset.inventory"] }, inventoryChecks: { key: "inventoryChecks", label: "资产盘点", icon: "inventory", href: "/inventory-checks", anyOf: ["asset.inventory"] },
cutover: { key: "cutover", label: "切换控制台", icon: "swap_calls", href: "/system/cutover", anyOf: ["system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.cutoverConsole }, cutover: { key: "cutover", label: "切换控制台", icon: "swap_calls", href: "/system/cutover", anyOf: ["system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.cutoverConsole },
erpIntegrations: { key: "erpIntegrations", label: "ERP 集成", icon: "hub", href: "/system/integrations/erp", anyOf: ["integration.manage"] }, erpIntegrations: { key: "erpIntegrations", label: "ERP 集成", icon: "hub", href: "/system/integrations/erp", anyOf: ["integration.manage"] },
@@ -1,8 +1,40 @@
const { const {
DEFAULT_MODULE_VISIBILITY, DEFAULT_MODULE_VISIBILITY,
buildDefaultWorkspaceSetting, buildDefaultWorkspaceSetting,
getSystemNavigation,
normalizeModuleVisibilityJson, normalizeModuleVisibilityJson,
} = require("../../src/services/workspaceSettings"); } = require("../../src/services/workspaceSettings");
const { hasPermission, resolvePermissionsForRoles } = require("../../src/constants/permissions");
function buildPrisma() {
return {
workspaceSetting: {
findFirst: vi.fn().mockResolvedValue(null),
},
featureFlag: {
findUnique: vi.fn().mockResolvedValue(null),
},
};
}
function buildConfig() {
return {
announcementsEnabled: false,
attendanceEnabled: false,
budgetPrecheckEnabled: false,
cutoverConsoleEnabled: false,
employeeLifecycleEnabled: false,
globalSearchEnabled: false,
paymentBatchEnabled: false,
portalHomeEnabled: false,
presenceBoardEnabled: false,
projectDomainEnabled: false,
projectLinkingEnabled: false,
todoBoardEnabled: false,
weeklyReportEnabled: false,
workflowDefinitionAdminEnabled: false,
};
}
describe("workspace small-team navigation defaults", () => { describe("workspace small-team navigation defaults", () => {
it("keeps only the R66 daily path in default main navigation", () => { it("keeps only the R66 daily path in default main navigation", () => {
@@ -44,4 +76,30 @@ describe("workspace small-team navigation defaults", () => {
expect(workspace.profile).toBe("small_team_internal"); expect(workspace.profile).toBe("small_team_internal");
expect(workspace.moduleVisibility.attendance).toBe("hidden"); expect(workspace.moduleVisibility.attendance).toBe("hidden");
}); });
it("keeps P2 and finance-only backup modules out of employee navigation", async () => {
const employee = {
id: "employee-1",
department_id: "dept-1",
permissionCodes: resolvePermissionsForRoles(["employee"]),
};
const navigation = await getSystemNavigation({
prisma: buildPrisma(),
config: buildConfig(),
employee,
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
});
const backupKeys = navigation.sections.backup.map((item) => item.key);
expect(backupKeys).toContain("purchaseRequests");
expect(backupKeys).not.toContain("travelRequests");
expect(backupKeys).not.toContain("invoicePool");
expect(backupKeys).not.toContain("contractsRegistry");
expect(backupKeys).not.toContain("budgets");
expect(backupKeys).not.toContain("paymentBatches");
expect(backupKeys).not.toContain("apOperations");
expect(backupKeys).not.toContain("erpIntegrations");
expect(backupKeys).not.toContain("dashboard");
});
}); });