179 lines
4.8 KiB
TypeScript
179 lines
4.8 KiB
TypeScript
import type { WeekdayAvailability } from "../types/resource.js";
|
|
|
|
export const DISPO_RESOURCE_CHAPTER_BY_TOKEN = {
|
|
"2D": "Digital Content Production",
|
|
"3D": "Digital Content Production",
|
|
PM: "Project Management",
|
|
AD: "Art Direction",
|
|
} as const;
|
|
|
|
export const DISPO_ASSIGNMENT_ROLE_BY_TOKEN = {
|
|
"2D": "2D Artist",
|
|
"3D": "3D Artist",
|
|
PM: "Project Manager",
|
|
AD: "Art Director",
|
|
} as const;
|
|
|
|
export const DISPO_UTILIZATION_CODE_BY_TOKEN = {
|
|
CH: "Chg",
|
|
MO: "M&O",
|
|
MD: "MD&I",
|
|
PD: "PD&R",
|
|
AB: "Absence",
|
|
NA: "Absence",
|
|
UN: null,
|
|
} as const;
|
|
|
|
export const DISPO_UTILIZATION_CATEGORIES = [
|
|
{
|
|
code: "Chg",
|
|
name: "Chargeable",
|
|
description: "Billable client project work",
|
|
sortOrder: 1,
|
|
isDefault: true,
|
|
},
|
|
{
|
|
code: "BD",
|
|
name: "Business Development",
|
|
description: "Sales, proposals, presales activities",
|
|
sortOrder: 2,
|
|
isDefault: false,
|
|
},
|
|
{
|
|
code: "MD&I",
|
|
name: "Market Development & Initiatives",
|
|
description: "R&D, innovation, market development",
|
|
sortOrder: 3,
|
|
isDefault: false,
|
|
},
|
|
{
|
|
code: "M&O",
|
|
name: "Management & Operations",
|
|
description: "Internal admin, management overhead",
|
|
sortOrder: 4,
|
|
isDefault: false,
|
|
},
|
|
{
|
|
code: "PD&R",
|
|
name: "People Development & Recruitment",
|
|
description: "Training, hiring, onboarding",
|
|
sortOrder: 5,
|
|
isDefault: false,
|
|
},
|
|
{
|
|
code: "Absence",
|
|
name: "Absence & Non Standard",
|
|
description: "Vacation, illness, non-standard leave (reduces SAH)",
|
|
sortOrder: 6,
|
|
isDefault: false,
|
|
},
|
|
] as const;
|
|
|
|
export const DISPO_INTERNAL_PROJECT_BUCKETS = [
|
|
{
|
|
sourceToken: "MO",
|
|
shortCode: "INT-MO",
|
|
name: "Management & Operations",
|
|
utilizationCategoryCode: "M&O",
|
|
},
|
|
{
|
|
sourceToken: "MD",
|
|
shortCode: "INT-MD",
|
|
name: "Market Development & Initiatives",
|
|
utilizationCategoryCode: "MD&I",
|
|
},
|
|
{
|
|
sourceToken: "PD",
|
|
shortCode: "INT-PD",
|
|
name: "People Development & Recruitment",
|
|
utilizationCategoryCode: "PD&R",
|
|
},
|
|
] as const;
|
|
|
|
export const DISPO_REQUIRED_ROLE_SEEDS = [
|
|
{
|
|
name: "2D Artist",
|
|
description: "Canonical Dispo assignment role for 2D delivery work",
|
|
},
|
|
{
|
|
name: "3D Artist",
|
|
description: "Canonical Dispo assignment role for 3D delivery work",
|
|
},
|
|
{
|
|
name: "Project Manager",
|
|
description: "Canonical Dispo assignment role for project management delivery",
|
|
},
|
|
{
|
|
name: "Art Director",
|
|
description: "Canonical Dispo assignment role for art direction delivery",
|
|
},
|
|
] as const;
|
|
|
|
export type DispoRoleToken = keyof typeof DISPO_ASSIGNMENT_ROLE_BY_TOKEN;
|
|
export type DispoChapterToken = keyof typeof DISPO_RESOURCE_CHAPTER_BY_TOKEN;
|
|
export type DispoUtilizationToken = keyof typeof DISPO_UTILIZATION_CODE_BY_TOKEN;
|
|
export type DispoInternalBucketDefinition = (typeof DISPO_INTERNAL_PROJECT_BUCKETS)[number];
|
|
|
|
function normalizeDispoTokenValue(value: string): string {
|
|
return value.trim().replace(/\s+/g, " ").toUpperCase();
|
|
}
|
|
|
|
function roundToTwoDecimals(value: number): number {
|
|
return Math.round(value * 100) / 100;
|
|
}
|
|
|
|
export function normalizeCanonicalResourceIdentity(value: string): string {
|
|
return value.trim().toLowerCase();
|
|
}
|
|
|
|
export function resolveCanonicalResourceIdentity(enterpriseId?: string | null, eid?: string | null) {
|
|
const normalizedEnterpriseId = enterpriseId ? normalizeCanonicalResourceIdentity(enterpriseId) : null;
|
|
const normalizedEid = eid ? normalizeCanonicalResourceIdentity(eid) : null;
|
|
const conflict = Boolean(
|
|
normalizedEnterpriseId &&
|
|
normalizedEid &&
|
|
normalizedEnterpriseId !== normalizedEid,
|
|
);
|
|
|
|
return {
|
|
canonicalId: conflict ? null : normalizedEnterpriseId ?? normalizedEid,
|
|
normalizedEnterpriseId,
|
|
normalizedEid,
|
|
conflict,
|
|
};
|
|
}
|
|
|
|
export function normalizeDispoChapterToken(token?: string | null): string | null {
|
|
if (!token) return null;
|
|
const normalizedToken = normalizeDispoTokenValue(token) as DispoChapterToken;
|
|
return DISPO_RESOURCE_CHAPTER_BY_TOKEN[normalizedToken] ?? null;
|
|
}
|
|
|
|
export function normalizeDispoRoleToken(token?: string | null): string | null {
|
|
if (!token) return null;
|
|
const normalizedToken = normalizeDispoTokenValue(token) as DispoRoleToken;
|
|
return DISPO_ASSIGNMENT_ROLE_BY_TOKEN[normalizedToken] ?? null;
|
|
}
|
|
|
|
export function normalizeDispoUtilizationToken(token?: string | null): string | null {
|
|
if (!token) return null;
|
|
const normalizedToken = normalizeDispoTokenValue(token) as DispoUtilizationToken;
|
|
return DISPO_UTILIZATION_CODE_BY_TOKEN[normalizedToken] ?? null;
|
|
}
|
|
|
|
export function createWeekdayAvailabilityFromFte(
|
|
fte: number,
|
|
dailyWorkingHours = 8,
|
|
): WeekdayAvailability {
|
|
const safeFte = Math.min(Math.max(fte, 0), 1);
|
|
const hoursPerDay = roundToTwoDecimals(dailyWorkingHours * safeFte);
|
|
|
|
return {
|
|
monday: hoursPerDay,
|
|
tuesday: hoursPerDay,
|
|
wednesday: hoursPerDay,
|
|
thursday: hoursPerDay,
|
|
friday: hoursPerDay,
|
|
};
|
|
}
|