mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 03:23:12 +00:00
Merge pull request #283 from Sea-Haven-Industries/fix/ab/sh-406-no-revoke-auto-approved
Hide Revoke from admins on auto-approved uplifts
This commit is contained in:
commit
810fa5d5b0
6 changed files with 62 additions and 7 deletions
|
|
@ -12,6 +12,7 @@ type WorkOrderUpliftListItemProps = {
|
|||
uplift: WorkOrderUplift;
|
||||
readOnly: boolean;
|
||||
currentUserId: string | number | null | undefined;
|
||||
currentUserIsAdmin: boolean;
|
||||
pendingAction?: boolean;
|
||||
onCancel?: () => void;
|
||||
onRevoke?: () => void;
|
||||
|
|
@ -21,6 +22,7 @@ export function WorkOrderUpliftListItem({
|
|||
uplift,
|
||||
readOnly,
|
||||
currentUserId,
|
||||
currentUserIsAdmin,
|
||||
pendingAction = false,
|
||||
onCancel,
|
||||
onRevoke,
|
||||
|
|
@ -28,7 +30,9 @@ export function WorkOrderUpliftListItem({
|
|||
const pillStyle = getUpliftStatusPillStyle(uplift.status);
|
||||
const showCancel = !readOnly && uplift.status === "pending" && Boolean(onCancel);
|
||||
const showRevoke =
|
||||
!readOnly && canRevokeWorkOrderUplift(uplift, currentUserId) && Boolean(onRevoke);
|
||||
!readOnly &&
|
||||
canRevokeWorkOrderUplift(uplift, currentUserId, currentUserIsAdmin) &&
|
||||
Boolean(onRevoke);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-[var(--color-border)] p-3">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { hasOpenWorkOrderUplift } from "@/domain/work-orders/utils/uplift-displa
|
|||
type WorkOrderUpliftsDialogContentProps = {
|
||||
readOnly: boolean;
|
||||
currentUserId: string | number | null | undefined;
|
||||
currentUserIsAdmin: boolean;
|
||||
readOnlyStatusLabel?: string;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
|
|
@ -28,6 +29,7 @@ type WorkOrderUpliftsDialogContentProps = {
|
|||
export function WorkOrderUpliftsDialogContent({
|
||||
readOnly,
|
||||
currentUserId,
|
||||
currentUserIsAdmin,
|
||||
readOnlyStatusLabel,
|
||||
isLoading,
|
||||
error,
|
||||
|
|
@ -77,6 +79,7 @@ export function WorkOrderUpliftsDialogContent({
|
|||
uplift={uplift}
|
||||
readOnly={readOnly}
|
||||
currentUserId={currentUserId}
|
||||
currentUserIsAdmin={currentUserIsAdmin}
|
||||
pendingAction={actionPending}
|
||||
onCancel={readOnly ? undefined : () => onCancelUplift(uplift.id)}
|
||||
onRevoke={readOnly ? undefined : () => onRevokeUplift(uplift.id)}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
isWorkOrderUpliftsReadOnly,
|
||||
upliftRevokeRequiresReason,
|
||||
} from "@/domain/work-orders/utils/uplift-display-utils";
|
||||
import { isAdminUser } from "@/lib/auth/user-utils";
|
||||
import { useAuthContext } from "@/providers/auth-context";
|
||||
|
||||
type RevokeTarget = {
|
||||
|
|
@ -78,6 +79,7 @@ export function WorkOrderUpliftsDialog({ row, open, onClose }: WorkOrderUpliftsD
|
|||
<WorkOrderUpliftsDialogContent
|
||||
readOnly={readOnly}
|
||||
currentUserId={user?.id}
|
||||
currentUserIsAdmin={isAdminUser(user?.userRoles)}
|
||||
readOnlyStatusLabel={row?.status}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
|
|
|
|||
|
|
@ -87,11 +87,16 @@ export function canOpenUpliftsDialog(
|
|||
export function canRevokeWorkOrderUplift(
|
||||
uplift: Pick<WorkOrderUplift, "status" | "requestedById">,
|
||||
currentUserId: string | number | null | undefined,
|
||||
currentUserIsAdmin: boolean,
|
||||
): boolean {
|
||||
if (uplift.status !== "auto_approved") {
|
||||
// SH-214/SH-212: admin-approved revoke lives on Uplift Approvals, not the WO dialog.
|
||||
return false;
|
||||
}
|
||||
if (currentUserIsAdmin) {
|
||||
// Admins revoke only admin-approved uplifts; an auto-approval is the requester's to revoke.
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
currentUserId != null &&
|
||||
currentUserId !== "" &&
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ const pendingUplift: WorkOrderUplift = {
|
|||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
uplifts: [] as WorkOrderUplift[],
|
||||
authUser: { id: "u1", userRoles: "Dispatcher" },
|
||||
}));
|
||||
|
||||
vi.mock("@/providers/auth-context", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/providers/auth-context")>()),
|
||||
useAuthContext: () => ({ user: mockState.authUser }),
|
||||
}));
|
||||
|
||||
const baseRow: WorkOrderTableRow = {
|
||||
|
|
@ -145,6 +151,31 @@ describe("WorkOrderUpliftsDialog affordances", () => {
|
|||
expect(screen.queryByRole("button", { name: /create uplift/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /cancel pending/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows no Revoke to an admin on the auto-approved uplift they requested", () => {
|
||||
mockState.authUser = { id: "admin-1", userRoles: "Admin" };
|
||||
mockState.uplifts = [{ ...pendingUplift, status: "auto_approved", requestedById: "admin-1" }];
|
||||
|
||||
renderWithProviders(<WorkOrderUpliftsDialog row={baseRow} open onClose={vi.fn()} />, {
|
||||
withAuth: true,
|
||||
});
|
||||
|
||||
expect(screen.getByText("Auto")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /revoke/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Revoke to the dispatcher on the auto-approved uplift they requested", () => {
|
||||
mockState.authUser = { id: "dispatcher-1", userRoles: "Dispatcher" };
|
||||
mockState.uplifts = [
|
||||
{ ...pendingUplift, status: "auto_approved", requestedById: "dispatcher-1" },
|
||||
];
|
||||
|
||||
renderWithProviders(<WorkOrderUpliftsDialog row={baseRow} open onClose={vi.fn()} />, {
|
||||
withAuth: true,
|
||||
});
|
||||
|
||||
expect(screen.getByRole("button", { name: /revoke/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkOrderUpliftListItem revoke affordances", () => {
|
||||
|
|
@ -154,6 +185,7 @@ describe("WorkOrderUpliftListItem revoke affordances", () => {
|
|||
uplift={{ ...pendingUplift, status: "approved" }}
|
||||
readOnly={false}
|
||||
currentUserId="admin-1"
|
||||
currentUserIsAdmin
|
||||
onRevoke={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
|
|
@ -168,6 +200,7 @@ describe("WorkOrderUpliftListItem revoke affordances", () => {
|
|||
uplift={{ ...pendingUplift, status: "auto_approved", requestedById: "dispatcher-1" }}
|
||||
readOnly={false}
|
||||
currentUserId="dispatcher-1"
|
||||
currentUserIsAdmin={false}
|
||||
onRevoke={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ describe("canRevokeWorkOrderUplift", () => {
|
|||
canRevokeWorkOrderUplift(
|
||||
{ status: "auto_approved", requestedById: "dispatcher-1" },
|
||||
"dispatcher-1",
|
||||
false,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
|
@ -21,6 +22,7 @@ describe("canRevokeWorkOrderUplift", () => {
|
|||
canRevokeWorkOrderUplift(
|
||||
{ status: "auto_approved", requestedById: "other-dispatcher" },
|
||||
"dispatcher-1",
|
||||
false,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
|
@ -30,28 +32,34 @@ describe("canRevokeWorkOrderUplift", () => {
|
|||
canRevokeWorkOrderUplift(
|
||||
{ status: "auto_approved", requestedById: "dispatcher-1" },
|
||||
"admin-user",
|
||||
true,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("allows admin to revoke their own auto-approved uplift as the requester", () => {
|
||||
it("rejects admin revoking an auto-approved uplift even as its requester", () => {
|
||||
expect(
|
||||
canRevokeWorkOrderUplift(
|
||||
{ status: "auto_approved", requestedById: "admin-user" },
|
||||
"admin-user",
|
||||
true,
|
||||
),
|
||||
).toBe(true);
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects approved revoke on the Work Order surface (SH-214)", () => {
|
||||
const uplift = { status: "approved" as const, requestedById: "dispatcher-1" };
|
||||
expect(canRevokeWorkOrderUplift(uplift, "dispatcher-1")).toBe(false);
|
||||
expect(canRevokeWorkOrderUplift(uplift, "admin-user")).toBe(false);
|
||||
expect(canRevokeWorkOrderUplift(uplift, "dispatcher-1", false)).toBe(false);
|
||||
expect(canRevokeWorkOrderUplift(uplift, "admin-user", true)).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects revoke for other statuses", () => {
|
||||
expect(canRevokeWorkOrderUplift({ status: "pending", requestedById: "u1" }, "u1")).toBe(false);
|
||||
expect(canRevokeWorkOrderUplift({ status: "rejected", requestedById: "u1" }, "u1")).toBe(false);
|
||||
expect(canRevokeWorkOrderUplift({ status: "pending", requestedById: "u1" }, "u1", false)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(canRevokeWorkOrderUplift({ status: "rejected", requestedById: "u1" }, "u1", false)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue