mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 14:23:11 +00:00
fix(work-orders): consume board pendingUpliftCount for SH-188 gate
Map the authoritative board/search/detail pendingUpliftCount into the Completed closability gate and align the pending-uplift tooltip with SH-188.
This commit is contained in:
parent
dd60b1d72e
commit
8a221c8819
5 changed files with 76 additions and 15 deletions
|
|
@ -177,23 +177,25 @@ function readFlagColor(record: Record<string, unknown>): string | null {
|
|||
|
||||
function readUpliftSummary(item: Record<string, unknown>): WorkOrderUpliftSummary | undefined {
|
||||
const nested = asRecord(item.upliftSummary ?? item.UpliftSummary);
|
||||
const source = Object.keys(nested).length > 0 ? nested : item;
|
||||
const hasNested = Object.keys(nested).length > 0;
|
||||
const source = hasNested ? nested : item;
|
||||
|
||||
const pendingCount =
|
||||
readNumber(source, "pendingCount", "PendingCount") ??
|
||||
readNumber(item, "pendingUpliftCount", "PendingUpliftCount") ??
|
||||
0;
|
||||
// Board/search/detail contract (SH-188): pendingUpliftCount is authoritative when present.
|
||||
const boardCount = readNumber(item, "pendingUpliftCount", "PendingUpliftCount");
|
||||
const nestedCount = readNumber(source, "pendingCount", "PendingCount");
|
||||
const pendingCount = nestedCount ?? boardCount ?? 0;
|
||||
const hasUplift =
|
||||
readBool(source, "hasUplift", "HasUplift") ||
|
||||
readBool(item, "hasUplift", "HasUplift") ||
|
||||
pendingCount > 0;
|
||||
|
||||
if (!hasUplift && pendingCount === 0) {
|
||||
// No nested summary and no board count → omit (legacy payloads without the contract field).
|
||||
if (!hasNested && boardCount == null && !hasUplift) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const summary: WorkOrderUpliftSummary = {
|
||||
hasUplift,
|
||||
hasUplift: hasUplift || pendingCount > 0,
|
||||
pendingCount,
|
||||
};
|
||||
const primaryStatus = readString(source, "primaryStatus", "PrimaryStatus", "status", "Status");
|
||||
|
|
@ -207,11 +209,15 @@ function readHasPendingUplift(
|
|||
item: Record<string, unknown>,
|
||||
upliftSummary: WorkOrderUpliftSummary | undefined,
|
||||
): boolean | undefined {
|
||||
if (readBool(item, "hasPendingUplift", "HasPendingUplift")) {
|
||||
return true;
|
||||
if ("hasPendingUplift" in item || "HasPendingUplift" in item) {
|
||||
return Boolean(item.hasPendingUplift ?? item.HasPendingUplift);
|
||||
}
|
||||
if ((upliftSummary?.pendingCount ?? 0) > 0) {
|
||||
return true;
|
||||
const boardCount = readNumber(item, "pendingUpliftCount", "PendingUpliftCount");
|
||||
if (boardCount != null) {
|
||||
return boardCount > 0;
|
||||
}
|
||||
if (upliftSummary != null) {
|
||||
return upliftSummary.pendingCount > 0;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,9 +159,16 @@ export interface WorkOrderBoardRowApi {
|
|||
serviceNotes?: string | null;
|
||||
extraServices?: string[] | null;
|
||||
docStatus?: number | null;
|
||||
/** Work completed date from board/detail contract. */
|
||||
completedDate?: string | null;
|
||||
primaryDispatchId?: number | null;
|
||||
rowVersion?: string | null;
|
||||
dispatchRowVersion?: string | null;
|
||||
/**
|
||||
* Authoritative pending-uplift count across all dispatches for this WO (SH-188 / BE board contract).
|
||||
* Drives the Completed closability gate.
|
||||
*/
|
||||
pendingUpliftCount?: number;
|
||||
}
|
||||
|
||||
export interface WorkOrderBoardCountsApi {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ export type WoClosabilityInput = {
|
|||
|
||||
export const PENDING_UPLIFT_GAP = "Pending uplift approval";
|
||||
|
||||
/** Design copy when Completed is blocked solely by a pending uplift. */
|
||||
/** SH-188 tooltip when Completed is blocked solely by a pending uplift. */
|
||||
export const UPLIFT_PENDING_COMPLETED_TITLE =
|
||||
"This work order cannot be completed while an uplift is pending approval.";
|
||||
"This work order has an uplift pending approval and cannot be completed.";
|
||||
|
||||
/** Real (non-placeholder) WO number — non-empty and not an SH-prefixed draft id. */
|
||||
export function isRealWoNumber(woNumber: string): boolean {
|
||||
|
|
@ -83,7 +83,8 @@ export function getClosabilityGaps(row: WoClosabilityInput): string[] {
|
|||
gaps.push("Service");
|
||||
}
|
||||
|
||||
// Only an actually pending uplift blocks completion; missing uplift fields mean no pending evidence.
|
||||
// Authoritative pending uplift (board pendingUpliftCount / hasPendingUplift) blocks completion.
|
||||
// Missing uplift fields mean no pending evidence until the board contract supplies the count.
|
||||
if (hasPendingUplift(row)) {
|
||||
gaps.push(PENDING_UPLIFT_GAP);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,13 +71,38 @@ describe("StatusCell pending uplift closability", () => {
|
|||
expect(completed).toBeDisabled();
|
||||
expect(completed).toHaveAttribute(
|
||||
"title",
|
||||
"This work order cannot be completed while an uplift is pending approval.",
|
||||
"This work order has an uplift pending approval and cannot be completed.",
|
||||
);
|
||||
|
||||
fireEvent.click(completed);
|
||||
expect(onChangeStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("disables Completed with tooltip when board pendingUpliftCount maps to pending", () => {
|
||||
const onChangeStatus = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
<StatusCell
|
||||
wo={makeRow({
|
||||
hasPendingUplift: true,
|
||||
upliftSummary: { hasUplift: true, pendingCount: 1 },
|
||||
})}
|
||||
onChangeStatus={onChangeStatus}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
||||
|
||||
const completed = screen.getByRole("button", { name: /completed/i });
|
||||
expect(completed).toBeDisabled();
|
||||
expect(completed).toHaveAttribute(
|
||||
"title",
|
||||
"This work order has an uplift pending approval and cannot be completed.",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows Completed when no pending uplift", () => {
|
||||
const onChangeStatus = vi.fn();
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,28 @@ describe("mapWorkOrderTableRow (board DTO)", () => {
|
|||
expect(row.hasPendingUplift).toBe(true);
|
||||
});
|
||||
|
||||
it("maps board pendingUpliftCount into hasPendingUplift (SH-188 contract)", () => {
|
||||
const pending = mapWorkOrderTableRow({
|
||||
id: 305,
|
||||
pendingUpliftCount: 1,
|
||||
});
|
||||
expect(pending.hasPendingUplift).toBe(true);
|
||||
expect(pending.upliftSummary).toEqual({ hasUplift: true, pendingCount: 1 });
|
||||
|
||||
const clear = mapWorkOrderTableRow({
|
||||
id: 306,
|
||||
PendingUpliftCount: 0,
|
||||
});
|
||||
expect(clear.hasPendingUplift).toBe(false);
|
||||
expect(clear.upliftSummary).toEqual({ hasUplift: false, pendingCount: 0 });
|
||||
});
|
||||
|
||||
it("leaves hasPendingUplift undefined when board count is absent", () => {
|
||||
const row = mapWorkOrderTableRow({ id: 307 });
|
||||
expect(row.hasPendingUplift).toBeUndefined();
|
||||
expect(row.upliftSummary).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps hasPendingUplift from top-level API flag", () => {
|
||||
const row = mapWorkOrderTableRow({
|
||||
id: 304,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue