fix: 用专用权限收窄经理 AP 访问(09-07-manager-ap-access-scope)
这个提交包含在:
@@ -68,6 +68,7 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
|
||||
"payment.batch.review",
|
||||
],
|
||||
ceo: [
|
||||
"finance.ledger.view",
|
||||
...EMPLOYEE_BASE_PERMISSIONS,
|
||||
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
|
||||
...MASTERDATA_VIEW_PERMISSIONS,
|
||||
@@ -86,6 +87,7 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
|
||||
"payment.batch.review",
|
||||
],
|
||||
finance: [
|
||||
"finance.ledger.view",
|
||||
...EMPLOYEE_BASE_PERMISSIONS,
|
||||
...MASTERDATA_VIEW_PERMISSIONS,
|
||||
"invoice.manage",
|
||||
@@ -106,6 +108,7 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
|
||||
"payroll.manage",
|
||||
],
|
||||
finance_mgr: [
|
||||
"finance.ledger.view",
|
||||
...EMPLOYEE_BASE_PERMISSIONS,
|
||||
...MASTERDATA_MANAGE_PERMISSIONS,
|
||||
"invoice.manage",
|
||||
|
||||
@@ -38,7 +38,7 @@ export const NAV_ITEMS: NavItem[] = [
|
||||
{ to: "/travel-requests", label: "差旅申请", icon: "flight", anyOf: ["travel.view_all", "travel.manage", "travel.payment.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", "payment.batch.review"] },
|
||||
{ to: "/ap-operations", label: "应付账款", icon: "account_balance_wallet", anyOf: ["payment.batch.manage", "risk.review.manage", "payment.batch.review"] },
|
||||
{ to: "/ap-operations", label: "应付账款", icon: "account_balance_wallet", anyOf: ["finance.ledger.view"] },
|
||||
{ to: "/invoice-pool", label: "票据", icon: "receipt_long", anyOf: ["invoice.manage"] },
|
||||
{ to: "/inventory-checks", label: "资产盘点", icon: "inventory", anyOf: ["asset.inventory"] },
|
||||
{ to: "/dashboard", label: "数据看板", icon: "dashboard", anyOf: ["report.dashboard.view"] },
|
||||
|
||||
@@ -315,7 +315,7 @@ const routeElements = (
|
||||
<Route
|
||||
path="ap-operations"
|
||||
element={
|
||||
<RequirePermission anyOf={["payment.batch.manage", "risk.review.manage", "payment.batch.review"]}>
|
||||
<RequirePermission anyOf={["finance.ledger.view"]}>
|
||||
{withRouteFallback(<ApOperationsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
|
||||
@@ -346,19 +346,40 @@ async function login(page: Page, actor: SyntheticActor) {
|
||||
await expect(page).toHaveURL(/\/(?:portal|mobile)$/);
|
||||
}
|
||||
|
||||
test("只有付款复核职责的经理可看应付账款但看不到导出", async ({ page }) => {
|
||||
test("只有付款复核职责的经理不能直连应付账款且不请求台账", async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const pageErrors = capturePageErrors(page);
|
||||
await mockSyntheticApis(page, REVIEWER_ACTOR);
|
||||
await login(page, REVIEWER_ACTOR);
|
||||
|
||||
let apRequests = 0;
|
||||
page.on("request", (request) => {
|
||||
if (new URL(request.url()).pathname.startsWith("/api/ap-operations")) apRequests += 1;
|
||||
});
|
||||
await page.goto("/ap-operations");
|
||||
await expect(page).toHaveURL(/\/ap-operations$/);
|
||||
await expect(page.getByRole("heading", { level: 1, name: "应付账款", exact: true })).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/403$/);
|
||||
await expect(page.getByRole("link", { name: "应付账款", exact: true })).toHaveCount(0);
|
||||
expect(apRequests).toBe(0);
|
||||
await expect(page.getByTestId("ap-export")).toHaveCount(0);
|
||||
expectNoPageErrors(pageErrors);
|
||||
});
|
||||
|
||||
for (const roleCodes of [["ceo"], ["finance"], ["finance_mgr"], ["manager", "finance"]]) {
|
||||
test(`AP 专用读取权限允许 ${roleCodes.join("+")} 进入台账且不提升导出权限`, async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const actor = { ...FINANCE_ACTOR, roleCodes };
|
||||
await mockSyntheticApis(page, actor);
|
||||
await login(page, actor);
|
||||
await page.goto("/ap-operations");
|
||||
await expect(page).toHaveURL(/\/ap-operations$/);
|
||||
await expect(page.getByRole("heading", { level: 1, name: "应付账款", exact: true })).toBeVisible();
|
||||
await page.getByRole("tab", { name: /^财务台账/ }).click();
|
||||
await expect(page.getByTestId("finance-ledger-table")).toBeVisible();
|
||||
if (roleCodes.includes("ceo")) await expect(page.getByTestId("ap-export")).toHaveCount(0);
|
||||
else await expect(page.getByTestId("ap-export")).toBeVisible();
|
||||
});
|
||||
}
|
||||
|
||||
test("只有付款复核职责的经理看不到新建批次", async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const pageErrors = capturePageErrors(page);
|
||||
|
||||
在新工单中引用
屏蔽一个用户