shoc-frontend-new/src/domain/work-orders/utils/uplift-display-utils.ts
arthur.bassi b60d0474bc fix(work-orders): keep admin-approved revoke off the WO dialog (SH-214)
SH-214/SH-212 own that action on Uplift Approvals; the WO surface only revokes the requester's auto-approved uplift.
2026-08-18 09:28:49 -03:00

130 lines
4 KiB
TypeScript

import type {
WorkOrderUplift,
UpliftSummary,
WorkOrderUpliftStatus,
} from "@/domain/work-orders/types/work-order-uplift";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
export function formatUpliftMoney(value: number | null | undefined): string {
if (value == null || Number.isNaN(value)) {
return "—";
}
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
}).format(value);
}
const STATUS_LABELS: Record<WorkOrderUpliftStatus, string> = {
pending: "Pending",
approved: "Approved",
auto_approved: "Auto",
rejected: "Rejected",
cancelled: "Cancelled",
revoked: "Revoked",
};
export function formatUpliftStatusLabel(status: WorkOrderUpliftStatus): string {
return STATUS_LABELS[status];
}
export function getUpliftCellLabel(summary: UpliftSummary): string | null {
if (!summary.hasUplift && summary.pendingCount <= 0) {
return null;
}
if (summary.pendingCount > 0) {
return summary.pendingCount === 1 ? "1 Pending" : `${summary.pendingCount} Pending`;
}
if (summary.primaryStatus) {
const label = formatUpliftStatusLabel(summary.primaryStatus);
if (summary.amount != null) {
return `${label} ${formatUpliftMoney(summary.amount)}`;
}
return label;
}
if (summary.hasUplift) {
return "Uplift";
}
return null;
}
export function getUpliftStatusPillStyle(status: WorkOrderUpliftStatus): {
background: string;
color: string;
} {
switch (status) {
case "pending":
return { background: "var(--status-pending-bg)", color: "var(--status-pending-text)" };
case "approved":
return { background: "var(--status-success-bg, #DCFCE7)", color: "var(--color-success)" };
case "auto_approved":
return { background: "var(--color-primary-tint)", color: "var(--color-primary)" };
case "rejected":
return { background: "#FEE2E2", color: "#B91C1C" };
case "cancelled":
case "revoked":
return { background: "var(--color-bg-muted)", color: "var(--color-text-muted)" };
default:
return { background: "var(--color-bg-muted)", color: "var(--color-text-muted)" };
}
}
export function isWorkOrderUpliftsReadOnly(status: WorkOrderTableRow["status"]): boolean {
return status === "Completed" || status === "Canceled";
}
export function canOpenUpliftsDialog(
summary: UpliftSummary,
status: WorkOrderTableRow["status"],
): boolean {
if (summary.hasUplift || summary.pendingCount > 0) {
return true;
}
return !isWorkOrderUpliftsReadOnly(status);
}
export function canRevokeWorkOrderUplift(
uplift: Pick<WorkOrderUplift, "status" | "requestedById">,
currentUserId: string | number | null | undefined,
): boolean {
if (uplift.status !== "auto_approved") {
// SH-214/SH-212: admin-approved revoke lives on Uplift Approvals, not the WO dialog.
return false;
}
return (
currentUserId != null &&
currentUserId !== "" &&
uplift.requestedById != null &&
String(uplift.requestedById) === String(currentUserId)
);
}
export function upliftRevokeRequiresReason(status: WorkOrderUpliftStatus): boolean {
return status === "approved";
}
export const UPLIFT_STANDARD_ALLOWANCE_USD = 500;
export const UPLIFT_EMERGENCY_ALLOWANCE_USD = 5000;
export function getUpliftAllowanceCap(workOrderType: string): number {
return workOrderType === "Emergency"
? UPLIFT_EMERGENCY_ALLOWANCE_USD
: UPLIFT_STANDARD_ALLOWANCE_USD;
}
export function hasOpenWorkOrderUplift(
uplifts: ReadonlyArray<Pick<WorkOrderUplift, "status">>,
): boolean {
return uplifts.some((uplift) => uplift.status === "pending");
}
export function getRemainingUpliftAllowance(
workOrderType: string,
uplifts: ReadonlyArray<Pick<WorkOrderUplift, "status" | "amount">>,
): number {
const consumed = uplifts
.filter((uplift) => uplift.status === "auto_approved")
.reduce((sum, uplift) => sum + uplift.amount, 0);
return Math.max(0, getUpliftAllowanceCap(workOrderType) - consumed);
}