chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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/PlanarchyExamples.xlsx");
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function toDisplayName(eid) {
|
||||
return eid
|
||||
.split(".")
|
||||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function toEmail(eid) {
|
||||
return `${eid}@planarchy.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 computePlanarchyEid(eid) {
|
||||
// In Planarchy 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 Planarchy
|
||||
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
// 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 Planarchy (no EMP-XXX prefix needed). e.g. steve.rogers",
|
||||
"Team / department. Maps to 'chapter' field in Planarchy.",
|
||||
"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",
|
||||
"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%).",
|
||||
"(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: "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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
// 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 Planarchy 'name' field.",
|
||||
"Short internal project code (5-6 chars) → maps to Planarchy '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.",
|
||||
"Chargeability % as decimal. Stored in dynamicFields.chargeabilityPercent.",
|
||||
"Planned resource cost in EUR. Multiply × 100 for Planarchy 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.",
|
||||
"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'.",
|
||||
"Confidentiality: Confidential / Not Confidential. Stored in dynamicFields.classification.",
|
||||
"Responsible EID / Owner (empty in source). Would map to a PM allocation in Planarchy.",
|
||||
];
|
||||
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 "Planarchy Data Model" sheet ──────────────────────────────────
|
||||
|
||||
let modelSheet = workbook.getWorksheet("Planarchy Data Model");
|
||||
if (!modelSheet) {
|
||||
modelSheet = workbook.addWorksheet("Planarchy Data Model");
|
||||
}
|
||||
|
||||
const modelData = [
|
||||
["Planarchy 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 = "Planarchy 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: 'Planarchy Data Model' (field reference)");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user