feat(application): complete dispo import operator flow
This commit is contained in:
@@ -14,7 +14,12 @@ import {
|
||||
StagedRecordStatus,
|
||||
VacationStatus,
|
||||
} from "@planarchy/db";
|
||||
import { buildBatchSummaryEntry, buildFallbackAccentureEmail, toJsonObject } from "./shared.js";
|
||||
import {
|
||||
buildBatchSummaryEntry,
|
||||
buildFallbackAccentureEmail,
|
||||
deriveRoleTokens,
|
||||
toJsonObject,
|
||||
} from "./shared.js";
|
||||
|
||||
type CommitDbClient = Pick<
|
||||
PrismaClient,
|
||||
@@ -83,6 +88,53 @@ interface AggregatedAssignment {
|
||||
utilizationCategoryCode: string | null;
|
||||
}
|
||||
|
||||
function asNullableString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
||||
}
|
||||
|
||||
function normalizeFallbackRoleName(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function inferRoleNameFromResource(resource: MergedStagedResource): string | null {
|
||||
const explicitRoleName = Array.from(resource.roleTokens)
|
||||
.map((token) => normalizeDispoRoleToken(token))
|
||||
.find((roleName): roleName is string => Boolean(roleName));
|
||||
if (explicitRoleName) {
|
||||
return explicitRoleName;
|
||||
}
|
||||
|
||||
const derivedRoleName = deriveRoleTokens(
|
||||
resource.chapter,
|
||||
asNullableString(resource.rawPayload.department),
|
||||
asNullableString(resource.rawPayload.mainSkillset),
|
||||
asNullableString(resource.rawPayload.sapOrgUnitLevelSix),
|
||||
asNullableString(resource.rawPayload.sapOrgUnitLevelSeven),
|
||||
asNullableString(resource.rawPayload.sapEmployeeName),
|
||||
)
|
||||
.map((token) => normalizeDispoRoleToken(token))
|
||||
.find((roleName): roleName is string => Boolean(roleName));
|
||||
if (derivedRoleName) {
|
||||
return derivedRoleName;
|
||||
}
|
||||
|
||||
if (resource.chapter === "Art Direction") {
|
||||
return "Art Director";
|
||||
}
|
||||
if (resource.chapter === "Project Management") {
|
||||
return "Project Manager";
|
||||
}
|
||||
|
||||
const fallbackRoleLabel =
|
||||
asNullableString(resource.rawPayload.department) ??
|
||||
asNullableString(resource.rawPayload.mainSkillset) ??
|
||||
asNullableString(resource.chapter) ??
|
||||
asNullableString(resource.rawPayload.sapOrgUnitLevelSeven) ??
|
||||
asNullableString(resource.rawPayload.sapOrgUnitLevelSix);
|
||||
|
||||
return fallbackRoleLabel ? normalizeFallbackRoleName(fallbackRoleLabel) : null;
|
||||
}
|
||||
|
||||
export interface CommitDispoImportBatchInput {
|
||||
allowTbdUnresolved?: boolean;
|
||||
importBatchId: string;
|
||||
@@ -268,6 +320,7 @@ function aggregateAssignments(
|
||||
resourceIdByKey: ReadonlyMap<string, string>,
|
||||
projectIdByShortCode: ReadonlyMap<string, string>,
|
||||
roleIdByName: ReadonlyMap<string, string>,
|
||||
resourceRoleNameByKey: ReadonlyMap<string, string>,
|
||||
): AggregatedAssignment[] {
|
||||
const resolvedRows = rows
|
||||
.filter((row) => !row.isUnassigned && !row.isTbd)
|
||||
@@ -275,7 +328,11 @@ function aggregateAssignments(
|
||||
const projectShortCode = row.isInternal
|
||||
? resolveInternalProjectShortCode(row.utilizationCategoryCode)
|
||||
: (row.projectKey ?? null);
|
||||
const roleName = row.roleName ?? normalizeDispoRoleToken(row.roleToken);
|
||||
const roleName =
|
||||
row.roleName ??
|
||||
normalizeDispoRoleToken(row.roleToken) ??
|
||||
resourceRoleNameByKey.get(row.resourceExternalId) ??
|
||||
null;
|
||||
const resourceId = resourceIdByKey.get(row.resourceExternalId);
|
||||
const projectId = projectShortCode ? projectIdByShortCode.get(projectShortCode) : null;
|
||||
const roleId = roleName ? roleIdByName.get(roleName) : null;
|
||||
@@ -541,12 +598,43 @@ export async function commitDispoImportBatch(
|
||||
);
|
||||
|
||||
const mergedResources = mergeStagedResources(stagedResources);
|
||||
const inferredRoleNames = new Set<string>();
|
||||
for (const resource of mergedResources.values()) {
|
||||
const inferredRoleName = inferRoleNameFromResource(resource);
|
||||
if (inferredRoleName) {
|
||||
inferredRoleNames.add(inferredRoleName);
|
||||
}
|
||||
}
|
||||
for (const roleName of inferredRoleNames) {
|
||||
if (roleIdByName.has(roleName)) {
|
||||
continue;
|
||||
}
|
||||
const role = await tx.role.upsert({
|
||||
where: { name: roleName },
|
||||
update: {
|
||||
description: "Imported Dispo resource role",
|
||||
isActive: true,
|
||||
},
|
||||
create: {
|
||||
name: roleName,
|
||||
description: "Imported Dispo resource role",
|
||||
isActive: true,
|
||||
},
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
roleIdByName.set(role.name, role.id);
|
||||
}
|
||||
const resourceIdByKey = new Map<string, string>();
|
||||
const resourceRoleNameByKey = new Map<string, string>();
|
||||
let upsertedResourceRoles = 0;
|
||||
let updatedResourceAvailabilities = 0;
|
||||
let updatedEntitlements = 0;
|
||||
|
||||
for (const resource of mergedResources.values()) {
|
||||
const inferredRoleName = inferRoleNameFromResource(resource);
|
||||
if (inferredRoleName) {
|
||||
resourceRoleNameByKey.set(resource.canonicalExternalId, inferredRoleName);
|
||||
}
|
||||
const managementGroup = resource.managementLevelGroupName
|
||||
? managementLevelGroupByName.get(resource.managementLevelGroupName)
|
||||
: null;
|
||||
@@ -641,11 +729,16 @@ export async function commitDispoImportBatch(
|
||||
|
||||
resourceIdByKey.set(resource.canonicalExternalId, committed.id);
|
||||
|
||||
for (const roleToken of resource.roleTokens) {
|
||||
const roleName = normalizeDispoRoleToken(roleToken);
|
||||
if (!roleName) {
|
||||
continue;
|
||||
}
|
||||
const resourceRoleNames = new Set(
|
||||
Array.from(resource.roleTokens)
|
||||
.map((roleToken) => normalizeDispoRoleToken(roleToken))
|
||||
.filter((roleName): roleName is string => Boolean(roleName)),
|
||||
);
|
||||
if (inferredRoleName) {
|
||||
resourceRoleNames.add(inferredRoleName);
|
||||
}
|
||||
|
||||
for (const roleName of resourceRoleNames) {
|
||||
const roleId = roleIdByName.get(roleName);
|
||||
if (!roleId) {
|
||||
continue;
|
||||
@@ -796,6 +889,7 @@ export async function commitDispoImportBatch(
|
||||
resourceIdByKey,
|
||||
projectIdByShortCode,
|
||||
roleIdByName,
|
||||
resourceRoleNameByKey,
|
||||
);
|
||||
|
||||
for (const assignment of aggregatedAssignments) {
|
||||
@@ -974,6 +1068,9 @@ export async function commitDispoImportBatch(
|
||||
skippedTbd: skippedTbdUnresolved,
|
||||
},
|
||||
} satisfies CommitDispoImportBatchResult;
|
||||
}, {
|
||||
maxWait: 30_000,
|
||||
timeout: 600_000,
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user