431 lines
20 KiB
JavaScript
431 lines
20 KiB
JavaScript
/**
|
||
* 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";
|
||
import { fileURLToPath } from "url";
|
||
import { dirname, join } from "path";
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
|
||
const EXCEL_PATH = join(__dirname, "../../../samples/CapaKrakenExamples.xlsx");
|
||
|
||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||
|
||
function toDisplayName(eid) {
|
||
return eid
|
||
.split(".")
|
||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
||
.join(" ");
|
||
}
|
||
|
||
function toEmail(eid) {
|
||
return `${eid}@capakraken.example`;
|
||
}
|
||
|
||
function computeSkillLabel(chapter, typeOfWork) {
|
||
if (chapter === "Project Management") return "Project Manager | Scrum Master";
|
||
if (chapter === "Digital Content Production") return "Unreal Engine | 3D Lighting | 3D Modeling";
|
||
if (chapter === "Product Data Management") return "Visualization Logic | Quality Assurance";
|
||
if (chapter === "Art Direction") return "Art Direction | 2D Compositing";
|
||
if (chapter === "CGI-Dev" && typeOfWork === "Dev Unreal") return "Unreal Dev | Technical Architect";
|
||
if (chapter === "CGI-Dev" && typeOfWork === "Dev Frontend") return "Frontend Dev | Backend Dev";
|
||
return typeOfWork;
|
||
}
|
||
|
||
function computeCapaKrakenEid(eid) {
|
||
// In CapaKraken the EID stays as firstname.lastname (unique key)
|
||
return eid;
|
||
}
|
||
|
||
// Project date table (matches seed.ts)
|
||
const PROJECT_DATES = {
|
||
JLFJFL: ["2024-01-15", "2024-05-31", "COMPLETED"],
|
||
JZTFG: ["2024-02-01", "2024-09-30", "COMPLETED"],
|
||
UZGHD: ["2024-03-01", "2024-07-31", "COMPLETED"],
|
||
DSFGHF: ["2024-04-01", "2024-12-31", "COMPLETED"],
|
||
TZUZG: ["2024-05-15", "2025-01-31", "COMPLETED"],
|
||
KLJLOH: ["2024-06-01", "2024-10-31", "COMPLETED"],
|
||
FGHJJ: ["2024-07-01", "2025-01-31", "COMPLETED"],
|
||
FFGHG: ["2024-08-01", "2024-12-31", "COMPLETED"],
|
||
ARGFH: ["2024-09-01", "2025-03-31", "COMPLETED"],
|
||
ERTGR: ["2024-10-01", "2025-01-31", "COMPLETED"],
|
||
HFGRT: ["2024-11-01", "2025-06-30", "COMPLETED"],
|
||
WERTF: ["2024-12-01", "2025-04-30", "COMPLETED"],
|
||
RDFGH: ["2025-01-15", "2025-08-31", "ACTIVE"],
|
||
HJZGJ: ["2025-02-01", "2025-10-31", "ACTIVE"],
|
||
SRTZRT: ["2025-03-01", "2025-09-30", "DRAFT"],
|
||
STTHG: ["2025-04-01", "2026-01-31", "ACTIVE"],
|
||
EDDGG: ["2025-05-01", "2025-12-31", "ACTIVE"],
|
||
FGHHGF: ["2025-06-01", "2026-03-31", "ACTIVE"],
|
||
AFDGH: ["2025-07-01", "2025-12-31", "ACTIVE"],
|
||
HTZUG: ["2025-08-01", "2026-04-30", "ON_HOLD"],
|
||
MUIUF: ["2025-09-01", "2026-06-30", "ACTIVE"],
|
||
FGJUO: ["2025-10-01", "2026-02-28", "ACTIVE"],
|
||
ZUITJZ: ["2025-11-01", "2026-08-31", "ACTIVE"],
|
||
SACVFH: ["2025-12-01", "2026-06-30", "ACTIVE"],
|
||
EWFSFH: ["2026-01-15", "2026-09-30", "ACTIVE"],
|
||
NTZJJ: ["2026-02-01", "2026-10-31", "ACTIVE"],
|
||
EFGTZ: ["2026-03-01", "2026-12-31", "ACTIVE"],
|
||
SJUIL: ["2026-04-01", "2026-11-30", "DRAFT"],
|
||
SAERZ: ["2026-05-01", "2027-01-31", "DRAFT"],
|
||
HJUOP: ["2026-06-01", "2027-03-31", "ACTIVE"],
|
||
AERGH: ["2026-07-01", "2026-12-31", "DRAFT"],
|
||
POUILZ: ["2026-08-01", "2027-02-28", "DRAFT"],
|
||
EWERZ: ["2026-09-01", "2027-06-30", "DRAFT"],
|
||
HZIOP: ["2026-10-01", "2027-04-30", "DRAFT"],
|
||
DGHBN: ["2026-11-01", "2027-05-31", "DRAFT"],
|
||
KUOOL: ["2026-12-01", "2027-09-30", "ACTIVE"],
|
||
};
|
||
|
||
const HEADER_FILL = {
|
||
type: "pattern",
|
||
pattern: "solid",
|
||
fgColor: { argb: "FF4F46E5" }, // indigo-600
|
||
};
|
||
const HEADER_FONT = { bold: true, color: { argb: "FFFFFFFF" }, size: 10 };
|
||
const DOC_FILL = {
|
||
type: "pattern",
|
||
pattern: "solid",
|
||
fgColor: { argb: "FFF0F9FF" }, // light blue
|
||
};
|
||
const DOC_FONT = { italic: true, color: { argb: "FF475569" }, size: 9 };
|
||
const NEW_FILL = {
|
||
type: "pattern",
|
||
pattern: "solid",
|
||
fgColor: { argb: "FFEEF2FF" }, // indigo-50
|
||
};
|
||
|
||
function styleNewHeader(cell, label) {
|
||
cell.value = label;
|
||
cell.fill = HEADER_FILL;
|
||
cell.font = HEADER_FONT;
|
||
cell.alignment = { wrapText: true, vertical: "middle" };
|
||
cell.border = { bottom: { style: "thin", color: { argb: "FF6366F1" } } };
|
||
}
|
||
|
||
function styleDocCell(cell, value) {
|
||
cell.value = value;
|
||
cell.fill = DOC_FILL;
|
||
cell.font = DOC_FONT;
|
||
cell.alignment = { wrapText: true, vertical: "top" };
|
||
}
|
||
|
||
function styleDataCell(cell, value) {
|
||
cell.value = value;
|
||
cell.fill = NEW_FILL;
|
||
cell.font = { size: 10 };
|
||
cell.alignment = { wrapText: false, vertical: "middle" };
|
||
}
|
||
|
||
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||
|
||
async function main() {
|
||
const workbook = new ExcelJS.Workbook();
|
||
await workbook.xlsx.readFile(EXCEL_PATH);
|
||
|
||
// ─── EID_Informationen ───────────────────────────────────────────────────
|
||
|
||
const eidSheet = workbook.getWorksheet("EID_Informationen");
|
||
if (!eidSheet) throw new Error("Sheet EID_Informationen not found");
|
||
|
||
// Add a "Documentation" row 2 below headers explaining each column
|
||
// First, let's add new columns to the right: N, O, P, Q
|
||
const eidLastCol = 14; // N
|
||
|
||
// Column N: Display Name
|
||
// Column O: Email
|
||
// Column P: Skills (derived)
|
||
// 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 CapaKraken.",
|
||
},
|
||
{
|
||
col: 15, // O
|
||
header: "Email\n(generated)",
|
||
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 CapaKraken with proficiency 1-5. Senior (LCR >= 118) -> 5, Mid-Senior (LCR >= 95) -> 4, Mid -> 3.",
|
||
},
|
||
{
|
||
col: 17, // Q
|
||
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",
|
||
},
|
||
];
|
||
|
||
// Add row 1 column headers for new columns
|
||
const eidHeaderRow = eidSheet.getRow(1);
|
||
for (const { col, header } of newEidCols) {
|
||
styleNewHeader(eidSheet.getCell(1, col), header);
|
||
}
|
||
|
||
// Add documentation row after header (insert at row 2, shifting data down)
|
||
eidSheet.spliceRows(2, 0, []); // insert empty row
|
||
const eidDocRow = eidSheet.getRow(2);
|
||
eidDocRow.height = 55;
|
||
// Add doc text to new columns
|
||
for (const { col, doc } of newEidCols) {
|
||
styleDocCell(eidSheet.getCell(2, col), doc);
|
||
}
|
||
// Style existing header cells for doc row
|
||
for (let c = 1; c <= 13; c++) {
|
||
const cell = eidSheet.getCell(2, c);
|
||
cell.fill = DOC_FILL;
|
||
cell.font = DOC_FONT;
|
||
cell.alignment = { wrapText: true, vertical: "top" };
|
||
}
|
||
eidSheet.getCell(2, 1).value = "← Column documentation (row 2)";
|
||
|
||
// Now fill data rows (row 3 onward = data rows 1-36)
|
||
for (let r = 3; r <= 38; r++) {
|
||
const row = eidSheet.getRow(r);
|
||
const eid = row.getCell(1).value?.toString().trim();
|
||
if (!eid) continue;
|
||
|
||
const chapter = row.getCell(2).value?.toString().trim() ?? "";
|
||
const typeOfWork = row.getCell(3).value?.toString().trim() ?? "";
|
||
const lcr = parseFloat(row.getCell(8).value?.toString() ?? "0");
|
||
|
||
styleDataCell(eidSheet.getCell(r, 14), toDisplayName(eid));
|
||
styleDataCell(eidSheet.getCell(r, 15), toEmail(eid));
|
||
styleDataCell(eidSheet.getCell(r, 16), computeSkillLabel(chapter, typeOfWork));
|
||
|
||
if (r === 3) {
|
||
styleDataCell(eidSheet.getCell(r, 17), "See header row notes →");
|
||
}
|
||
}
|
||
|
||
// Set column widths
|
||
eidSheet.getColumn(14).width = 22;
|
||
eidSheet.getColumn(15).width = 34;
|
||
eidSheet.getColumn(16).width = 36;
|
||
eidSheet.getColumn(17).width = 50;
|
||
|
||
// Also add doc notes to existing header columns A-M in row 1
|
||
const eidExistingDocs = [
|
||
"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 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 CapaKraken % (0.75 → 75%).",
|
||
"(unused)",
|
||
];
|
||
for (let c = 1; c <= 13; c++) {
|
||
const doc = eidExistingDocs[c - 1];
|
||
if (doc) {
|
||
eidSheet.getCell(2, c).value = doc;
|
||
}
|
||
}
|
||
|
||
// ─── Projektinfomartionen ────────────────────────────────────────────────
|
||
|
||
const projSheet = workbook.getWorksheet("Projektinfomartionen");
|
||
if (!projSheet) throw new Error("Sheet Projektinfomartionen not found");
|
||
|
||
const newProjCols = [
|
||
{
|
||
col: 17, // Q
|
||
header: "Start Date\n(synthesized)",
|
||
doc: "Synthesized project start date (YYYY-MM-DD). Source Excel had no dates — spread across 2024-2027 based on win probability and project order.",
|
||
},
|
||
{
|
||
col: 18, // R
|
||
header: "End Date\n(synthesized)",
|
||
doc: "Synthesized end date (YYYY-MM-DD). Duration based on resource costs planned and person hours. Ranges from 3-12 months.",
|
||
},
|
||
{
|
||
col: 19, // S
|
||
header: "Status\n(derived)",
|
||
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: "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",
|
||
},
|
||
];
|
||
|
||
// Add header row columns
|
||
const projHeaderRow = projSheet.getRow(1);
|
||
for (const { col, header } of newProjCols) {
|
||
styleNewHeader(projSheet.getCell(1, col), header);
|
||
}
|
||
|
||
// Insert documentation row 2
|
||
projSheet.spliceRows(2, 0, []);
|
||
const projDocRow = projSheet.getRow(2);
|
||
projDocRow.height = 55;
|
||
for (const { col, doc } of newProjCols) {
|
||
styleDocCell(projSheet.getCell(2, col), doc);
|
||
}
|
||
projSheet.getCell(2, 1).value = "← Column documentation (row 2)";
|
||
for (let c = 1; c <= 16; c++) {
|
||
const cell = projSheet.getCell(2, c);
|
||
cell.fill = DOC_FILL;
|
||
cell.font = DOC_FONT;
|
||
cell.alignment = { wrapText: true, vertical: "top" };
|
||
}
|
||
|
||
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 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 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 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 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 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 CapaKraken.",
|
||
];
|
||
for (let c = 1; c <= 16; c++) {
|
||
const doc = projExistingDocs[c - 1];
|
||
if (doc) projSheet.getCell(2, c).value = doc;
|
||
}
|
||
|
||
// Fill data rows
|
||
for (let r = 3; r <= 38; r++) {
|
||
const row = projSheet.getRow(r);
|
||
const shortCode = row.getCell(3).value?.toString().trim();
|
||
if (!shortCode) continue;
|
||
|
||
const dates = PROJECT_DATES[shortCode];
|
||
if (dates) {
|
||
styleDataCell(projSheet.getCell(r, 17), dates[0]);
|
||
styleDataCell(projSheet.getCell(r, 18), dates[1]);
|
||
styleDataCell(projSheet.getCell(r, 19), dates[2]);
|
||
}
|
||
if (r === 3) {
|
||
styleDataCell(projSheet.getCell(r, 20), "See header row notes →");
|
||
}
|
||
}
|
||
|
||
projSheet.getColumn(17).width = 18;
|
||
projSheet.getColumn(18).width = 18;
|
||
projSheet.getColumn(19).width = 16;
|
||
projSheet.getColumn(20).width = 50;
|
||
|
||
// ─── Add a "CapaKraken Data Model" sheet ──────────────────────────────────
|
||
|
||
let modelSheet = workbook.getWorksheet("CapaKraken Data Model");
|
||
if (!modelSheet) {
|
||
modelSheet = workbook.addWorksheet("CapaKraken Data Model");
|
||
}
|
||
|
||
const modelData = [
|
||
["CapaKraken Data Model — Field Reference", "", "", "", ""],
|
||
["", "", "", "", ""],
|
||
["RESOURCE FIELDS", "", "", "", ""],
|
||
["Field", "Type", "Required", "Example", "Description"],
|
||
["eid", "String (unique)", "Yes", "steve.rogers", "Employee identifier. Must be unique. Shown in UI as EID badge."],
|
||
["displayName", "String", "Yes", "Steve Rogers", "Full display name shown in all views."],
|
||
["email", "String (unique)", "Yes", "steve.rogers@company.com", "Unique email address. Used for login if resource is also a user."],
|
||
["chapter", "String", "No", "Project Management", "Team or department. Used for grouping in dashboard and filters."],
|
||
["lcrCents", "Integer (cents)", "Yes", "13377", "Loaded Cost Rate in euro-cents (€/h × 100). e.g. €133.77/h → 13377"],
|
||
["ucrCents", "Integer (cents)", "Yes", "7465", "Unloaded/Utilization Cost Rate in euro-cents. e.g. €74.65/h → 7465"],
|
||
["currency", "String", "Yes", "EUR", "Currency code for LCR/UCR. Default EUR."],
|
||
["chargeabilityTarget", "Float (0-100)", "Yes", "75", "Target chargeability percentage. 75 = 75% of available hours should be chargeable."],
|
||
["availability", "JSON", "Yes", "{monday:8, ...}", "Available hours per weekday. Partial days for part-time. e.g. 4h = half day."],
|
||
["skills", "JSON Array", "No", "[{skill:'Unreal Engine', proficiency:4}]", "Skill entries with proficiency 1(Junior)–5(Expert). Optional category and yearsExperience."],
|
||
["isActive", "Boolean", "Yes", "true", "Active resources appear in planning. Deactivated resources are hidden but kept in history."],
|
||
["dynamicFields", "JSON", "No", "{city:'Stuttgart', ...}", "Additional fields defined by Blueprint (e.g. city, clientUnit, workType, employeeType)."],
|
||
["", "", "", "", ""],
|
||
["PROJECT FIELDS", "", "", "", ""],
|
||
["Field", "Type", "Required", "Example", "Description"],
|
||
["shortCode", "String (unique)", "Yes", "STTHG", "Short internal project code. Unique key. Shown as monospace badge in UI."],
|
||
["name", "String", "Yes", "Spider-Man: Homecoming (2017)", "Full project name."],
|
||
["orderType", "Enum", "Yes", "CHARGEABLE", "BD = Business Development, CHARGEABLE = billable client work, INTERNAL = internal, OVERHEAD = overhead."],
|
||
["allocationType", "Enum", "Yes", "EXT", "INT = Internal allocation (internal team), EXT = External (client-facing)."],
|
||
["winProbability", "Integer (0-100)", "Yes", "100", "Probability this project proceeds. Used in pipeline reports. 100 = confirmed."],
|
||
["budgetCents", "Integer (cents)", "Yes", "6789900", "Total project budget in euro-cents. e.g. €67,899 → 6789900. Used for utilization tracking."],
|
||
["startDate", "Date", "Yes", "2025-04-01", "Project start date (ISO format)."],
|
||
["endDate", "Date", "Yes", "2026-01-31", "Project end date (ISO format)."],
|
||
["status", "Enum", "Yes", "ACTIVE", "DRAFT / ACTIVE / ON_HOLD / COMPLETED / CANCELLED. Drives timeline filtering."],
|
||
["staffingReqs", "JSON Array", "No", "[{role:'3D Artist', fteCount:2}]", "Staffing requirements. Used in Demand View widget to show gaps vs actual allocations."],
|
||
["dynamicFields", "JSON", "No", "{clientUnit:'[DAI]', ...}", "Blueprint fields: clientUnit, personHoursSold, classification, etc."],
|
||
["", "", "", "", ""],
|
||
["ALLOCATION FIELDS", "", "", "", ""],
|
||
["Field", "Type", "Required", "Example", "Description"],
|
||
["resourceId", "String (FK)", "Yes", "(cuid)", "Reference to Resource.id"],
|
||
["projectId", "String (FK)", "Yes", "(cuid)", "Reference to Project.id"],
|
||
["startDate", "Date", "Yes", "2025-04-01", "Start of allocation (often same as project start)."],
|
||
["endDate", "Date", "Yes", "2026-01-31", "End of allocation (can be partial project duration)."],
|
||
["hoursPerDay", "Float", "Yes", "8", "Hours per working day. 8 = full day. Can exceed 8 (overtime, shown in amber)."],
|
||
["percentage", "Float (0-100)", "Yes", "100", "Allocation percentage (100 = full-time, 50 = half-time)."],
|
||
["role", "String", "Yes", "Project Manager", "Role on this project (free text)."],
|
||
["dailyCostCents", "Integer (cents)", "Yes", "106960", "LCR × hoursPerDay × 100. Computed automatically on create."],
|
||
["status", "Enum", "Yes", "ACTIVE", "PROPOSED / CONFIRMED / ACTIVE / COMPLETED / CANCELLED"],
|
||
];
|
||
|
||
modelSheet.addRows(modelData);
|
||
|
||
// Style the model sheet
|
||
modelSheet.getColumn(1).width = 28;
|
||
modelSheet.getColumn(2).width = 22;
|
||
modelSheet.getColumn(3).width = 12;
|
||
modelSheet.getColumn(4).width = 35;
|
||
modelSheet.getColumn(5).width = 72;
|
||
|
||
// Style title row
|
||
const titleCell = modelSheet.getCell(1, 1);
|
||
titleCell.font = { bold: true, size: 14, color: { argb: "FF4F46E5" } };
|
||
titleCell.value = "CapaKraken Data Model — Field Reference";
|
||
|
||
// Style section headers and field headers
|
||
const sectionRows = [3, 17, 31];
|
||
const fieldHeaderRows = [4, 18, 32];
|
||
|
||
for (const r of sectionRows) {
|
||
for (let c = 1; c <= 5; c++) {
|
||
const cell = modelSheet.getCell(r, c);
|
||
cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FF4F46E5" } };
|
||
cell.font = { bold: true, color: { argb: "FFFFFFFF" }, size: 11 };
|
||
}
|
||
}
|
||
|
||
for (const r of fieldHeaderRows) {
|
||
for (let c = 1; c <= 5; c++) {
|
||
const cell = modelSheet.getCell(r, c);
|
||
cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFE0E7FF" } };
|
||
cell.font = { bold: true, color: { argb: "FF3730A3" }, size: 10 };
|
||
}
|
||
}
|
||
|
||
// ─── Save ────────────────────────────────────────────────────────────────
|
||
|
||
await workbook.xlsx.writeFile(EXCEL_PATH);
|
||
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: 'CapaKraken Data Model' (field reference)");
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|