mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 21:45:03 +00:00
Merge pull request #923 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { AuthUser } from "@/auth/types";
|
||||
import { canApproveContractStep } from "./permissions";
|
||||
|
||||
const withPositionType = (typeKey: string): AuthUser => ({
|
||||
employee: [{ positions: [{ positionType: { key: typeKey } }] }],
|
||||
});
|
||||
|
||||
const withRole = (roleKey: string): AuthUser => ({ roles: [{ key: roleKey }] });
|
||||
|
||||
const withPermission = (permKey: string): AuthUser => ({
|
||||
permissionKeys: [permKey],
|
||||
});
|
||||
|
||||
describe("canApproveContractStep", () => {
|
||||
it("shows to the matching position type only", () => {
|
||||
const chief = withPositionType("-marketing-chief");
|
||||
expect(canApproveContractStep(chief, "-marketing-chief")).toBe(true);
|
||||
// a marketing officer must NOT see the chief step's buttons
|
||||
expect(canApproveContractStep(chief, "-marketing-director-")).toBe(false);
|
||||
});
|
||||
|
||||
it("lets super/org admins action any step", () => {
|
||||
expect(canApproveContractStep(withRole("super_admin"), "anything")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
canApproveContractStep(withRole("organization_admin"), "-marketing-chief"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves legacy chain roles via their position-type aliases", () => {
|
||||
const director = withPositionType("operation-director");
|
||||
expect(canApproveContractStep(director, "DIRECTOR")).toBe(true);
|
||||
expect(canApproveContractStep(director, "CEO")).toBe(false);
|
||||
});
|
||||
|
||||
it("honours the role's own legacy approve permission", () => {
|
||||
const staff = withPermission(
|
||||
"edr_freight_app:contracts:approve_director",
|
||||
);
|
||||
expect(canApproveContractStep(staff, "DIRECTOR")).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT show to holders of an unrelated approve permission", () => {
|
||||
// the dropped blanket fallback: a line-staff approver is not a chief
|
||||
const lineStaff = withPermission(
|
||||
"edr_freight_app:contracts:approve_line_staff",
|
||||
);
|
||||
expect(canApproveContractStep(lineStaff, "-marketing-chief")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false without a user or role", () => {
|
||||
expect(canApproveContractStep(null, "-marketing-chief")).toBe(false);
|
||||
expect(canApproveContractStep(withPositionType("x"), null)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -240,12 +240,6 @@ export const FREIGHT_PERMS = {
|
||||
cancel: "edr_freight_app:warehouse_fee_invoices:cancel",
|
||||
pay: "edr_freight_app:warehouse_fee_invoices:pay",
|
||||
},
|
||||
config: {
|
||||
contractValidity: {
|
||||
view: "edr_freight_app:config:contract_validity:view",
|
||||
manage: "edr_freight_app:config:contract_validity:manage",
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
fileUpload: {
|
||||
view: "edr_freight_app:settings:file_upload:view",
|
||||
@@ -420,6 +414,53 @@ export function hasPermission(
|
||||
return getPermissionKeys(user).includes(key);
|
||||
}
|
||||
|
||||
// Legacy chain roles predate position types; map each to the position types
|
||||
// that stand in for it. Mirror of the API's LEGACY_ROLE_POSITION_TYPES so the
|
||||
// button visibility matches what the approve/reject endpoint will accept.
|
||||
const LEGACY_ROLE_POSITION_TYPES: Record<string, string[]> = {
|
||||
LINE_STAFF: ["employee", "teamLeader", "officeHead", "recordOfficer"],
|
||||
DIRECTOR: ["director", "operation-director"],
|
||||
CEO: ["chief", "deputy"],
|
||||
};
|
||||
|
||||
const CONTRACT_APPROVE_ROLE_PERMISSION: Record<string, string> = {
|
||||
LINE_STAFF: FREIGHT_PERMS.contracts.approveLineStaff,
|
||||
DIRECTOR: FREIGHT_PERMS.contracts.approveDirector,
|
||||
CEO: FREIGHT_PERMS.contracts.approveCeo,
|
||||
};
|
||||
|
||||
/**
|
||||
* Can this user action a contract approval step requiring `requiredRole`?
|
||||
*
|
||||
* `requiredRole` is an `iam.position_types.key` (the role vocabulary approval
|
||||
* chains are configured in), or a legacy LINE_STAFF/DIRECTOR/CEO string. Used
|
||||
* to show Approve/Reject only to the step's actual approver — a chief step
|
||||
* shows only to a chief, a marketing-officer step only to that officer.
|
||||
*
|
||||
* Deliberately STRICTER than the API's `assertCanApproveContractStep`, which
|
||||
* also lets through anyone holding any contract-approve permission (a fallback
|
||||
* for delegates whose token omits the position type). That blanket is what made
|
||||
* every approver see the button, so it is dropped here: the visibility rule is
|
||||
* admin OR the matching position type (direct / legacy alias) OR the role's own
|
||||
* legacy approve permission. The server still guards the mutation.
|
||||
*/
|
||||
export function canApproveContractStep(
|
||||
user: AuthUser | null | undefined,
|
||||
requiredRole: string | null | undefined,
|
||||
): boolean {
|
||||
if (!user || !requiredRole) return false;
|
||||
if (isFreightApprovalAdmin(user)) return true;
|
||||
|
||||
const positionTypes = getPositionTypeKeys(user);
|
||||
if (positionTypes.includes(requiredRole)) return true;
|
||||
|
||||
const aliases = LEGACY_ROLE_POSITION_TYPES[requiredRole] ?? [];
|
||||
if (aliases.some((alias) => positionTypes.includes(alias))) return true;
|
||||
|
||||
const legacyPermission = CONTRACT_APPROVE_ROLE_PERMISSION[requiredRole];
|
||||
return Boolean(legacyPermission && hasPermission(user, legacyPermission));
|
||||
}
|
||||
|
||||
export function canAccessBookings(user: AuthUser | null | undefined): boolean {
|
||||
return hasPermission(user, FREIGHT_PERMS.bookings.view);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user