diff --git a/e2e/vendors/vendor-uplift-workflow.spec.ts b/e2e/vendors/vendor-uplift-workflow.spec.ts
index 69205a2e..f23f3ee1 100644
--- a/e2e/vendors/vendor-uplift-workflow.spec.ts
+++ b/e2e/vendors/vendor-uplift-workflow.spec.ts
@@ -349,7 +349,7 @@ test("internal approver can approve, reject, and revoke with audited notes", asy
await expect(page.getByRole("heading", { name: "Uplift Approvals" })).toBeVisible();
const approvalRow = page.getByRole("row").filter({ hasText: "DSP-41" });
- await approvalRow.getByRole("button", { name: "Approve" }).click();
+ await approvalRow.getByRole("button", { name: "Approve $25.00" }).click();
const changesRow = page.getByRole("row").filter({ hasText: "DSP-42" });
await changesRow.getByRole("button", { name: "Reject" }).click();
@@ -360,7 +360,7 @@ test("internal approver can approve, reject, and revoke with audited notes", asy
const approvedRow = page.getByRole("row").filter({ hasText: "DSP-41" });
await approvedRow.getByRole("button", { name: "Revoke" }).click();
await page.getByLabel("Reason for revoking").fill("Approval was made in error.");
- await page.getByRole("button", { name: "Revoke $125" }).click();
+ await page.getByRole("button", { name: "Revoke $25.00" }).click();
await expect
.poll(() => decisions)
diff --git a/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx b/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx
index 5cc446ce..3f52d36b 100644
--- a/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx
+++ b/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx
@@ -18,6 +18,7 @@ import {
} from "@/app/(protected)/uplifts/_components/format-uplift-money";
import { Text } from "@/components/ui/text";
import { useUpliftsCanApprove } from "@/domain/uplifts/use-cases/use-uplifts-can-approve";
+import { upliftAmount } from "@/domain/uplifts/utils/uplift-amount";
import type { UpliftQueueItem } from "@/domain/uplifts/types/uplift";
import { formatDateTime, getWaitTimeTextClass, timeSince, waitTimeColor } from "@/lib/time-utils";
@@ -103,7 +104,7 @@ function PendingRowActions({
disabled={!canDecide || isDecisionPending}
onClick={() => onApprove(row)}
>
- Approve
+ Approve {formatUpliftMoney(upliftAmount(row))}
@@ -214,7 +215,7 @@ function UpliftApprovalRow({
- {formatUpliftMoney(row.requestedNTE)}
+ {formatUpliftMoney(upliftAmount(row))}
diff --git a/src/app/(protected)/uplifts/_components/uplift-decision-dialogs.tsx b/src/app/(protected)/uplifts/_components/uplift-decision-dialogs.tsx
index 7cc8537b..f6653a7b 100644
--- a/src/app/(protected)/uplifts/_components/uplift-decision-dialogs.tsx
+++ b/src/app/(protected)/uplifts/_components/uplift-decision-dialogs.tsx
@@ -1,5 +1,6 @@
import { RejectDialog } from "@/app/(protected)/uplifts/_components/reject-dialog";
import { RevokeDialog } from "@/app/(protected)/uplifts/_components/revoke-dialog";
+import { upliftAmount } from "@/domain/uplifts/utils/uplift-amount";
import type { useUpliftApprovalController } from "@/domain/uplifts/use-cases/use-uplift-approval-controller";
export type UpliftApprovalController = ReturnType;
@@ -23,7 +24,7 @@ export function UpliftDecisionDialogs({ controller }: { controller: UpliftApprov
<>
onApprove(item)}
>
- Approve
+ Approve {formatUpliftMoney(upliftAmount(item))}
@@ -394,7 +395,7 @@ function UpliftDetailContent({
Amount
- {formatUpliftMoney(item.requestedNTE)}
+ {formatUpliftMoney(upliftAmount(item))}
diff --git a/src/domain/uplifts/use-cases/use-uplift-approval-controller.ts b/src/domain/uplifts/use-cases/use-uplift-approval-controller.ts
index f02e7664..bbf7bbce 100644
--- a/src/domain/uplifts/use-cases/use-uplift-approval-controller.ts
+++ b/src/domain/uplifts/use-cases/use-uplift-approval-controller.ts
@@ -5,6 +5,7 @@ import {
useRejectUplift,
useRevokeUplift,
} from "@/domain/uplifts/use-cases/use-uplift-actions";
+import { upliftAmount } from "@/domain/uplifts/utils/uplift-amount";
export function useUpliftApprovalController() {
const [detailItem, setDetailItem] = useState(null);
@@ -15,7 +16,7 @@ export function useUpliftApprovalController() {
const revokeUplift = useRevokeUplift();
const handleApprove = (row: UpliftQueueItem) => {
- approveUplift.mutate({ id: row.id, amount: row.delta, woNumber: row.woNumber });
+ approveUplift.mutate({ id: row.id, amount: upliftAmount(row), woNumber: row.woNumber });
setDetailItem(null);
};
@@ -35,7 +36,7 @@ export function useUpliftApprovalController() {
{
id: rejectTarget.id,
note: reason,
- amount: rejectTarget.delta,
+ amount: upliftAmount(rejectTarget),
woNumber: rejectTarget.woNumber,
},
{ onSuccess: () => setRejectTarget(null) },
@@ -48,7 +49,7 @@ export function useUpliftApprovalController() {
{
upliftId: revokeTarget.id,
reason,
- amount: revokeTarget.delta,
+ amount: upliftAmount(revokeTarget),
woNumber: revokeTarget.woNumber,
},
{ onSuccess: () => setRevokeTarget(null) },
diff --git a/src/domain/uplifts/utils/uplift-amount.ts b/src/domain/uplifts/utils/uplift-amount.ts
new file mode 100644
index 00000000..bd0110db
--- /dev/null
+++ b/src/domain/uplifts/utils/uplift-amount.ts
@@ -0,0 +1,11 @@
+import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
+
+/**
+ * The amount of an uplift: the increase being requested on top of the work
+ * order's current NTE. Every surface that names "the uplift" (queue Amount,
+ * Approve label, detail modal, reject/revoke dialogs and decision toasts)
+ * shows this figure; `requestedNTE` is the resulting NTE total, not the uplift.
+ */
+export function upliftAmount(request: Pick): number {
+ return request.delta;
+}
diff --git a/src/test/app/(protected)/uplifts/uplift-approvals-approved-tab.test.tsx b/src/test/app/(protected)/uplifts/uplift-approvals-approved-tab.test.tsx
index 05034d48..3bbf9dc1 100644
--- a/src/test/app/(protected)/uplifts/uplift-approvals-approved-tab.test.tsx
+++ b/src/test/app/(protected)/uplifts/uplift-approvals-approved-tab.test.tsx
@@ -132,7 +132,7 @@ describe("Uplift Approvals approved tab", () => {
expect(screen.getByRole("heading", { name: "Revoke this approval?" })).toBeInTheDocument();
expect(
screen.getByText(
- "The approved uplift of $900.00 on WO WO-56 will be withdrawn. This does not recover money already spent — it records that the authorization was a mistake.",
+ "The approved uplift of $400.00 on WO WO-56 will be withdrawn. This does not recover money already spent — it records that the authorization was a mistake.",
),
).toBeInTheDocument();
});
diff --git a/src/test/app/(protected)/uplifts/uplift-approvals-one-amount.test.tsx b/src/test/app/(protected)/uplifts/uplift-approvals-one-amount.test.tsx
new file mode 100644
index 00000000..30a13a51
--- /dev/null
+++ b/src/test/app/(protected)/uplifts/uplift-approvals-one-amount.test.tsx
@@ -0,0 +1,189 @@
+import { fireEvent, screen, waitFor, within } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import type { UpliftQueueItem, UpliftsQueueResult } from "@/domain/uplifts/types/uplift";
+import { renderWithProviders } from "@/test/test-utils";
+
+// A work order that already carries an NTE: the uplift (the increase) and the
+// requested NTE total differ, so any surface reading the wrong field shows up.
+const UPLIFT = "$4,999,997,000.00";
+const REQUESTED_NTE_TOTAL = "$5,000,000,000.00";
+
+const api = vi.hoisted(() => ({
+ approve: vi.fn(),
+ reject: vi.fn(),
+ revoke: vi.fn(),
+}));
+const toastSuccess = vi.hoisted(() => vi.fn());
+const queueState = vi.hoisted(() => ({
+ data: null as UpliftsQueueResult | null,
+}));
+
+vi.mock("@/domain/uplifts/api/uplifts-api", () => ({
+ upliftsApi: {
+ approve: (...args: unknown[]) => api.approve(...args),
+ reject: (...args: unknown[]) => api.reject(...args),
+ revoke: (...args: unknown[]) => api.revoke(...args),
+ },
+}));
+
+vi.mock("react-toastify", async (importOriginal) => ({
+ ...(await importOriginal