feat(platform): checkpoint current implementation state

This commit is contained in:
2026-04-01 07:42:03 +02:00
parent 3e53471f05
commit 8c5be51251
125 changed files with 10269 additions and 17808 deletions
@@ -77,6 +77,16 @@ export interface TimelineAllocationCarveResult {
resourceId: string | null;
}
export interface TimelineAllocationExtractResult {
action: "unchanged" | "extracted";
allocationGroupId: string;
extractedAllocationId: string;
updatedAllocationIds: string[];
createdAllocationIds: string[];
projectId: string;
resourceId: string | null;
}
export async function carveTimelineAllocationRange(input: {
db: PrismaClient;
allocationId: string;
@@ -183,3 +193,119 @@ export async function carveTimelineAllocationRange(input: {
};
});
}
export async function extractTimelineAllocationFragment(input: {
db: PrismaClient;
allocationId: string;
startDate: Date;
endDate: Date;
}): Promise<TimelineAllocationExtractResult> {
const resolved = await loadAllocationEntry(input.db, input.allocationId);
if (resolved.kind !== "assignment") {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Only staffed assignments can currently be extracted into fragments.",
});
}
const extractStart = toUtcCalendarDate(input.startDate);
const extractEnd = toUtcCalendarDate(input.endDate);
if (extractEnd < extractStart) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Extract end date must be on or after the extract start date.",
});
}
const assignment = resolved.assignment;
const assignmentStart = toUtcCalendarDate(assignment.startDate);
const assignmentEnd = toUtcCalendarDate(assignment.endDate);
if (extractStart < assignmentStart || extractEnd > assignmentEnd) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "The requested extract range must be fully inside the existing allocation.",
});
}
const { groupId, metadata } = readFragmentMetadata(assignment.metadata, assignment.id);
const hasLeftFragment = extractStart > assignmentStart;
const hasRightFragment = extractEnd < assignmentEnd;
if (!hasLeftFragment && !hasRightFragment) {
return {
action: "unchanged",
allocationGroupId: groupId,
extractedAllocationId: assignment.id,
updatedAllocationIds: [],
createdAllocationIds: [],
projectId: assignment.projectId,
resourceId: assignment.resourceId,
};
}
return input.db.$transaction(async (tx) => {
const createdAllocationIds: string[] = [];
const updated = await updateAssignment(
tx as unknown as Parameters<typeof updateAssignment>[0],
assignment.id,
{
startDate: extractStart,
endDate: extractEnd,
metadata,
},
);
if (hasLeftFragment) {
const createdLeft = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
projectId: assignment.projectId,
startDate: assignmentStart,
endDate: addDays(extractStart, -1),
hoursPerDay: assignment.hoursPerDay,
percentage: assignment.percentage,
role: assignment.role ?? undefined,
roleId: assignment.roleId ?? undefined,
dailyCostCents: assignment.dailyCostCents,
status: toSharedAllocationStatus(assignment.status),
metadata,
},
);
createdAllocationIds.push(createdLeft.id);
}
if (hasRightFragment) {
const createdRight = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
projectId: assignment.projectId,
startDate: addDays(extractEnd, 1),
endDate: assignmentEnd,
hoursPerDay: assignment.hoursPerDay,
percentage: assignment.percentage,
role: assignment.role ?? undefined,
roleId: assignment.roleId ?? undefined,
dailyCostCents: assignment.dailyCostCents,
status: toSharedAllocationStatus(assignment.status),
metadata,
},
);
createdAllocationIds.push(createdRight.id);
}
return {
action: "extracted" as const,
allocationGroupId: groupId,
extractedAllocationId: updated.id,
updatedAllocationIds: [updated.id],
createdAllocationIds,
projectId: assignment.projectId,
resourceId: assignment.resourceId,
};
});
}