Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/integrations/projects/pipelineHandoverRoute.server.ts
T

38 lines
1.9 KiB
TypeScript

// WF-P8 M04 — route-side assembly of the handover dialog's view-model.
//
// Two screens reach the same state: the board (S01, right after a card lands in
// the final stage) and the detail page (S02, when the project is already
// sitting there). Both need the identical answer to "is a handover pending, and
// what does it contain", so the permission gate, the preview load and the name
// resolution live here once instead of in two route files that could drift.
import { buildPipelineHandoverVM, type PipelineHandoverVM } from "@hivegrid/view-models";
import { resolveUserLabels } from "~/utils/integrations/crm/crmService.server";
import { loadPendingHandover } from "./pipelineHandoverService.server";
import { toProjektNotizContext } from "./projektNotizService.server";
import type { ProjectRouteContext } from "./projectAccess.server";
/**
* The M04 payload for a project, or null when no handover is pending — which is
* the normal case and never an error: without the permission, without a
* successor board, or anywhere but the final stage there is simply nothing to
* offer.
*/
export async function loadHandoverVM(input: {
tenantId: string;
projectId: string;
ctx: ProjectRouteContext;
}): Promise<PipelineHandoverVM | null> {
if (!input.ctx.permissions.includes("projects.handover")) return null;
const pending = await loadPendingHandover({
tenantId: input.tenantId,
projectId: input.projectId,
ctx: toProjektNotizContext(input.ctx),
});
if (!pending) return null;
// The preview carries raw author/assignee ids. Resolve exactly those — the
// view-model drops what it cannot name rather than print a cuid at a user.
const labels = await resolveUserLabels(pending.candidates.map((candidate) => candidate.personId).filter((id): id is string => Boolean(id)));
return buildPipelineHandoverVM({ ...pending, userLabels: Object.fromEntries(labels) });
}