chore(repo): checkpoint current capakraken implementation state

This commit is contained in:
2026-03-29 12:47:12 +02:00
parent beae1a5d6e
commit 47e4d701ff
94 changed files with 4283 additions and 1710 deletions
+3 -3
View File
@@ -86,11 +86,11 @@ test("assertDestructiveDbAllowed rejects missing destructive allow flag", () =>
);
});
test("assertSafeSeedTarget rejects legacy planarchy disposable databases", () => {
test("assertSafeSeedTarget rejects unexpected legacy disposable databases", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/planarchy_test",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/legacy_test",
ALLOW_DESTRUCTIVE_DB_TOOLS: "true",
CONFIRM_DESTRUCTIVE_DB_NAME: "planarchy_test",
CONFIRM_DESTRUCTIVE_DB_NAME: "legacy_test",
});
assert.throws(
+1 -1
View File
@@ -6,7 +6,7 @@ interface DestructiveGuardOptions {
requireConfirmation?: boolean;
}
const PROTECTED_DATABASE_NAMES = new Set(["capakraken", "planarchy"]);
const PROTECTED_DATABASE_NAMES = new Set(["capakraken"]);
function parseDatabaseUrl(rawUrl: string) {
const parsed = new URL(rawUrl);
+4 -4
View File
@@ -1,5 +1,5 @@
/**
* Generate samples/PlanarchyExamples.xlsx from the live database.
* Generate samples/CapaKrakenExamples.xlsx from the live database.
*
* Run from repo root:
* DATABASE_URL=postgresql://capakraken:capakraken_dev@localhost:5433/capakraken \
@@ -334,7 +334,7 @@ async function buildSummarySheet(wb: ExcelJS.Workbook) {
] as ExcelJS.Column[];
const title = ws.getCell("A1");
title.value = "Planarchy — Seed Data Summary";
title.value = "CapaKraken — Seed Data Summary";
title.font = { bold: true, size: 14, color: { argb: COLORS.headerBg } };
ws.mergeCells("A1:B1");
ws.getRow(1).height = 28;
@@ -367,7 +367,7 @@ async function main() {
console.log("Connecting to database...");
const wb = new ExcelJS.Workbook();
wb.creator = "Planarchy";
wb.creator = "CapaKraken";
wb.created = new Date();
wb.modified = new Date();
@@ -377,7 +377,7 @@ async function main() {
await buildProjectsSheet(wb);
await buildAllocationsSheet(wb);
const outPath = path.resolve(__dirname, "../../../samples/PlanarchyExamples.xlsx");
const outPath = path.resolve(__dirname, "../../../samples/CapaKrakenExamples.xlsx");
await wb.xlsx.writeFile(outPath);
console.log(`Excel written to: ${outPath}`);
}
+1 -1
View File
@@ -420,7 +420,7 @@ export async function runImportDispoBatch(options: ImportDispoBatchOptions) {
ensureCommitAllowed(options, stageResult.readiness);
console.log("");
console.log("Committing staged rows into live Planarchy tables...");
console.log("Committing staged rows into live CapaKraken tables...");
const commitResult = await dispoImport.commitDispoImportBatch(prisma, {
allowTbdUnresolved: options.allowTbdUnresolved,
+1 -1
View File
@@ -28,7 +28,7 @@ function parseArgs(argv: string[]): ResetOptions {
backupDir: DEFAULT_BACKUP_DIR,
adminEmail: "admin@capakraken.dev",
adminPassword: "admin123",
adminName: "Planarchy Admin",
adminName: "CapaKraken Admin",
};
for (let index = 0; index < argv.length; index += 1) {
+36 -36
View File
@@ -1,7 +1,7 @@
/**
* Updates PlanarchyExamples.xlsx with missing columns and documentation.
* Adds: Display Name, Email, Skills, Planarchy Notes columns to EID sheet.
* Adds: Start Date, End Date, Status, Planarchy Notes columns to Projects sheet.
* Updates CapaKrakenExamples.xlsx with missing columns and documentation.
* Adds: Display Name, Email, Skills, CapaKraken Notes columns to EID sheet.
* Adds: Start Date, End Date, Status, CapaKraken Notes columns to Projects sheet.
*/
import ExcelJS from "exceljs";
@@ -11,7 +11,7 @@ import { dirname, join } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const EXCEL_PATH = join(__dirname, "../../../samples/PlanarchyExamples.xlsx");
const EXCEL_PATH = join(__dirname, "../../../samples/CapaKrakenExamples.xlsx");
// ─── Helpers ─────────────────────────────────────────────────────────────────
@@ -23,7 +23,7 @@ function toDisplayName(eid) {
}
function toEmail(eid) {
return `${eid}@planarchy.example`;
return `${eid}@capakraken.example`;
}
function computeSkillLabel(chapter, typeOfWork) {
@@ -36,8 +36,8 @@ function computeSkillLabel(chapter, typeOfWork) {
return typeOfWork;
}
function computePlanarchyEid(eid) {
// In Planarchy the EID stays as firstname.lastname (unique key)
function computeCapaKrakenEid(eid) {
// In CapaKraken the EID stays as firstname.lastname (unique key)
return eid;
}
@@ -139,28 +139,28 @@ async function main() {
// Column N: Display Name
// Column O: Email
// Column P: Skills (derived)
// Column Q: Description / Notes for Planarchy
// Column Q: Description / Notes for CapaKraken
const newEidCols = [
{
col: 14, // N
header: "Display Name\n(auto-generated)",
doc: "Full display name derived from EID (firstname.lastname → Firstname Lastname). Used as the person's name in Planarchy.",
doc: "Full display name derived from EID (firstname.lastname → Firstname Lastname). Used as the person's name in CapaKraken.",
},
{
col: 15, // O
header: "Email\n(generated)",
doc: "Generated email: firstname.lastname@planarchy.example. Required unique field in Planarchy. Replace with real email in production.",
doc: "Generated email: firstname.lastname@capakraken.example. Required unique field in CapaKraken. Replace with real email in production.",
},
{
col: 16, // P
header: "Skills\n(derived from Chapter)",
doc: "Skill tags assigned based on Chapter + Type of Work. Format: 'SkillA | SkillB'. Stored as JSON array in Planarchy with proficiency 1-5. Senior (LCR 118) 5, Mid-Senior (LCR 95) 4, Mid 3.",
doc: "Skill tags assigned based on Chapter + Type of Work. Format: 'SkillA | SkillB'. Stored as JSON array in CapaKraken with proficiency 1-5. Senior (LCR >= 118) -> 5, Mid-Senior (LCR >= 95) -> 4, Mid -> 3.",
},
{
col: 17, // Q
header: "Planarchy Notes",
doc: "How data maps to Planarchy:\n EID = unique key (col A)\n Chapter = chapter field\n LCR / UCR multiply by 100 for integer cents (85.00 8500)\n Hours fraction × 8 = daily availability hours\n Chargeability multiply by 100 for % (0.75 75%)\n Employee type, City, Client Unit stored in dynamicFields JSONB",
header: "CapaKraken Notes",
doc: "How data maps to CapaKraken:\n- EID = unique key (col A)\n- Chapter = chapter field\n- LCR / UCR -> multiply by 100 for integer cents (EUR85.00 -> 8500)\n- Hours fraction x 8 = daily availability hours\n- Chargeability -> multiply by 100 for % (0.75 -> 75%)\n- Employee type, City, Client Unit -> stored in dynamicFields JSONB",
},
];
@@ -214,18 +214,18 @@ async function main() {
// Also add doc notes to existing header columns A-M in row 1
const eidExistingDocs = [
"Unique identifier. Used as EID in Planarchy (no EMP-XXX prefix needed). e.g. steve.rogers",
"Team / department. Maps to 'chapter' field in Planarchy.",
"Unique identifier. Used as EID in CapaKraken (no EMP-XXX prefix needed). e.g. steve.rogers",
"Team / department. Maps to 'chapter' field in CapaKraken.",
"Specialization within chapter. Stored in dynamicFields.workType.",
"Assigned client account. Stored in dynamicFields.clientUnit.",
"Unit-specific field. Currently unused — can be stored in dynamicFields.",
"Office city location. Stored in dynamicFields.city.",
"Employment type: Employee or Freelancer. Stored in dynamicFields.employeeType.",
"Loaded Cost Rate (LCR) in EUR/h. Multiply × 100 for Planarchy cents. e.g. 133.77 → 13377",
"Loaded Cost Rate (LCR) in EUR/h. Multiply × 100 for CapaKraken cents. e.g. 133.77 → 13377",
"Unloaded/Utilization Cost Rate (UCR) in EUR/h. Multiply × 100 for cents.",
"FTE fraction (1.0 = 40h/week, 0.8 = 4 days, 0.5 = 20h/week). Combined with col K for availability JSON.",
"Available weekdays. 'all' = Mon-Fri. Specific days listed = only those days active at 8h.",
"Chargeability target as decimal. Multiply × 100 for Planarchy % (0.75 → 75%).",
"Chargeability target as decimal. Multiply × 100 for CapaKraken % (0.75 → 75%).",
"(unused)",
];
for (let c = 1; c <= 13; c++) {
@@ -254,12 +254,12 @@ async function main() {
{
col: 19, // S
header: "Status\n(derived)",
doc: "Planarchy status derived from 'is ordered' + win probability + date:\n• COMPLETED: ordered + 100% + past dates\n• ACTIVE: ordered + 100% + current/future\n• ON_HOLD: ordered but paused\n• DRAFT: not ordered or low win probability",
doc: "CapaKraken status derived from 'is ordered' + win probability + date:\n• COMPLETED: ordered + 100% + past dates\n• ACTIVE: ordered + 100% + current/future\n• ON_HOLD: ordered but paused\n• DRAFT: not ordered or low win probability",
},
{
col: 20, // T
header: "Planarchy Notes",
doc: "How data maps to Planarchy:\n• Col C (short code) → shortCode (unique key)\n• Col B → name\n• BD/CH/UN → OrderType: BD / CHARGEABLE / INTERNAL\n• Internal/External → allocationType: INT / EXT\n• Resource Costs (col I) × 100 = budgetCents in Planarchy\n• Col H (chargability %) → stored in dynamicFields.chargeabilityPercent\n• Col J (person hours) → stored in dynamicFields.personHoursSold\n• Col O (classification) → stored in dynamicFields.classification",
header: "CapaKraken Notes",
doc: "How data maps to CapaKraken:\n• Col C (short code) → shortCode (unique key)\n• Col B → name\n• BD/CH/UN → OrderType: BD / CHARGEABLE / INTERNAL\n• Internal/External → allocationType: INT / EXT\n• Resource Costs (col I) × 100 = budgetCents in CapaKraken\n• Col H (chargability %) → stored in dynamicFields.chargeabilityPercent\n• Col J (person hours) → stored in dynamicFields.personHoursSold\n• Col O (classification) → stored in dynamicFields.classification",
},
];
@@ -286,21 +286,21 @@ async function main() {
const projExistingDocs = [
"Client Unit tag. e.g. [DAI]=Daimler, [PAG]=Porsche AG, [BMW], [JLR]=Jaguar Land Rover. Stored in dynamicFields.clientUnit.",
"Full project name → maps to Planarchy 'name' field.",
"Short internal project code (5-6 chars) → maps to Planarchy 'shortCode' (unique key). e.g. JLFJFL",
"Full project name → maps to CapaKraken 'name' field.",
"Short internal project code (5-6 chars) → maps to CapaKraken 'shortCode' (unique key). e.g. JLFJFL",
"'yes'/'no' — whether the project is formally ordered. Drives status: yes+100% → ACTIVE/COMPLETED.",
"Order type: BD=Business Development, CH=Chargeable, UN=Internal/Unordered. Maps to Planarchy OrderType.",
"Win probability 0-100. Used in Planarchy 'winProbability' field for pipeline forecasting.",
"Allocation type: Internal → INT, External → EXT. Maps to Planarchy 'allocationType' field.",
"Order type: BD=Business Development, CH=Chargeable, UN=Internal/Unordered. Maps to CapaKraken OrderType.",
"Win probability 0-100. Used in CapaKraken 'winProbability' field for pipeline forecasting.",
"Allocation type: Internal → INT, External → EXT. Maps to CapaKraken 'allocationType' field.",
"Chargeability % as decimal. Stored in dynamicFields.chargeabilityPercent.",
"Planned resource cost in EUR. Multiply × 100 for Planarchy budgetCents. e.g. 78799 → 7879900 cents.",
"Planned resource cost in EUR. Multiply × 100 for CapaKraken budgetCents. e.g. 78799 → 7879900 cents.",
"Person hours planned/sold. Stored in dynamicFields.personHoursSold for budget tracking.",
"Team staffing (empty in source). Would list assigned EIDs. Handled by Allocations in Planarchy.",
"Team staffing (empty in source). Would list assigned EIDs. Handled by Allocations in CapaKraken.",
"Day project sold (empty in source). Could be stored as dynamicFields.dateSold.",
"Project start date (empty in source → synthesized in col Q). Maps to Planarchy 'startDate'.",
"Project end date (empty in source → synthesized in col R). Maps to Planarchy 'endDate'.",
"Project start date (empty in source → synthesized in col Q). Maps to CapaKraken 'startDate'.",
"Project end date (empty in source → synthesized in col R). Maps to CapaKraken 'endDate'.",
"Confidentiality: Confidential / Not Confidential. Stored in dynamicFields.classification.",
"Responsible EID / Owner (empty in source). Would map to a PM allocation in Planarchy.",
"Responsible EID / Owner (empty in source). Would map to a PM allocation in CapaKraken.",
];
for (let c = 1; c <= 16; c++) {
const doc = projExistingDocs[c - 1];
@@ -329,15 +329,15 @@ async function main() {
projSheet.getColumn(19).width = 16;
projSheet.getColumn(20).width = 50;
// ─── Add a "Planarchy Data Model" sheet ──────────────────────────────────
// ─── Add a "CapaKraken Data Model" sheet ──────────────────────────────────
let modelSheet = workbook.getWorksheet("Planarchy Data Model");
let modelSheet = workbook.getWorksheet("CapaKraken Data Model");
if (!modelSheet) {
modelSheet = workbook.addWorksheet("Planarchy Data Model");
modelSheet = workbook.addWorksheet("CapaKraken Data Model");
}
const modelData = [
["Planarchy Data Model — Field Reference", "", "", "", ""],
["CapaKraken Data Model — Field Reference", "", "", "", ""],
["", "", "", "", ""],
["RESOURCE FIELDS", "", "", "", ""],
["Field", "Type", "Required", "Example", "Description"],
@@ -393,7 +393,7 @@ async function main() {
// Style title row
const titleCell = modelSheet.getCell(1, 1);
titleCell.font = { bold: true, size: 14, color: { argb: "FF4F46E5" } };
titleCell.value = "Planarchy Data Model — Field Reference";
titleCell.value = "CapaKraken Data Model — Field Reference";
// Style section headers and field headers
const sectionRows = [3, 17, 31];
@@ -421,7 +421,7 @@ async function main() {
console.log(`✅ Excel updated: ${EXCEL_PATH}`);
console.log(" - EID_Informationen: added Display Name, Email, Skills, Notes columns");
console.log(" - Projektinfomartionen: added Start Date, End Date, Status, Notes columns");
console.log(" - Added new sheet: 'Planarchy Data Model' (field reference)");
console.log(" - Added new sheet: 'CapaKraken Data Model' (field reference)");
}
main().catch((err) => {