rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled

rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-05-21 16:28:40 +02:00
committed by Hartmut
parent d9a7ec0338
commit b41c1d2501
943 changed files with 24548 additions and 16832 deletions
@@ -1,10 +1,10 @@
import { DispoStagedRecordType } from "@capakraken/db";
import { DispoStagedRecordType } from "@nexus/db";
import {
VacationType,
normalizeCanonicalResourceIdentity,
normalizeDispoRoleToken,
normalizeDispoUtilizationToken,
} from "@capakraken/shared";
} from "@nexus/shared";
import { readWorksheetMatrix, toColumnLetter, type WorksheetCellValue } from "./read-workbook.js";
import {
DISPO_PLANNING_SHEET,
@@ -182,27 +182,37 @@ function isPlanningSummaryRow(row: ReadonlyArray<WorksheetCellValue>): boolean {
const normalizedLabels = new Set(repeatedLabels.map((value) => value.toLowerCase()));
const label = repeatedLabels[0] ?? null;
return normalizedLabels.size === 1 && label !== null && label.startsWith("(") && label.endsWith(")");
return (
normalizedLabels.size === 1 && label !== null && label.startsWith("(") && label.endsWith(")")
);
}
function buildPlanningColumns(rows: ReadonlyArray<ReadonlyArray<WorksheetCellValue>>) {
const columns: PlanningColumn[] = [];
const headerWidth = Math.max(rows[DISPO_DATE_ROW - 1]?.length ?? 0, rows[DISPO_SLOT_ROW - 1]?.length ?? 0);
const headerWidth = Math.max(
rows[DISPO_DATE_ROW - 1]?.length ?? 0,
rows[DISPO_SLOT_ROW - 1]?.length ?? 0,
);
for (let columnNumber = DISPO_PLANNING_START_COLUMN; columnNumber <= headerWidth; columnNumber += 1) {
for (
let columnNumber = DISPO_PLANNING_START_COLUMN;
columnNumber <= headerWidth;
columnNumber += 1
) {
const slotLabel = normalizeNullableWorkbookValue(rows[DISPO_SLOT_ROW - 1]?.[columnNumber - 1]);
if (!slotLabel) {
continue;
}
const currentHeaderValue = rows[DISPO_DATE_ROW - 1]?.[columnNumber - 1] ?? null;
const previousHeaderLabel = normalizeNullableWorkbookValue(rows[DISPO_DATE_ROW - 1]?.[columnNumber - 2]);
const previousHeaderLabel = normalizeNullableWorkbookValue(
rows[DISPO_DATE_ROW - 1]?.[columnNumber - 2],
);
const currentHeaderLabel = normalizeNullableWorkbookValue(currentHeaderValue);
const nextHeaderValue = rows[DISPO_DATE_ROW - 1]?.[columnNumber] ?? null;
const assignmentDate =
toDateOnlyInBerlin(currentHeaderValue) ??
toDateOnlyInBerlin(nextHeaderValue);
toDateOnlyInBerlin(currentHeaderValue) ?? toDateOnlyInBerlin(nextHeaderValue);
if (!assignmentDate) {
continue;
@@ -236,10 +246,15 @@ function normalizePlanningToken(token: string): string {
}
function extractBracketTokens(token: string): string[] {
return Array.from(token.matchAll(/\[([^\]]+)\]/g), (match) => match[1]?.trim() ?? "").filter(Boolean);
return Array.from(token.matchAll(/\[([^\]]+)\]/g), (match) => match[1]?.trim() ?? "").filter(
Boolean,
);
}
function extractUtilizationToken(token: string): { utilizationToken: string | null; winProbability: number | null } {
function extractUtilizationToken(token: string): {
utilizationToken: string | null;
winProbability: number | null;
} {
const matches = Array.from(token.matchAll(/\{([A-Z]+)(\d{0,3})\}/gi));
const lastMatch = matches.at(-1);
if (!lastMatch) {
@@ -263,7 +278,9 @@ function extractRoleToken(token: string, metadata: PlanningRowMetadata): string
return explicitRoleToken;
}
return deriveRoleTokens(metadata.chapter, metadata.typeOfWork, metadata.unitSpecificField)[0] ?? null;
return (
deriveRoleTokens(metadata.chapter, metadata.typeOfWork, metadata.unitSpecificField)[0] ?? null
);
}
function extractProjectKey(token: string): string | null {
@@ -466,9 +483,10 @@ function buildAvailabilityAccumulator(
} = {},
): AvailabilityAccumulator {
const percentage = input.percentage ?? parsePercentage(rawToken);
const availableHours = percentage !== null
? Math.round((percentage / 100) * 8 * 100) / 100
: (input.availableHours ?? (8 - SLOT_HOURS));
const availableHours =
percentage !== null
? Math.round((percentage / 100) * 8 * 100) / 100
: (input.availableHours ?? 8 - SLOT_HOURS);
const warnings = new Set<string>();
if (input.warning) {
warnings.add(input.warning);
@@ -548,7 +566,9 @@ export async function parseDispoPlanningWorkbook(
if (normalizedToken.startsWith("[_AB]")) {
const note = extractLabel(rawToken);
const vacationType = note?.toLowerCase().includes("sick") ? VacationType.SICK : VacationType.ANNUAL;
const vacationType = note?.toLowerCase().includes("sick")
? VacationType.SICK
: VacationType.ANNUAL;
const key = `${metadata.eid}|${getDateKey(column.assignmentDate)}|VAC|${rawToken}`;
const existing = vacations.get(key);
if (existing) {
@@ -577,11 +597,17 @@ export async function parseDispoPlanningWorkbook(
existing.halfDayParts.add(column.halfDayPart);
}
} else {
const vacation = buildVacationAccumulator(column, metadata, rawToken, VacationType.PUBLIC_HOLIDAY, {
holidayName,
isPublicHoliday: true,
note: holidayName,
});
const vacation = buildVacationAccumulator(
column,
metadata,
rawToken,
VacationType.PUBLIC_HOLIDAY,
{
holidayName,
isPublicHoliday: true,
note: holidayName,
},
);
vacation.sourceRow = rowNumber;
vacations.set(key, vacation);
}
@@ -595,7 +621,9 @@ export async function parseDispoPlanningWorkbook(
const existing = availabilityRules.get(key);
if (existing) {
const nextAvailability = buildAvailabilityAccumulator(column, metadata, rawToken, {
...(naHandling.availableHours !== undefined ? { availableHours: naHandling.availableHours } : {}),
...(naHandling.availableHours !== undefined
? { availableHours: naHandling.availableHours }
: {}),
...(naHandling.percentage !== undefined ? { percentage: naHandling.percentage } : {}),
...(naHandling.ruleType !== undefined ? { ruleType: naHandling.ruleType } : {}),
...(naHandling.warning !== undefined ? { warning: naHandling.warning } : {}),
@@ -607,7 +635,9 @@ export async function parseDispoPlanningWorkbook(
}
} else {
const availabilityRule = buildAvailabilityAccumulator(column, metadata, rawToken, {
...(naHandling.availableHours !== undefined ? { availableHours: naHandling.availableHours } : {}),
...(naHandling.availableHours !== undefined
? { availableHours: naHandling.availableHours }
: {}),
...(naHandling.percentage !== undefined ? { percentage: naHandling.percentage } : {}),
...(naHandling.ruleType !== undefined ? { ruleType: naHandling.ruleType } : {}),
...(naHandling.warning !== undefined ? { warning: naHandling.warning } : {}),
@@ -673,7 +703,8 @@ export async function parseDispoPlanningWorkbook(
resourceExternalId: metadata.eid,
projectKey: null,
message: `Unable to resolve project key from planning token "${rawToken}"`,
resolutionHint: "Add a WBS token or classify this cell as an internal bucket before commit",
resolutionHint:
"Add a WBS token or classify this cell as an internal bucket before commit",
warnings: Array.from(assignment.warnings),
normalizedData: {
assignmentDate: getDateKey(column.assignmentDate),
@@ -716,30 +747,32 @@ export async function parseDispoPlanningWorkbook(
}
}
const parsedAssignments: ParsedPlanningAssignment[] = Array.from(assignments.values()).map((entry) => ({
assignmentDate: entry.assignmentDate,
chapterToken: entry.chapterToken,
hoursPerDay: entry.hoursPerDay,
isInternal: entry.isInternal,
isTbd: entry.isTbd,
isUnassigned: entry.isUnassigned,
percentage: entry.slotCount * 50,
projectKey: entry.projectKey,
rawToken: entry.rawToken,
resourceExternalId: entry.resourceExternalId,
roleName: entry.roleName,
roleToken: entry.roleToken,
slotFraction: entry.slotCount / 2,
sourceColumn: toColumnLetter(entry.firstColumnNumber),
sourceRow: entry.sourceRow,
utilizationCategoryCode: entry.utilizationCategoryCode,
warnings: Array.from(entry.warnings),
winProbability: entry.winProbability,
}));
const parsedAssignments: ParsedPlanningAssignment[] = Array.from(assignments.values()).map(
(entry) => ({
assignmentDate: entry.assignmentDate,
chapterToken: entry.chapterToken,
hoursPerDay: entry.hoursPerDay,
isInternal: entry.isInternal,
isTbd: entry.isTbd,
isUnassigned: entry.isUnassigned,
percentage: entry.slotCount * 50,
projectKey: entry.projectKey,
rawToken: entry.rawToken,
resourceExternalId: entry.resourceExternalId,
roleName: entry.roleName,
roleToken: entry.roleToken,
slotFraction: entry.slotCount / 2,
sourceColumn: toColumnLetter(entry.firstColumnNumber),
sourceRow: entry.sourceRow,
utilizationCategoryCode: entry.utilizationCategoryCode,
warnings: Array.from(entry.warnings),
winProbability: entry.winProbability,
}),
);
const parsedVacations: ParsedPlanningVacation[] = Array.from(vacations.values()).map((entry) => ({
endDate: entry.endDate,
halfDayPart: entry.halfDayParts.size === 1 ? Array.from(entry.halfDayParts)[0] ?? null : null,
halfDayPart: entry.halfDayParts.size === 1 ? (Array.from(entry.halfDayParts)[0] ?? null) : null,
holidayName: entry.holidayName,
isHalfDay: entry.halfDayParts.size === 1,
isPublicHoliday: entry.isPublicHoliday,
@@ -753,7 +786,9 @@ export async function parseDispoPlanningWorkbook(
warnings: Array.from(entry.warnings),
}));
const parsedAvailabilityRules: ParsedPlanningAvailabilityRule[] = Array.from(availabilityRules.values()).map((entry) => ({
const parsedAvailabilityRules: ParsedPlanningAvailabilityRule[] = Array.from(
availabilityRules.values(),
).map((entry) => ({
availableHours: entry.availableHours,
effectiveEndDate: entry.effectiveEndDate,
effectiveStartDate: entry.effectiveStartDate,