import { AllocationStatus, AllocationType, BlueprintTarget, FieldType, OrderType, ProjectStatus, ResourceType, SystemRole, VacationStatus, VacationType, } from "@capakraken/shared"; import { PrismaClient, type Prisma, type Resource, type Project } from "@prisma/client"; import { hash } from "@node-rs/argon2"; import { getHolidayDemoProfileForIndex } from "./holiday-demo-profiles.js"; import { loadWorkspaceEnv } from "./load-workspace-env.js"; import { assertSafeSeedTarget } from "./safe-destructive-env.js"; loadWorkspaceEnv(); const prisma = new PrismaClient(); // ─── Skill helpers ───────────────────────────────────────────────────────────── interface SkillEntry { skill: string; proficiency: number; category: string; } function computeProficiency(lcr: number): number { if (lcr >= 118) return 5; if (lcr >= 95) return 4; return 3; } function computeSkills(chapter: string, typeOfWork: string, lcr: number): SkillEntry[] { const prof = computeProficiency(lcr); const p2 = Math.max(2, prof - 1); if (chapter === "Project Management" && typeOfWork === "Project Management") { return [ { skill: "Project Manager", proficiency: prof, category: "PM" }, { skill: "Scrum Master", proficiency: p2, category: "PM" }, ]; } if (chapter === "Digital Content Production" && typeOfWork === "Unreal Engine Layerstack") { return [ { skill: "Unreal Engine", proficiency: prof, category: "3D" }, { skill: "3D Lighting", proficiency: p2, category: "3D" }, { skill: "3D Modeling", proficiency: p2, category: "3D" }, ]; } if (chapter === "Product Data Management" && typeOfWork === "Vis Logic") { return [ { skill: "Visualization Logic", proficiency: prof, category: "PDM" }, { skill: "Quality Assurance", proficiency: p2, category: "PDM" }, ]; } if (chapter === "Art Direction" && typeOfWork === "Art Direction") { return [ { skill: "Art Direction", proficiency: prof, category: "Art" }, { skill: "2D Compositing", proficiency: p2, category: "Art" }, ]; } if (chapter === "CGI-Dev" && typeOfWork === "Dev Unreal") { return [ { skill: "Unreal Dev", proficiency: prof, category: "Dev" }, { skill: "Technical Architect", proficiency: p2, category: "Dev" }, ]; } if (chapter === "CGI-Dev" && typeOfWork === "Dev Frontend") { return [ { skill: "Frontend Dev", proficiency: prof, category: "Dev" }, { skill: "Backend Dev", proficiency: p2, category: "Dev" }, ]; } return [{ skill: typeOfWork, proficiency: prof, category: chapter }]; } // ─── Availability helpers ─────────────────────────────────────────────────────── interface WeekdayAvailability { monday: number; tuesday: number; wednesday: number; thursday: number; friday: number; } function computeAvailability(fraction: number, availDays: string): WeekdayAvailability { const full = 8; const half = Math.round(full * fraction); if (availDays === "all") { if (fraction === 1.0) { return { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }; } if (fraction === 0.5) { return { monday: 4, tuesday: 4, wednesday: 4, thursday: 4, friday: 4 }; } if (fraction === 0.8) { return { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 0 }; } return { monday: half, tuesday: half, wednesday: half, thursday: half, friday: half }; } if (availDays === "tue,wed,thu,fri") { return { monday: 0, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }; } if (availDays === "mon,tue,thu,fri") { return { monday: 8, tuesday: 8, wednesday: 0, thursday: 8, friday: 8 }; } if (availDays === "mon,tue,wed,thu") { return { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 0 }; } return { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }; } // ─── Resource data ───────────────────────────────────────────────────────────── // [eid, chapter, typeOfWork, clientUnit, city, employeeType, lcr, ucr, fraction, availDays, chargeability] type ResourceRow = [ string, string, string, string, string, string, number, number, number, string, number, ]; const RESOURCE_DATA: ResourceRow[] = [ // ── Project Management ── ["steve.rogers", "Project Management", "Project Management", "Porsche AG", "Stuttgart", "Employee", 133.77, 74.65, 1.0, "all", 0.75], ["bruce.banner", "Project Management", "Project Management", "Daimler", "Stuttgart", "Employee", 118.35, 88.52, 1.0, "all", 0.75], ["natasha.romanoff","Project Management", "Project Management", "Italy (FER/MAS)", "Stuttgart", "Employee", 133.77, 74.65, 1.0, "all", 1.0 ], ["charles.xavier", "Project Management", "Project Management", "Moving Image", "Muenchen", "Employee", 96.77, 65.24, 1.0, "all", 0.75], ["scott.summers", "Project Management", "Project Management", "Moving Image", "Stuttgart", "Employee", 65.00, 88.52, 0.8, "mon,tue,thu,fri", 0.75], ["ororo.munroe", "Project Management", "Project Management", "Moving Image", "Stuttgart", "Employee", 118.35, 88.52, 1.0, "all", 0.75], ["gamora.gamora", "Project Management", "Project Management", "Moving Image", "Hamburg", "Employee", 67.00, 88.52, 0.8, "mon,tue,wed,thu", 1.0 ], // ── 3D / Digital Content Production ── ["tony.stark", "Digital Content Production","Unreal Engine Layerstack", "Italy (FER/MAS)", "Hamburg", "Employee", 96.77, 65.24, 1.0, "all", 0.75], ["thor.odinson", "Digital Content Production","Unreal Engine Layerstack", "Daimler", "Stuttgart", "Employee", 76.64, 55.98, 1.0, "all", 0.75], ["sam.wilson", "Digital Content Production","Unreal Engine Layerstack", "Porsche AG", "Stuttgart", "Employee", 133.77, 74.65, 1.0, "all", 0.75], ["james.barnes", "Digital Content Production","Unreal Engine Layerstack", "Porsche AG", "Hamburg", "Employee", 118.35, 88.52, 1.0, "all", 0.75], ["wanda.maximoff", "Digital Content Production","Unreal Engine Layerstack", "BMW", "Stuttgart", "Employee", 76.64, 55.98, 0.8, "tue,wed,thu,fri", 0.75], ["peter.parker", "Digital Content Production","Unreal Engine Layerstack", "BMW", "Hamburg", "Employee", 96.77, 65.24, 1.0, "all", 0.75], ["miles.morales", "Digital Content Production","Unreal Engine Layerstack", "Cross-Unit", "Stuttgart", "Employee", 76.64, 55.98, 1.0, "all", 0.86], ["jessica.drew", "Digital Content Production","Unreal Engine Layerstack", "BMW", "Stuttgart", "Employee", 76.64, 55.98, 1.0, "all", 0.75], ["logan.howlett", "Digital Content Production","Unreal Engine Layerstack", "BMW", "Hamburg", "Employee", 66.67, 49.18, 1.0, "all", 0.75], ["erik.lehnsherr", "Digital Content Production","Unreal Engine Layerstack", "Daimler", "Hamburg", "Employee", 66.67, 49.18, 1.0, "all", 1.0 ], ["jean.grey", "Digital Content Production","Unreal Engine Layerstack", "Daimler", "Muenchen", "Employee", 133.77, 74.65, 1.0, "all", 0.75], ["stephen.strange", "Digital Content Production","Unreal Engine Layerstack", "Porsche AG", "Muenchen", "Employee", 66.67, 49.18, 1.0, "all", 0.75], ["wong.wong", "Digital Content Production","Unreal Engine Layerstack", "Porsche AG", "Stuttgart", "Employee", 66.67, 49.18, 0.5, "all", 0.75], // ── Art Direction ── ["vision.vision", "Art Direction", "Art Direction", "BMW", "Stuttgart", "Employee", 66.67, 49.18, 1.0, "all", 0.75], ["drax.drax", "Art Direction", "Art Direction", "Daimler", "Stuttgart", "Employee", 96.77, 65.24, 1.0, "all", 0.75], ["rocket.rocket", "Art Direction", "Art Direction", "Cross-Unit", "Stuttgart", "Employee", 118.35, 88.52, 0.8, "mon,tue,wed,thu", 0.75], ["groot.groot", "Art Direction", "Art Direction", "Jaguar Land Rover", "Hamburg", "Employee", 118.35, 88.52, 1.0, "all", 0.75], ["tchalla.tchalla", "Art Direction", "Art Direction", "Jaguar Land Rover", "Stuttgart", "Employee", 96.77, 65.24, 1.0, "all", 0.9 ], // ── Product Data Management / Compositor ── ["clint.barton", "Product Data Management", "Vis Logic", "Cross-Unit", "Muenchen", "Employee", 76.64, 55.98, 1.0, "all", 0.9 ], ["hank.mccoy", "Product Data Management", "Vis Logic", "Porsche AG", "Muenchen", "Employee", 133.77, 74.65, 1.0, "all", 1.0 ], ["anna.marie", "Product Data Management", "Vis Logic", "Daimler", "Hamburg", "Employee", 118.35, 88.52, 1.0, "all", 0.86], ["peter.quill", "Product Data Management", "Vis Logic", "Moving Image", "Stuttgart", "Employee", 95.00, 88.52, 1.0, "all", 0.75], // ── CGI-Dev Unreal ── ["matt.murdock", "CGI-Dev", "Dev Unreal", "Daimler", "Muenchen", "Employee", 118.35, 88.52, 1.0, "all", 1.0 ], ["carl.lucas", "CGI-Dev", "Dev Unreal", "Daimler", "Muenchen", "Employee", 133.77, 74.65, 0.5, "all", 0.75], ["jessica.jones", "CGI-Dev", "Dev Unreal", "Porsche AG", "Stuttgart", "Employee", 76.64, 55.98, 1.0, "all", 0.85], ["danny.rand", "CGI-Dev", "Dev Unreal", "Porsche AG", "Stuttgart", "Employee", 118.35, 88.52, 1.0, "all", 0.75], // ── CGI-Dev Frontend ── ["frank.castle", "CGI-Dev", "Dev Frontend", "Daimler", "Hamburg", "Employee", 96.77, 65.24, 0.5, "all", 0.75], ["carol.danvers", "CGI-Dev", "Dev Frontend", "Moving Image", "Stuttgart", "Freelancer", 76.64, 55.98, 1.0, "all", 0.85], ["kamala.khan", "CGI-Dev", "Dev Frontend", "Jaguar Land Rover", "Stuttgart", "Freelancer", 76.64, 55.98, 1.0, "all", 0.75], ]; // ─── Project data ────────────────────────────────────────────────────────────── // [shortCode, name, clientTag, isOrdered, orderTypeStr, winProb, allocTypeStr, budgetCents] type ProjectRow = [string, string, string, boolean, string, number, string, number]; const PROJECT_DATA: ProjectRow[] = [ // ── Completed 3D Stills ──────────────────────────────────────────────────── ["PORS24A", "Porsche 911 GT3 Campaign Stills", "PAG", true, "CHARGEABLE", 100, "EXT", 11500000], ["BMW24B", "BMW M5 Competition Pack", "BMW", true, "CHARGEABLE", 100, "EXT", 8800000], ["JLR24C", "Jaguar F-Type Reveal Stills", "JLR", true, "CHARGEABLE", 100, "EXT", 6700000], ["DAI24E", "Mercedes AMG Night Edition Stills", "DAI", true, "CHARGEABLE", 100, "EXT", 9200000], ["JLR24G", "Defender Trail Series Stills", "JLR", true, "BD", 100, "EXT", 5500000], ["AUDI25C", "Audi Q8 e-tron Campaign Stills", "PAG", true, "CHARGEABLE", 100, "EXT", 10500000], ["JLR25E", "Range Rover Lifestyle Stills", "JLR", true, "CHARGEABLE", 100, "EXT", 7200000], ["DAI25F", "Mercedes S-Class Interior Viz", "DAI", true, "CHARGEABLE", 100, "EXT", 9800000], ["BMW25H", "BMW M3 Competition Still Pack", "BMW", true, "CHARGEABLE", 100, "EXT", 8500000], // ── Completed Animated Movies ────────────────────────────────────────────── ["HERO24D", "Heritage Brand Film Stuttgart", "PAG", true, "CHARGEABLE", 100, "EXT", 28500000], ["BMW24F", "BMW iX Vision Brand Film", "BMW", true, "CHARGEABLE", 100, "EXT", 34000000], ["DAI24H", "AMG GT Campaign 2024", "DAI", true, "CHARGEABLE", 100, "EXT", 21000000], ["JLR25A", "Defender Chronicles Pilot Film", "JLR", true, "CHARGEABLE", 100, "EXT", 43500000], ["PAG25B", "Porsche 75 Years Anniversary Film", "PAG", true, "CHARGEABLE", 100, "EXT", 44500000], ["BMW25D", "MINI Electric Short Film", "BMW", true, "CHARGEABLE", 100, "EXT", 19500000], // ── Completed Pipeline / Internal ───────────────────────────────────────── ["DEV24A", "Render Farm v3 Migration", "INT", true, "OVERHEAD", 100, "INT", 5200000], ["DEV25A", "Houdini UE5 Pipeline Bridge", "INT", true, "INTERNAL", 100, "INT", 7800000], // ── Active Animated Movies ───────────────────────────────────────────────── ["PAG25G", "Porsche Taycan Sport Film", "PAG", true, "CHARGEABLE", 100, "EXT", 32000000], ["JLR26A", "Jaguar EV Launch Film", "JLR", true, "CHARGEABLE", 100, "EXT", 38500000], // ── Active Pipeline ──────────────────────────────────────────────────────── ["DEV25B", "Asset Library & Pipeline Rebuild", "INT", true, "OVERHEAD", 100, "INT", 6500000], // ── Active 3D Stills ─────────────────────────────────────────────────────── ["DAI26B", "AMG EV Reveal Campaign Stills", "DAI", true, "CHARGEABLE", 100, "EXT", 12500000], ["PAG26C", "Porsche Carrera Grand Viz", "PAG", true, "CHARGEABLE", 100, "EXT", 11000000], // ── Draft 3D Stills ──────────────────────────────────────────────────────── ["BMW26B", "BMW X5 Facelift Campaign", "BMW", false, "CHARGEABLE", 80, "EXT", 9500000], ["BMW26C", "MINI Aceman Launch Campaign", "BMW", false, "CHARGEABLE", 75, "EXT", 7800000], // ── Draft Animated Movies ────────────────────────────────────────────────── ["JLR26B", "Defender 90 Anniversary Film", "JLR", false, "CHARGEABLE", 90, "EXT", 41000000], ["PAG26D", "Porsche Mission X Concept Film", "PAG", false, "BD", 60, "EXT", 45000000], ["DAI26C", "Mercedes G-Class Offroad Film", "DAI", false, "CHARGEABLE", 85, "EXT", 29500000], // ── Draft Pipeline ───────────────────────────────────────────────────────── ["DEV26A", "UE5 Realtime Pipeline v4", "INT", false, "INTERNAL", 100, "INT", 8500000], ]; // shortCode → [startDate, endDate, status] const PROJECT_DATES: Record = { // Completed 3D Stills PORS24A: ["2024-02-05", "2024-03-15", ProjectStatus.COMPLETED], BMW24B: ["2024-03-18", "2024-05-03", ProjectStatus.COMPLETED], JLR24C: ["2024-04-08", "2024-05-17", ProjectStatus.COMPLETED], DAI24E: ["2024-06-03", "2024-07-12", ProjectStatus.COMPLETED], JLR24G: ["2024-09-02", "2024-10-11", ProjectStatus.COMPLETED], AUDI25C: ["2025-05-05", "2025-06-20", ProjectStatus.COMPLETED], JLR25E: ["2025-08-04", "2025-09-26", ProjectStatus.COMPLETED], DAI25F: ["2025-09-01", "2025-10-17", ProjectStatus.COMPLETED], BMW25H: ["2025-11-03", "2025-12-31", ProjectStatus.COMPLETED], // Completed Animated Movies HERO24D: ["2024-05-06", "2024-09-27", ProjectStatus.COMPLETED], BMW24F: ["2024-08-05", "2024-12-20", ProjectStatus.COMPLETED], DAI24H: ["2024-10-07", "2025-01-31", ProjectStatus.COMPLETED], JLR25A: ["2025-02-03", "2025-09-30", ProjectStatus.COMPLETED], PAG25B: ["2025-04-01", "2025-12-19", ProjectStatus.COMPLETED], BMW25D: ["2025-07-07", "2025-11-28", ProjectStatus.COMPLETED], // Completed Pipeline DEV24A: ["2024-07-01", "2024-08-30", ProjectStatus.COMPLETED], DEV25A: ["2025-06-02", "2025-09-30", ProjectStatus.COMPLETED], // Active (currently running — today is 2026-03-04) PAG25G: ["2025-10-01", "2026-03-31", ProjectStatus.ACTIVE], JLR26A: ["2026-01-05", "2026-07-31", ProjectStatus.ACTIVE], DEV25B: ["2025-11-03", "2026-04-30", ProjectStatus.ACTIVE], DAI26B: ["2026-02-02", "2026-04-17", ProjectStatus.ACTIVE], PAG26C: ["2026-02-16", "2026-04-24", ProjectStatus.ACTIVE], // Draft / Planned BMW26B: ["2026-05-04", "2026-06-26", ProjectStatus.DRAFT], BMW26C: ["2026-10-01", "2026-11-27", ProjectStatus.DRAFT], JLR26B: ["2026-06-01", "2026-11-30", ProjectStatus.DRAFT], PAG26D: ["2026-07-01", "2027-01-31", ProjectStatus.DRAFT], DAI26C: ["2026-09-01", "2027-02-28", ProjectStatus.DRAFT], DEV26A: ["2026-05-04", "2026-08-28", ProjectStatus.DRAFT], }; function parseOrderType(s: string): OrderType { switch (s) { case "BD": return OrderType.BD; case "CHARGEABLE": return OrderType.CHARGEABLE; case "INTERNAL": return OrderType.INTERNAL; case "OVERHEAD": return OrderType.OVERHEAD; default: return OrderType.CHARGEABLE; } } function parseAllocationType(s: string): AllocationType { return s === "INT" ? AllocationType.INT : AllocationType.EXT; } // ─── Main ────────────────────────────────────────────────────────────────────── async function main() { const target = assertSafeSeedTarget("db:seed"); console.warn(`Seeding CapaKraken example data into ${target.databaseName} (${target.hostname}${target.port ? `:${target.port}` : ""})...`); // ── 1. Delete all data (keep users) ──────────────────────────────────────── console.warn(`Deleting existing data from disposable seed target '${target.databaseName}'...`); await prisma.auditLog.deleteMany({}); await prisma.notification.deleteMany({}); // Estimates (deep hierarchy) await prisma.estimateExport.deleteMany({}); await prisma.estimateMetric.deleteMany({}); await prisma.estimateDemandLine.deleteMany({}); await prisma.estimateAssumption.deleteMany({}); await prisma.resourceCostSnapshot.deleteMany({}); await prisma.estimateVersion.deleteMany({}); await prisma.estimate.deleteMany({}); // Assignments + demands before projects/resources await prisma.assignment.deleteMany({}); await prisma.demandRequirement.deleteMany({}); await prisma.resourceRole.deleteMany({}); await prisma.vacation.deleteMany({}); await prisma.vacationEntitlement.deleteMany({}); await prisma.calculationRule.deleteMany({}); await prisma.project.deleteMany({}); await prisma.resource.deleteMany({}); await prisma.role.deleteMany({}); await prisma.blueprint.deleteMany({}); // Dispo v2 entities (children before parents) await prisma.managementLevel.deleteMany({}); await prisma.managementLevelGroup.deleteMany({}); await prisma.metroCity.deleteMany({}); await prisma.country.deleteMany({}); await prisma.orgUnit.deleteMany({}); await prisma.utilizationCategory.deleteMany({}); await prisma.client.deleteMany({}); console.warn("Existing data deleted."); // ── 2. Upsert users ──────────────────────────────────────────────────────── const adminHash = await hash("admin123"); const managerHash = await hash("manager123"); const viewerHash = await hash("viewer123"); const admin = await prisma.user.upsert({ where: { email: "admin@capakraken.dev" }, update: { passwordHash: adminHash }, create: { email: "admin@capakraken.dev", name: "Admin User", passwordHash: adminHash, systemRole: SystemRole.ADMIN }, }); const manager = await prisma.user.upsert({ where: { email: "manager@capakraken.dev" }, update: { passwordHash: managerHash }, create: { email: "manager@capakraken.dev", name: "Manager User", passwordHash: managerHash, systemRole: SystemRole.MANAGER }, }); const viewer = await prisma.user.upsert({ where: { email: "viewer@capakraken.dev" }, update: { passwordHash: viewerHash }, create: { email: "viewer@capakraken.dev", name: "Viewer User", passwordHash: viewerHash, systemRole: SystemRole.VIEWER }, }); console.warn(`Users: admin=${admin.id}, manager=${manager.id}, viewer=${viewer.id}`); // ── 2b. Create Dispo v2 entities ────────────────────────────────────────── // Countries + Metro Cities const countryDE = await prisma.country.create({ data: { code: "DE", name: "Germany", dailyWorkingHours: 8.0 }, }); const countryIN = await prisma.country.create({ data: { code: "IN", name: "India", dailyWorkingHours: 9.0 }, }); const countryES = await prisma.country.create({ data: { code: "ES", name: "Spain", dailyWorkingHours: 8.0, scheduleRules: { type: "spain", regularHours: 8.5, fridayHours: 7, summerHours: 7, summerPeriod: { from: "06-15", to: "09-15" }, } as unknown as Prisma.InputJsonValue, }, }); const countryUS = await prisma.country.create({ data: { code: "US", name: "United States", dailyWorkingHours: 8.0 }, }); const cityMap = new Map(); // cityName → id for (const [countryId, cities] of [ [countryDE.id, ["Augsburg", "Berlin", "Hamburg", "Koeln", "Muenchen", "Stuttgart"]], [countryIN.id, ["Bangalore", "Mumbai"]], [countryES.id, ["Madrid", "Barcelona"]], [countryUS.id, ["New York", "Los Angeles"]], ] as [string, string[]][]) { for (const cityName of cities) { const city = await prisma.metroCity.create({ data: { name: cityName, countryId }, }); cityMap.set(cityName, city.id); } } console.warn(`Countries: 4 created, Metro Cities: ${cityMap.size} created`); // Org Units (3-level hierarchy: L5 → L6 → L7) const orgUnitMap = new Map(); // name → id const l5 = await prisma.orgUnit.create({ data: { name: "Studio Services", level: 5, sortOrder: 1 }, }); orgUnitMap.set("Studio Services", l5.id); const l6ContentProd = await prisma.orgUnit.create({ data: { name: "Content Production", level: 6, parentId: l5.id, sortOrder: 1 }, }); orgUnitMap.set("Content Production", l6ContentProd.id); for (const [name, order] of [["3D Visualization", 1], ["Animation", 2], ["Art Direction", 3]] as [string, number][]) { const unit = await prisma.orgUnit.create({ data: { name, level: 7, parentId: l6ContentProd.id, sortOrder: order }, }); orgUnitMap.set(name, unit.id); } const l6Tech = await prisma.orgUnit.create({ data: { name: "Technology", level: 6, parentId: l5.id, sortOrder: 2 }, }); orgUnitMap.set("Technology", l6Tech.id); for (const [name, order] of [["CGI Development", 1], ["Pipeline Engineering", 2]] as [string, number][]) { const unit = await prisma.orgUnit.create({ data: { name, level: 7, parentId: l6Tech.id, sortOrder: order }, }); orgUnitMap.set(name, unit.id); } const l6Ops = await prisma.orgUnit.create({ data: { name: "Operations", level: 6, parentId: l5.id, sortOrder: 3 }, }); orgUnitMap.set("Operations", l6Ops.id); for (const [name, order] of [["Product Data Management", 1], ["Project Management", 2]] as [string, number][]) { const unit = await prisma.orgUnit.create({ data: { name, level: 7, parentId: l6Ops.id, sortOrder: order }, }); orgUnitMap.set(name, unit.id); } console.warn(`Org Units: ${orgUnitMap.size} created`); // Utilization Categories const utilCatMap = new Map(); // code → id const UTIL_CATS = [ { code: "Chg", name: "Chargeable", isDefault: true, sortOrder: 1 }, { code: "BD", name: "Business Development", sortOrder: 2 }, { code: "MD&I", name: "Market Development & Innovation", sortOrder: 3 }, { code: "M&O", name: "Management & Operations", sortOrder: 4 }, { code: "PD&R", name: "People Development & Recruiting", sortOrder: 5 }, { code: "Absence", name: "Absence", sortOrder: 6 }, ]; for (const cat of UTIL_CATS) { const created = await prisma.utilizationCategory.create({ data: cat }); utilCatMap.set(cat.code, created.id); } console.warn(`Utilization Categories: ${utilCatMap.size} created`); // Management Level Groups + Levels const mgmtGroupMap = new Map(); // groupName → id const mgmtLevelMap = new Map(); // levelName → id const MGMT_GROUPS = [ { name: "Accenture Leadership", target: 0.365, levels: ["CL10-Managing Director", "CL9-Senior Managing Director"] }, { name: "Senior Manager", target: 0.546, levels: ["CL8-Senior Manager"] }, { name: "Manager", target: 0.747, levels: ["CL7-Manager"] }, { name: "Consultant", target: 0.808, levels: ["CL6-Consultant", "CL5-Senior Analyst"] }, { name: "Analyst", target: 0.805, levels: ["CL4-Analyst"] }, { name: "Associate", target: 0.77, levels: ["CL3-Associate", "CL2-Junior Associate"] }, ]; for (const g of MGMT_GROUPS) { const group = await prisma.managementLevelGroup.create({ data: { name: g.name, targetPercentage: g.target, sortOrder: MGMT_GROUPS.indexOf(g) + 1 }, }); mgmtGroupMap.set(g.name, group.id); for (const levelName of g.levels) { const level = await prisma.managementLevel.create({ data: { name: levelName, groupId: group.id }, }); mgmtLevelMap.set(levelName, level.id); } } console.warn(`Management Level Groups: ${mgmtGroupMap.size}, Levels: ${mgmtLevelMap.size} created`); // Clients (hierarchy: Master → Entity) const clientMap = new Map(); // name → id const CLIENT_TREE = [ { master: "Automotive OEM", entities: ["Porsche AG", "BMW Group", "Daimler / Mercedes", "Jaguar Land Rover"] }, { master: "Moving Image & Digital", entities: ["Moving Image", "Cross-Unit"] }, { master: "Internal", entities: ["Internal Projects"] }, ]; for (const tree of CLIENT_TREE) { const master = await prisma.client.create({ data: { name: tree.master, sortOrder: CLIENT_TREE.indexOf(tree) + 1 }, }); clientMap.set(tree.master, master.id); for (const entityName of tree.entities) { const entity = await prisma.client.create({ data: { name: entityName, parentId: master.id, sortOrder: tree.entities.indexOf(entityName) + 1 }, }); clientMap.set(entityName, entity.id); } } console.warn(`Clients: ${clientMap.size} created`); // ── 3. Create blueprints ─────────────────────────────────────────────────── // Shared option sets const deliveryFormatOptions = [ { value: "WebHD_1080p", label: "Web HD (1080p)" }, { value: "4K_UHD", label: "4K UHD (3840×2160)" }, { value: "8K", label: "8K (7680×4320)" }, { value: "DCP", label: "DCP (Cinema)" }, { value: "Custom", label: "Custom / TBD" }, ]; const frameRateOptions = [ { value: "24", label: "24 fps (Film)" }, { value: "25", label: "25 fps (PAL)" }, { value: "30", label: "30 fps (NTSC)" }, { value: "60", label: "60 fps (HFR)" }, ]; const colorSpaceOptions = [ { value: "sRGB_Rec709", label: "sRGB / Rec.709" }, { value: "DCI-P3", label: "DCI-P3 (Cinema)" }, { value: "Rec2020_HDR", label: "Rec.2020 / HDR" }, ]; const classificationOptions = [ { value: "Confidential", label: "Confidential" }, { value: "Not Confidential", label: "Not Confidential" }, ]; const resourceBlueprint = await prisma.blueprint.create({ data: { name: "Studio Resource Blueprint", target: BlueprintTarget.RESOURCE, description: "Standard fields for 3D studio resources", fieldDefs: [ // Group: Basic Info { id: "fd-client-unit", label: "Client Unit / Account", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Basic Info" }, { id: "fd-work-type", label: "Type of Work / Specialization", key: "workType", type: FieldType.TEXT, required: false, order: 1, group: "Basic Info" }, { id: "fd-city", label: "Office Location (Metro City)", key: "city", type: FieldType.TEXT, required: false, order: 2, group: "Basic Info" }, { id: "fd-employee-type", label: "Employee Type", key: "employeeType", type: FieldType.SELECT, required: false, order: 3, group: "Basic Info", options: [ { value: "Employee", label: "Employee" }, { value: "Freelancer", label: "Freelancer" }, { value: "Intern", label: "Intern" }, { value: "External", label: "External Contractor" }, ], }, // Group: Work Setup { id: "fd-remote-eligible", label: "Eligible for Remote Work", key: "remoteEligible", type: FieldType.BOOLEAN, required: false, order: 4, group: "Work Setup" }, { id: "fd-timezone", label: "Timezone", key: "timezone", type: FieldType.SELECT, required: false, order: 5, group: "Work Setup", options: [ { value: "UTC-5", label: "UTC-5 (EST)" }, { value: "UTC-4", label: "UTC-4 (EDT)" }, { value: "UTC+0", label: "UTC+0 (GMT/WET)" }, { value: "UTC+1", label: "UTC+1 (CET)" }, { value: "UTC+2", label: "UTC+2 (EET/CEST)" }, { value: "UTC+3", label: "UTC+3 (MSK)" }, { value: "UTC+5.5", label: "UTC+5:30 (IST)" }, { value: "UTC+8", label: "UTC+8 (CST/HKT)" }, { value: "UTC+9", label: "UTC+9 (JST)" }, ], }, { id: "fd-primary-software", label: "Primary Software", key: "primarySoftware", type: FieldType.MULTI_SELECT, required: false, order: 6, group: "Work Setup", options: [ { value: "Maya", label: "Maya" }, { value: "Cinema4D", label: "Cinema 4D" }, { value: "Houdini", label: "Houdini" }, { value: "Blender", label: "Blender" }, { value: "UnrealEngine", label: "Unreal Engine" }, { value: "AfterEffects", label: "After Effects" }, { value: "Nuke", label: "Nuke" }, { value: "Photoshop", label: "Photoshop" }, { value: "SubstancePainter", label: "Substance Painter" }, { value: "ZBrush", label: "ZBrush" }, { value: "PremierePro", label: "Premiere Pro" }, { value: "DaVinciResolve", label: "DaVinci Resolve" }, ], }, // Group: Background { id: "fd-years-experience", label: "Years of Industry Experience", key: "yearsOfExperience", type: FieldType.NUMBER, required: false, order: 7, group: "Background", description: "Total years working in the industry" }, { id: "fd-languages", label: "Spoken Languages", key: "spokenLanguages", type: FieldType.TEXT, required: false, order: 8, group: "Background", placeholder: "e.g. English, German, French" }, { id: "fd-portfolio-notes", label: "Portfolio / Work Notes", key: "portfolioNotes", type: FieldType.TEXTAREA, required: false, order: 9, group: "Background", description: "Links or notes about portfolio / notable projects" }, { id: "fd-internal-notes", label: "Internal Notes", key: "internalNotes", type: FieldType.TEXTAREA, required: false, order: 10, group: "Background", description: "HR / team notes (not visible to the resource)" }, ], defaults: {}, validationRules: [], }, }); const projectBlueprint = await prisma.blueprint.create({ data: { name: "Studio Project Blueprint", target: BlueprintTarget.PROJECT, description: "Standard fields for client and internal projects", fieldDefs: [ // Group: Client & Billing { id: "fd-proj-client-unit", label: "Client Unit Tag", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Client & Billing", placeholder: "e.g. [DAI], [BMW]" }, { id: "fd-person-hours-sold", label: "Person Hours Sold", key: "personHoursSold", type: FieldType.NUMBER, required: false, order: 1, group: "Client & Billing", description: "Planned billable person hours agreed with client" }, { id: "fd-classification", label: "Classification", key: "classification", type: FieldType.SELECT, required: false, order: 2, group: "Client & Billing", options: classificationOptions }, { id: "fd-client-contact", label: "Client Contact / Account Mgr", key: "clientContact", type: FieldType.TEXT, required: false, order: 3, group: "Client & Billing" }, { id: "fd-crm-reference", label: "CRM Reference / Opportunity", key: "crmReference", type: FieldType.TEXT, required: false, order: 4, group: "Client & Billing", description: "CRM opportunity ID or ticket reference" }, // Group: Delivery { id: "fd-delivery-deadline", label: "Final Delivery Date", key: "deliveryDeadline", type: FieldType.DATE, required: false, order: 5, group: "Delivery" }, { id: "fd-delivery-format", label: "Delivery Format", key: "deliveryFormat", type: FieldType.SELECT, required: false, order: 6, group: "Delivery", options: deliveryFormatOptions }, { id: "fd-frame-rate", label: "Frame Rate", key: "frameRate", type: FieldType.SELECT, required: false, order: 7, group: "Delivery", options: frameRateOptions }, { id: "fd-color-space", label: "Color Space", key: "colorSpace", type: FieldType.SELECT, required: false, order: 8, group: "Delivery", options: colorSpaceOptions }, // Group: Scope { id: "fd-approval-rounds", label: "Client Approval Rounds", key: "clientApprovalRounds", type: FieldType.NUMBER, required: false, order: 9, group: "Scope", description: "Estimated number of client review cycles" }, { id: "fd-revision-budget", label: "Revision Budget (hours)", key: "revisionBudgetHours", type: FieldType.NUMBER, required: false, order: 10, group: "Scope", description: "Person-hours reserved for revisions and corrections" }, { id: "fd-notes", label: "Project Notes", key: "notes", type: FieldType.TEXTAREA, required: false, order: 11, group: "Scope", description: "Technical or creative notes / special requirements" }, ], defaults: { classification: "Not Confidential" }, validationRules: [], }, }); const blueprint3D = await prisma.blueprint.create({ data: { name: "3D Content Production", target: BlueprintTarget.PROJECT, description: "Blueprint for 3D rendering and stills projects", fieldDefs: [ // Group: Client & Billing { id: "fd-3d-client-unit", label: "Client Unit Tag", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Client & Billing", placeholder: "e.g. [DAI], [BMW]" }, { id: "fd-3d-hours-sold", label: "Person Hours Sold", key: "personHoursSold", type: FieldType.NUMBER, required: false, order: 1, group: "Client & Billing", description: "Planned billable person hours agreed with client" }, { id: "fd-3d-classification", label: "Classification", key: "classification", type: FieldType.SELECT, required: false, order: 2, group: "Client & Billing", options: classificationOptions }, { id: "fd-3d-client-contact", label: "Client Contact / Account Mgr", key: "clientContact", type: FieldType.TEXT, required: false, order: 3, group: "Client & Billing" }, { id: "fd-3d-crm", label: "CRM Reference / Opportunity", key: "crmReference", type: FieldType.TEXT, required: false, order: 4, group: "Client & Billing", description: "CRM opportunity ID or ticket reference" }, // Group: Technical Specs { id: "fd-3d-render-engine", label: "Render Engine", key: "renderEngine", type: FieldType.SELECT, required: false, order: 5, group: "Technical Specs", options: [ { value: "Arnold", label: "Arnold" }, { value: "VRay", label: "V-Ray" }, { value: "Redshift", label: "Redshift" }, { value: "Cycles", label: "Cycles (Blender)" }, { value: "Octane", label: "Octane" }, { value: "Corona", label: "Corona" }, { value: "KeyShot", label: "KeyShot" }, ], }, { id: "fd-3d-render-farm", label: "Render Farm", key: "renderFarm", type: FieldType.SELECT, required: false, order: 6, group: "Technical Specs", options: [ { value: "InhouseCPU", label: "In-house CPU" }, { value: "InhouseGPU", label: "In-house GPU" }, { value: "CloudAWS", label: "Cloud (AWS)" }, { value: "CloudGCP", label: "Cloud (GCP)" }, { value: "Hybrid", label: "Hybrid" }, ], }, { id: "fd-3d-delivery-format", label: "Delivery Format", key: "deliveryFormat", type: FieldType.SELECT, required: false, order: 7, group: "Technical Specs", options: deliveryFormatOptions }, { id: "fd-3d-frame-rate", label: "Frame Rate", key: "frameRate", type: FieldType.SELECT, required: false, order: 8, group: "Technical Specs", options: frameRateOptions }, { id: "fd-3d-color-space", label: "Color Space", key: "colorSpace", type: FieldType.SELECT, required: false, order: 9, group: "Technical Specs", options: colorSpaceOptions }, { id: "fd-3d-texture-res", label: "Texture Resolution", key: "textureResolution", type: FieldType.SELECT, required: false, order: 10, group: "Technical Specs", options: [ { value: "2K", label: "2K Textures" }, { value: "4K", label: "4K Textures" }, { value: "8K", label: "8K Textures" }, ], }, // Group: Asset Scope { id: "fd-3d-num-assets", label: "Number of Assets", key: "numberOfAssets", type: FieldType.NUMBER, required: false, order: 11, group: "Asset Scope", description: "Total 3D models / assets to produce" }, { id: "fd-3d-asset-complexity", label: "Asset Complexity", key: "assetComplexity", type: FieldType.SELECT, required: false, order: 12, group: "Asset Scope", options: [ { value: "Low", label: "Low" }, { value: "Medium", label: "Medium" }, { value: "High", label: "High" }, { value: "VeryHigh", label: "Very High" }, ], }, { id: "fd-3d-lighting-setups", label: "Lighting Setups", key: "numberOfLightingSetups", type: FieldType.NUMBER, required: false, order: 13, group: "Asset Scope", description: "Number of distinct lighting scenarios" }, { id: "fd-3d-render-hours", label: "Render Hours / Frame", key: "estimatedRenderHoursPerFrame", type: FieldType.NUMBER, required: false, order: 14, group: "Asset Scope" }, { id: "fd-3d-iterations", label: "Iterations per Asset", key: "numberOfIterations", type: FieldType.NUMBER, required: false, order: 15, group: "Asset Scope" }, { id: "fd-3d-post-processing", label: "Post-Processing Required", key: "postProcessingRequired", type: FieldType.BOOLEAN, required: false, order: 16, group: "Asset Scope", description: "Compositing / retouching needed" }, // Group: Scope { id: "fd-3d-scope-approval-rounds", label: "Client Approval Rounds", key: "clientApprovalRounds", type: FieldType.NUMBER, required: false, order: 17, group: "Scope", description: "Estimated number of client review cycles" }, { id: "fd-3d-scope-revision-budget", label: "Revision Budget (hours)", key: "revisionBudgetHours", type: FieldType.NUMBER, required: false, order: 18, group: "Scope", description: "Person-hours reserved for revisions and corrections" }, { id: "fd-3d-scope-notes", label: "Project Notes", key: "notes", type: FieldType.TEXTAREA, required: false, order: 19, group: "Scope", description: "Technical or creative notes / special requirements" }, ], defaults: { classification: "Not Confidential" }, validationRules: [], rolePresets: [ { id: "rp-3d-pm", role: "Project Manager", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 }, { id: "rp-3d-lead", role: "3D Lead", requiredSkills: ["3D Modeling", "3D Lighting"], hoursPerDay: 8, headcount: 1 }, { id: "rp-3d-art", role: "3D Artist", requiredSkills: ["3D Modeling"], hoursPerDay: 8, headcount: 2 }, { id: "rp-3d-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 }, { id: "rp-3d-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 }, { id: "rp-3d-td", role: "Technical Director", requiredSkills: ["Pipeline", "3D Modeling"], hoursPerDay: 6, headcount: 1 }, ], }, }); const blueprintAnimation = await prisma.blueprint.create({ data: { name: "Animation Production", target: BlueprintTarget.PROJECT, description: "Blueprint for animated films and motion projects", fieldDefs: [ // Group: Client & Billing { id: "fd-anim-client-unit", label: "Client Unit Tag", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Client & Billing", placeholder: "e.g. [DAI], [BMW]" }, { id: "fd-anim-hours-sold", label: "Person Hours Sold", key: "personHoursSold", type: FieldType.NUMBER, required: false, order: 1, group: "Client & Billing", description: "Planned billable person hours agreed with client" }, { id: "fd-anim-classification", label: "Classification", key: "classification", type: FieldType.SELECT, required: false, order: 2, group: "Client & Billing", options: classificationOptions }, { id: "fd-anim-client-contact", label: "Client Contact / Account Mgr", key: "clientContact", type: FieldType.TEXT, required: false, order: 3, group: "Client & Billing" }, { id: "fd-anim-crm", label: "CRM Reference / Opportunity", key: "crmReference", type: FieldType.TEXT, required: false, order: 4, group: "Client & Billing", description: "CRM opportunity ID or ticket reference" }, // Group: Animation Specs { id: "fd-anim-style", label: "Animation Style", key: "animationStyle", type: FieldType.SELECT, required: false, order: 5, group: "Animation Specs", options: [ { value: "Realistic", label: "Realistic / Photoreal" }, { value: "Stylized", label: "Stylized" }, { value: "MotionCapture", label: "Motion Capture" }, { value: "Procedural", label: "Procedural" }, { value: "CelShaded", label: "Cel-Shaded / Toon" }, { value: "Mixed", label: "Mixed / Hybrid" }, ], }, { id: "fd-anim-duration", label: "Duration (seconds)", key: "durationSeconds", type: FieldType.NUMBER, required: false, order: 6, group: "Animation Specs" }, { id: "fd-anim-scenes", label: "Number of Scenes", key: "sceneCount", type: FieldType.NUMBER, required: false, order: 7, group: "Animation Specs" }, { id: "fd-anim-shots", label: "Number of Shots", key: "shotCount", type: FieldType.NUMBER, required: false, order: 8, group: "Animation Specs" }, { id: "fd-anim-delivery", label: "Delivery Format", key: "deliveryFormat", type: FieldType.SELECT, required: false, order: 9, group: "Animation Specs", options: deliveryFormatOptions }, { id: "fd-anim-fps", label: "Frame Rate", key: "frameRate", type: FieldType.SELECT, required: false, order: 10, group: "Animation Specs", options: frameRateOptions }, // Group: Characters & Rigging { id: "fd-anim-chars", label: "Number of Characters", key: "characterCount", type: FieldType.NUMBER, required: false, order: 11, group: "Characters & Rigging" }, { id: "fd-anim-rig-complexity", label: "Rig Complexity", key: "rigComplexity", type: FieldType.SELECT, required: false, order: 12, group: "Characters & Rigging", options: [ { value: "Simple", label: "Simple (basic bones)" }, { value: "Standard", label: "Standard (FK/IK)" }, { value: "Complex", label: "Complex (full-body IK)" }, { value: "Hero", label: "Hero (simulation + secondary motion)" }, ], }, { id: "fd-anim-mocap", label: "Motion Capture Required", key: "mocapRequired", type: FieldType.BOOLEAN, required: false, order: 13, group: "Characters & Rigging" }, { id: "fd-anim-storyboard", label: "Storyboard Provided", key: "storyboardProvided", type: FieldType.BOOLEAN, required: false, order: 14, group: "Characters & Rigging", description: "Client provides storyboard" }, // Group: Post & Audio { id: "fd-anim-music", label: "Music / Sound Design", key: "musicPostRequired", type: FieldType.BOOLEAN, required: false, order: 15, group: "Post & Audio" }, { id: "fd-anim-colgrade", label: "Color Grading", key: "colorGradingRequired", type: FieldType.BOOLEAN, required: false, order: 16, group: "Post & Audio" }, // Group: Scope { id: "fd-anim-scope-approval-rounds", label: "Client Approval Rounds", key: "clientApprovalRounds", type: FieldType.NUMBER, required: false, order: 17, group: "Scope", description: "Estimated number of client review cycles" }, { id: "fd-anim-scope-revision-budget", label: "Revision Budget (hours)", key: "revisionBudgetHours", type: FieldType.NUMBER, required: false, order: 18, group: "Scope", description: "Person-hours reserved for revisions and corrections" }, { id: "fd-anim-scope-notes", label: "Project Notes", key: "notes", type: FieldType.TEXTAREA, required: false, order: 19, group: "Scope", description: "Technical or creative notes / special requirements" }, ], defaults: { classification: "Not Confidential" }, validationRules: [], rolePresets: [ { id: "rp-anim-pm", role: "Project Manager", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 }, { id: "rp-anim-dir", role: "Animation Director", requiredSkills: ["Animation", "Unreal Engine"], hoursPerDay: 8, headcount: 1 }, { id: "rp-anim-anim", role: "Animator", requiredSkills: ["Animation"], hoursPerDay: 8, headcount: 2 }, { id: "rp-anim-rig", role: "Rigger / TD", requiredSkills: ["Rigging"], hoursPerDay: 8, headcount: 1 }, { id: "rp-anim-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 }, { id: "rp-anim-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 }, ], }, }); const blueprintVFX = await prisma.blueprint.create({ data: { name: "VFX / Compositing", target: BlueprintTarget.PROJECT, description: "Blueprint for VFX and compositing projects", fieldDefs: [ // Group: Client & Billing { id: "fd-vfx-client-unit", label: "Client Unit Tag", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Client & Billing", placeholder: "e.g. [DAI], [BMW]" }, { id: "fd-vfx-hours-sold", label: "Person Hours Sold", key: "personHoursSold", type: FieldType.NUMBER, required: false, order: 1, group: "Client & Billing", description: "Planned billable person hours agreed with client" }, { id: "fd-vfx-classification", label: "Classification", key: "classification", type: FieldType.SELECT, required: false, order: 2, group: "Client & Billing", options: classificationOptions }, { id: "fd-vfx-client-contact", label: "Client Contact / Account Mgr", key: "clientContact", type: FieldType.TEXT, required: false, order: 3, group: "Client & Billing" }, { id: "fd-vfx-crm", label: "CRM Reference / Opportunity", key: "crmReference", type: FieldType.TEXT, required: false, order: 4, group: "Client & Billing", description: "CRM opportunity ID or ticket reference" }, // Group: VFX Specs { id: "fd-vfx-type", label: "VFX Type(s)", key: "vfxType", type: FieldType.MULTI_SELECT, required: false, order: 5, group: "VFX Specs", options: [ { value: "GreenScreen", label: "Green Screen / Chroma Key" }, { value: "CGIIntegration", label: "CGI Integration" }, { value: "MotionTracking", label: "Motion Tracking" }, { value: "Rotoscoping", label: "Rotoscoping" }, { value: "ParticleFX", label: "Particle FX" }, { value: "FluidSim", label: "Fluid Simulation" }, { value: "MattePainting", label: "Matte Painting" }, { value: "TitleSequence", label: "Title Sequence" }, ], }, { id: "fd-vfx-shot-count", label: "Number of VFX Shots", key: "shotCount", type: FieldType.NUMBER, required: false, order: 6, group: "VFX Specs" }, { id: "fd-vfx-complexity", label: "Avg. Shot Complexity", key: "avgShotComplexity", type: FieldType.SELECT, required: false, order: 7, group: "VFX Specs", options: [ { value: "Low", label: "Low (cleanup / grade)" }, { value: "Medium", label: "Medium (integration)" }, { value: "High", label: "High (simulation)" }, { value: "Photoreal", label: "Photoreal" }, ], }, { id: "fd-vfx-delivery", label: "Delivery Format", key: "deliveryFormat", type: FieldType.SELECT, required: false, order: 8, group: "VFX Specs", options: deliveryFormatOptions }, { id: "fd-vfx-fps", label: "Frame Rate", key: "frameRate", type: FieldType.SELECT, required: false, order: 9, group: "VFX Specs", options: frameRateOptions }, { id: "fd-vfx-color-space", label: "Color Space", key: "colorSpace", type: FieldType.SELECT, required: false, order: 10, group: "VFX Specs", options: colorSpaceOptions }, // Group: Source Material { id: "fd-vfx-footage", label: "Footage Provided by Client", key: "footageProvided", type: FieldType.BOOLEAN, required: false, order: 11, group: "Source Material" }, { id: "fd-vfx-audio-sync", label: "Audio Sync Required", key: "audioSyncRequired", type: FieldType.BOOLEAN, required: false, order: 12, group: "Source Material" }, { id: "fd-vfx-onset-sup", label: "VFX Supervisor On Set", key: "hasOnSetVFXSup", type: FieldType.BOOLEAN, required: false, order: 13, group: "Source Material" }, // Group: Scope { id: "fd-vfx-scope-approval-rounds", label: "Client Approval Rounds", key: "clientApprovalRounds", type: FieldType.NUMBER, required: false, order: 14, group: "Scope", description: "Estimated number of client review cycles" }, { id: "fd-vfx-scope-revision-budget", label: "Revision Budget (hours)", key: "revisionBudgetHours", type: FieldType.NUMBER, required: false, order: 15, group: "Scope", description: "Person-hours reserved for revisions and corrections" }, { id: "fd-vfx-scope-notes", label: "Project Notes", key: "notes", type: FieldType.TEXTAREA, required: false, order: 16, group: "Scope", description: "Technical or creative notes / special requirements" }, ], defaults: { classification: "Not Confidential" }, validationRules: [], rolePresets: [ { id: "rp-vfx-producer", role: "VFX Producer", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 }, { id: "rp-vfx-sup", role: "VFX Supervisor", requiredSkills: ["Compositing", "Art Direction"], hoursPerDay: 6, headcount: 1 }, { id: "rp-vfx-sr-comp", role: "Senior Compositor", requiredSkills: ["Compositing", "Nuke"], hoursPerDay: 8, headcount: 2 }, { id: "rp-vfx-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 }, { id: "rp-vfx-roto", role: "Roto / Paint Artist", requiredSkills: ["Rotoscoping"], hoursPerDay: 8, headcount: 1 }, { id: "rp-vfx-tracker", role: "Motion Tracker", requiredSkills: ["Motion Tracking"], hoursPerDay: 8, headcount: 1 }, ], }, }); const blueprintMotion = await prisma.blueprint.create({ data: { name: "Motion Design", target: BlueprintTarget.PROJECT, description: "Blueprint for motion graphics and animation", fieldDefs: [ // Group: Client & Billing { id: "fd-mog-client-unit", label: "Client Unit Tag", key: "clientUnit", type: FieldType.TEXT, required: false, order: 0, group: "Client & Billing", placeholder: "e.g. [DAI], [BMW]" }, { id: "fd-mog-hours-sold", label: "Person Hours Sold", key: "personHoursSold", type: FieldType.NUMBER, required: false, order: 1, group: "Client & Billing", description: "Planned billable person hours agreed with client" }, { id: "fd-mog-classification", label: "Classification", key: "classification", type: FieldType.SELECT, required: false, order: 2, group: "Client & Billing", options: classificationOptions }, { id: "fd-mog-client-contact", label: "Client Contact / Account Mgr", key: "clientContact", type: FieldType.TEXT, required: false, order: 3, group: "Client & Billing" }, { id: "fd-mog-crm", label: "CRM Reference / Opportunity", key: "crmReference", type: FieldType.TEXT, required: false, order: 4, group: "Client & Billing", description: "CRM opportunity ID or ticket reference" }, // Group: Motion Specs { id: "fd-mog-style", label: "Motion Style", key: "motionStyle", type: FieldType.SELECT, required: false, order: 5, group: "Motion Specs", options: [ { value: "Flat2D", label: "2D Flat / Icon Animation" }, { value: "KineticType", label: "Kinetic Typography" }, { value: "TwoHalfD", label: "2.5D" }, { value: "ThreeD", label: "3D Motion" }, { value: "Mixed", label: "Mixed / Hybrid" }, ], }, { id: "fd-mog-duration", label: "Duration (seconds)", key: "durationSeconds", type: FieldType.NUMBER, required: false, order: 6, group: "Motion Specs" }, { id: "fd-mog-formats", label: "Delivery Formats", key: "deliveryFormats", type: FieldType.MULTI_SELECT, required: false, order: 7, group: "Motion Specs", options: [ { value: "Social_916", label: "Social (9:16 vertical)" }, { value: "Landscape_169", label: "Landscape (16:9)" }, { value: "Square_11", label: "Square (1:1)" }, { value: "Banner", label: "Banner / Display Ads" }, { value: "Custom", label: "Custom" }, ], }, { id: "fd-mog-variants", label: "Number of Variants", key: "numberOfVariants", type: FieldType.NUMBER, required: false, order: 8, group: "Motion Specs", description: "Format / language / size variants" }, // Group: Assets & Design { id: "fd-mog-design-system", label: "Client Design System Provided", key: "hasDesignSystem", type: FieldType.BOOLEAN, required: false, order: 9, group: "Assets & Design", description: "Brand guidelines, fonts, and color palette provided" }, { id: "fd-mog-storyboard", label: "Storyboard / Animatic Required",key: "storyboardRequired", type: FieldType.BOOLEAN, required: false, order: 10, group: "Assets & Design" }, { id: "fd-mog-assets", label: "Design Assets Provided", key: "assetsProvided", type: FieldType.BOOLEAN, required: false, order: 11, group: "Assets & Design", description: "Logos, images, and fonts provided by client" }, // Group: Post & Audio { id: "fd-mog-voiceover", label: "Voice-Over Required", key: "voiceoverRequired", type: FieldType.BOOLEAN, required: false, order: 12, group: "Post & Audio" }, { id: "fd-mog-music", label: "Music Licensing Required", key: "musicLicensingRequired", type: FieldType.BOOLEAN, required: false, order: 13, group: "Post & Audio" }, { id: "fd-mog-sound", label: "Custom Sound Design", key: "soundDesignRequired", type: FieldType.BOOLEAN, required: false, order: 14, group: "Post & Audio" }, // Group: Scope { id: "fd-mog-scope-approval-rounds", label: "Client Approval Rounds", key: "clientApprovalRounds", type: FieldType.NUMBER, required: false, order: 15, group: "Scope", description: "Estimated number of client review cycles" }, { id: "fd-mog-scope-revision-budget", label: "Revision Budget (hours)", key: "revisionBudgetHours", type: FieldType.NUMBER, required: false, order: 16, group: "Scope", description: "Person-hours reserved for revisions and corrections" }, { id: "fd-mog-scope-notes", label: "Project Notes", key: "notes", type: FieldType.TEXTAREA, required: false, order: 17, group: "Scope", description: "Technical or creative notes / special requirements" }, ], defaults: { classification: "Not Confidential" }, validationRules: [], rolePresets: [ { id: "rp-mog-designer", role: "Motion Designer", requiredSkills: ["After Effects", "Motion Design"], hoursPerDay: 8, headcount: 2 }, { id: "rp-mog-sr-designer", role: "Senior Motion Designer", requiredSkills: ["After Effects", "Motion Design", "Art Direction"], hoursPerDay: 8, headcount: 1 }, { id: "rp-mog-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 }, { id: "rp-mog-producer", role: "Producer", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 }, { id: "rp-mog-sound", role: "Sound Designer", requiredSkills: ["Sound Design"], hoursPerDay: 4, headcount: 1 }, ], }, }); console.warn(`Blueprints: resource=${resourceBlueprint.id}, project=${projectBlueprint.id}, 3D=${blueprint3D.id}, animation=${blueprintAnimation.id}, vfx=${blueprintVFX.id}, motion=${blueprintMotion.id}`); // ── 4. Create roles ──────────────────────────────────────────────────────── const ROLE_SEED = [ { name: "Project Manager", color: "#6366f1", description: "Project lead and client liaison" }, { name: "3D Lead", color: "#8b5cf6", description: "Senior 3D artist and technical lead" }, { name: "3D Artist", color: "#a78bfa", description: "3D modeling, texturing, lighting" }, { name: "Art Director", color: "#ec4899", description: "Visual direction and brand alignment" }, { name: "Unreal Dev", color: "#14b8a6", description: "Real-time engine development (UE5)" }, { name: "Frontend Dev", color: "#22d3ee", description: "Web & tool frontend development" }, { name: "PDM Specialist", color: "#f59e0b", description: "Product data management and compositing" }, { name: "3D Generalist", color: "#84cc16", description: "General 3D production support" }, { name: "3D Tech Tester", color: "#f97316", description: "Pipeline and render QA testing" }, ]; const roleMap = new Map(); // name → id for (const r of ROLE_SEED) { const role = await prisma.role.create({ data: { name: r.name, color: r.color, description: r.description, isActive: true }, }); roleMap.set(r.name, role.id); } console.warn(`Roles: ${roleMap.size} created`); // ── 5. Create resources ──────────────────────────────────────────────────── const resourceMap = new Map(); const countryIdByCode = new Map([ ["DE", countryDE.id], ["ES", countryES.id], ["IN", countryIN.id], ["US", countryUS.id], ]); for (const [index, row] of RESOURCE_DATA.entries()) { const [eid, chapter, typeOfWork, clientUnit, , employeeType, lcr, ucr, fraction, availDays, chargeability] = row; const holidayProfile = getHolidayDemoProfileForIndex(index); const displayName = eid .split(".") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); const email = `${eid}@capakraken.example`; const lcrCents = Math.round(lcr * 100); const ucrCents = Math.round(ucr * 100); const availability = computeAvailability(fraction, availDays); const skills = computeSkills(chapter, typeOfWork, lcr); // Dispo v2: resolve FKs const resCountryId = countryIdByCode.get(holidayProfile.countryCode) ?? countryDE.id; const resMetroCityId = cityMap.get(holidayProfile.cityName) ?? null; // chapter → orgUnit mapping const chapterToOrgUnit: Record = { "Project Management": "Project Management", "Digital Content Production": "3D Visualization", "Art Direction": "Art Direction", "Product Data Management": "Product Data Management", "CGI-Dev": "CGI Development", }; const resOrgUnitId = orgUnitMap.get(chapterToOrgUnit[chapter] ?? "") ?? null; // lcr → management level mapping let resMgmtGroupName: string; let resMgmtLevelName: string; if (lcr >= 130) { resMgmtGroupName = "Consultant"; resMgmtLevelName = "CL6-Consultant"; } else if (lcr >= 115) { resMgmtGroupName = "Manager"; resMgmtLevelName = "CL7-Manager"; } else if (lcr >= 90) { resMgmtGroupName = "Consultant"; resMgmtLevelName = "CL5-Senior Analyst"; } else if (lcr >= 75) { resMgmtGroupName = "Analyst"; resMgmtLevelName = "CL4-Analyst"; } else { resMgmtGroupName = "Associate"; resMgmtLevelName = "CL3-Associate"; } const resMgmtGroupId = mgmtGroupMap.get(resMgmtGroupName) ?? null; const resMgmtLevelId = mgmtLevelMap.get(resMgmtLevelName) ?? null; // resourceType const resResourceType = employeeType === "Freelancer" ? ResourceType.FREELANCER : ResourceType.EMPLOYEE; // clientUnit → client mapping const clientUnitToClient: Record = { "Porsche AG": "Porsche AG", "BMW": "BMW Group", "Daimler": "Daimler / Mercedes", "Jaguar Land Rover": "Jaguar Land Rover", "Moving Image": "Moving Image", "Cross-Unit": "Cross-Unit", "Italy (FER/MAS)": "Porsche AG", // mapped to Porsche AG (Porsche/Ferrari/Maserati group) }; const resClientUnitId = clientMap.get(clientUnitToClient[clientUnit] ?? "") ?? null; const resource = await prisma.resource.create({ data: { eid, displayName, email, chapter, lcrCents, ucrCents, currency: "EUR", chargeabilityTarget: chargeability * 100, availability: availability as unknown as Prisma.InputJsonValue, skills: skills as unknown as Prisma.InputJsonValue, dynamicFields: { clientUnit, workType: typeOfWork, city: holidayProfile.cityName, employeeType, holidayCountryCode: holidayProfile.countryCode, holidayStateCode: holidayProfile.stateCode, }, blueprintId: resourceBlueprint.id, countryId: resCountryId, federalState: holidayProfile.stateCode, ...(resMetroCityId ? { metroCityId: resMetroCityId } : {}), ...(resOrgUnitId ? { orgUnitId: resOrgUnitId } : {}), ...(resMgmtGroupId ? { managementLevelGroupId: resMgmtGroupId } : {}), ...(resMgmtLevelId ? { managementLevelId: resMgmtLevelId } : {}), resourceType: resResourceType, fte: fraction, chgResponsibility: true, enterpriseId: eid, ...(resClientUnitId ? { clientUnitId: resClientUnitId } : {}), }, }); resourceMap.set(eid, resource); } console.warn(`Resources: ${resourceMap.size} created`); // ── 5. Create projects ───────────────────────────────────────────────────── const projectMap = new Map(); for (const row of PROJECT_DATA) { const [shortCode, name, clientTag, , orderTypeStr, winProb, allocTypeStr, budgetCents] = row; const dates = PROJECT_DATES[shortCode]; if (!dates) { console.warn(`WARNING: no dates for project ${shortCode}, skipping`); continue; } const [startDateStr, endDateStr, status] = dates; // Dispo v2: utilizationCategory mapping const orderTypeToUtilCat: Record = { "CHARGEABLE": "Chg", "BD": "BD", "INTERNAL": "M&O", "OVERHEAD": "M&O", }; const projUtilCatId = utilCatMap.get(orderTypeToUtilCat[orderTypeStr] ?? "") ?? null; // Dispo v2: client mapping const clientTagToClient: Record = { "PAG": "Porsche AG", "BMW": "BMW Group", "DAI": "Daimler / Mercedes", "JLR": "Jaguar Land Rover", "INT": "Internal Projects", }; const projClientId = clientMap.get(clientTagToClient[clientTag] ?? "") ?? null; const project = await prisma.project.create({ data: { shortCode, name, orderType: parseOrderType(orderTypeStr), allocationType: parseAllocationType(allocTypeStr), winProbability: winProb, budgetCents, startDate: new Date(startDateStr), endDate: new Date(endDateStr), status, staffingReqs: [], dynamicFields: { clientUnit: clientTag }, blueprintId: projectBlueprint.id, ...(projUtilCatId ? { utilizationCategoryId: projUtilCatId } : {}), ...(projClientId ? { clientId: projClientId } : {}), }, }); projectMap.set(shortCode, project); } console.warn(`Projects: ${projectMap.size} created`); // ── 6. Create demand requirements + assignments ─────────────────────────── // Seed planning data for active and draft projects // Helper: compute dailyCostCents from resource LCR and hoursPerDay function seedDailyCost(lcrCents: number, hoursPerDay: number): number { return Math.round(lcrCents * hoursPerDay); } // Demand requirements (open demand / placeholder roles) interface DemandSeed { projectCode: string; roleName: string; start: string; end: string; hoursPerDay: number; percentage: number; headcount: number; status: AllocationStatus; } const DEMAND_SEEDS: DemandSeed[] = [ // PAG25G: Porsche Taycan Sport Film — needs 2 more 3D artists { projectCode: "PAG25G", roleName: "3D Artist", start: "2026-03-16", end: "2026-03-31", hoursPerDay: 8, percentage: 100, headcount: 2, status: AllocationStatus.PROPOSED }, // JLR26A: Jaguar EV Launch Film — open Art Director + PDM slot { projectCode: "JLR26A", roleName: "Art Director", start: "2026-02-02", end: "2026-05-29", hoursPerDay: 8, percentage: 100, headcount: 1, status: AllocationStatus.CONFIRMED }, { projectCode: "JLR26A", roleName: "PDM Specialist", start: "2026-03-02", end: "2026-06-30", hoursPerDay: 4, percentage: 50, headcount: 1, status: AllocationStatus.PROPOSED }, // DAI26B: AMG EV Reveal — needs Unreal Dev { projectCode: "DAI26B", roleName: "Unreal Dev", start: "2026-02-16", end: "2026-04-17", hoursPerDay: 8, percentage: 100, headcount: 1, status: AllocationStatus.PROPOSED }, // BMW26B: Draft — early demand planning { projectCode: "BMW26B", roleName: "3D Lead", start: "2026-05-04", end: "2026-06-26", hoursPerDay: 8, percentage: 100, headcount: 1, status: AllocationStatus.PROPOSED }, { projectCode: "BMW26B", roleName: "3D Artist", start: "2026-05-04", end: "2026-06-26", hoursPerDay: 8, percentage: 100, headcount: 3, status: AllocationStatus.PROPOSED }, { projectCode: "BMW26B", roleName: "Art Director", start: "2026-05-04", end: "2026-06-26", hoursPerDay: 4, percentage: 50, headcount: 1, status: AllocationStatus.PROPOSED }, // JLR26B: Draft film — early demand { projectCode: "JLR26B", roleName: "Project Manager",start: "2026-06-01", end: "2026-11-30", hoursPerDay: 8, percentage: 100, headcount: 1, status: AllocationStatus.PROPOSED }, { projectCode: "JLR26B", roleName: "3D Artist", start: "2026-06-01", end: "2026-11-30", hoursPerDay: 8, percentage: 100, headcount: 4, status: AllocationStatus.PROPOSED }, // DEV25B: Pipeline rebuild — open frontend dev slot { projectCode: "DEV25B", roleName: "Frontend Dev", start: "2026-01-05", end: "2026-04-30", hoursPerDay: 8, percentage: 100, headcount: 1, status: AllocationStatus.PROPOSED }, ]; let demandCount = 0; for (const d of DEMAND_SEEDS) { const project = projectMap.get(d.projectCode); const roleId = roleMap.get(d.roleName); if (!project) { console.warn(`WARN: demand skip — project ${d.projectCode} not found`); continue; } await prisma.demandRequirement.create({ data: { projectId: project.id, startDate: new Date(d.start), endDate: new Date(d.end), hoursPerDay: d.hoursPerDay, percentage: d.percentage, role: d.roleName, ...(roleId ? { roleId } : {}), headcount: d.headcount, status: d.status, metadata: {}, }, }); demandCount++; } console.warn(`Demand requirements: ${demandCount} created`); // Assignments (resource-backed allocations) interface AssignmentSeed { eid: string; projectCode: string; roleName: string; start: string; end: string; hoursPerDay: number; percentage: number; status: AllocationStatus; } const ASSIGNMENT_SEEDS: AssignmentSeed[] = [ // PAG25G: Porsche Taycan Sport Film { eid: "steve.rogers", projectCode: "PAG25G", roleName: "Project Manager", start: "2025-10-01", end: "2026-03-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "tony.stark", projectCode: "PAG25G", roleName: "3D Artist", start: "2025-10-13", end: "2026-03-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "sam.wilson", projectCode: "PAG25G", roleName: "3D Artist", start: "2025-11-03", end: "2026-03-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "vision.vision", projectCode: "PAG25G", roleName: "Art Director", start: "2025-10-01", end: "2026-03-31", hoursPerDay: 4, percentage: 50, status: AllocationStatus.ACTIVE }, { eid: "hank.mccoy", projectCode: "PAG25G", roleName: "PDM Specialist", start: "2026-01-05", end: "2026-03-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, // JLR26A: Jaguar EV Launch Film { eid: "natasha.romanoff",projectCode: "JLR26A", roleName: "Project Manager", start: "2026-01-05", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "james.barnes", projectCode: "JLR26A", roleName: "3D Artist", start: "2026-01-05", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "peter.parker", projectCode: "JLR26A", roleName: "3D Artist", start: "2026-01-19", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "miles.morales", projectCode: "JLR26A", roleName: "3D Artist", start: "2026-02-02", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, { eid: "groot.groot", projectCode: "JLR26A", roleName: "Art Director", start: "2026-01-05", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "clint.barton", projectCode: "JLR26A", roleName: "PDM Specialist", start: "2026-02-16", end: "2026-06-30", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, // DEV25B: Asset Library & Pipeline Rebuild { eid: "matt.murdock", projectCode: "DEV25B", roleName: "Unreal Dev", start: "2025-11-03", end: "2026-04-30", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "jessica.jones", projectCode: "DEV25B", roleName: "Unreal Dev", start: "2026-01-05", end: "2026-04-30", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, { eid: "frank.castle", projectCode: "DEV25B", roleName: "Frontend Dev", start: "2025-11-03", end: "2026-04-30", hoursPerDay: 4, percentage: 50, status: AllocationStatus.ACTIVE }, // DAI26B: AMG EV Reveal Campaign Stills { eid: "bruce.banner", projectCode: "DAI26B", roleName: "Project Manager", start: "2026-02-02", end: "2026-04-17", hoursPerDay: 4, percentage: 50, status: AllocationStatus.ACTIVE }, { eid: "thor.odinson", projectCode: "DAI26B", roleName: "3D Artist", start: "2026-02-02", end: "2026-04-17", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "erik.lehnsherr", projectCode: "DAI26B", roleName: "3D Artist", start: "2026-02-16", end: "2026-04-17", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, { eid: "drax.drax", projectCode: "DAI26B", roleName: "Art Director", start: "2026-02-02", end: "2026-04-17", hoursPerDay: 4, percentage: 50, status: AllocationStatus.ACTIVE }, { eid: "anna.marie", projectCode: "DAI26B", roleName: "PDM Specialist", start: "2026-03-02", end: "2026-04-17", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, // PAG26C: Porsche Carrera Grand Viz { eid: "charles.xavier", projectCode: "PAG26C", roleName: "Project Manager", start: "2026-02-16", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "jean.grey", projectCode: "PAG26C", roleName: "3D Artist", start: "2026-02-16", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "stephen.strange", projectCode: "PAG26C", roleName: "3D Artist", start: "2026-02-16", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, { eid: "jessica.drew", projectCode: "PAG26C", roleName: "3D Artist", start: "2026-03-02", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.CONFIRMED }, { eid: "tchalla.tchalla", projectCode: "PAG26C", roleName: "Art Director", start: "2026-02-16", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.ACTIVE }, { eid: "peter.quill", projectCode: "PAG26C", roleName: "PDM Specialist", start: "2026-03-02", end: "2026-04-24", hoursPerDay: 4, percentage: 50, status: AllocationStatus.PROPOSED }, // Cross-project partial allocations { eid: "wanda.maximoff", projectCode: "DAI26B", roleName: "3D Artist", start: "2026-03-03", end: "2026-04-17", hoursPerDay: 6, percentage: 75, status: AllocationStatus.PROPOSED }, { eid: "logan.howlett", projectCode: "PAG26C", roleName: "3D Artist", start: "2026-03-16", end: "2026-04-24", hoursPerDay: 8, percentage: 100, status: AllocationStatus.PROPOSED }, { eid: "wong.wong", projectCode: "PAG25G", roleName: "3D Artist", start: "2026-01-05", end: "2026-03-31", hoursPerDay: 4, percentage: 50, status: AllocationStatus.CONFIRMED }, { eid: "danny.rand", projectCode: "JLR26A", roleName: "Unreal Dev", start: "2026-03-02", end: "2026-07-31", hoursPerDay: 8, percentage: 100, status: AllocationStatus.PROPOSED }, { eid: "scott.lang", projectCode: "PAG25G", roleName: "3D Generalist", start: "2026-01-19", end: "2026-03-31", hoursPerDay: 4, percentage: 50, status: AllocationStatus.CONFIRMED }, // Draft project pre-allocations { eid: "carol.danvers", projectCode: "DEV26A", roleName: "Frontend Dev", start: "2026-05-04", end: "2026-08-28", hoursPerDay: 8, percentage: 100, status: AllocationStatus.PROPOSED }, { eid: "kamala.khan", projectCode: "DEV26A", roleName: "Frontend Dev", start: "2026-05-04", end: "2026-08-28", hoursPerDay: 8, percentage: 100, status: AllocationStatus.PROPOSED }, ]; let assignmentCount = 0; for (const a of ASSIGNMENT_SEEDS) { const resource = resourceMap.get(a.eid); const project = projectMap.get(a.projectCode); const roleId = roleMap.get(a.roleName); if (!resource || !project) { console.warn(`WARN: assignment skip — ${a.eid}/${a.projectCode} not found`); continue; } const dailyCost = seedDailyCost(resource.lcrCents, a.hoursPerDay); await prisma.assignment.create({ data: { resourceId: resource.id, projectId: project.id, startDate: new Date(a.start), endDate: new Date(a.end), hoursPerDay: a.hoursPerDay, percentage: a.percentage, role: a.roleName, ...(roleId ? { roleId } : {}), dailyCostCents: dailyCost, status: a.status, metadata: {}, }, }); assignmentCount++; } console.warn(`Assignments: ${assignmentCount} created`); // ── Vacations ────────────────────────────────────────────────────────────── const vacationEntries: Array<{ eid: string; type: VacationType; status: VacationStatus; start: string; end: string; note?: string; }> = [ { eid: "steve.rogers", type: VacationType.ANNUAL, status: VacationStatus.APPROVED, start: "2026-04-07", end: "2026-04-11", note: "Easter break" }, { eid: "tony.stark", type: VacationType.ANNUAL, status: VacationStatus.APPROVED, start: "2026-05-25", end: "2026-06-06", note: "Summer vacation" }, { eid: "natasha.romanoff", type: VacationType.SICK, status: VacationStatus.APPROVED, start: "2026-03-10", end: "2026-03-12" }, { eid: "bruce.banner", type: VacationType.ANNUAL, status: VacationStatus.PENDING, start: "2026-06-15", end: "2026-06-26", note: "Conference + holiday" }, { eid: "thor.odinson", type: VacationType.PUBLIC_HOLIDAY, status: VacationStatus.APPROVED, start: "2026-05-01", end: "2026-05-01", note: "Labour Day" }, { eid: "sam.wilson", type: VacationType.ANNUAL, status: VacationStatus.APPROVED, start: "2026-07-20", end: "2026-08-07", note: "Summer vacation" }, { eid: "peter.parker", type: VacationType.SICK, status: VacationStatus.APPROVED, start: "2026-03-18", end: "2026-03-19" }, { eid: "wanda.maximoff", type: VacationType.ANNUAL, status: VacationStatus.APPROVED, start: "2026-04-14", end: "2026-04-17", note: "Long weekend" }, { eid: "scott.lang", type: VacationType.ANNUAL, status: VacationStatus.PENDING, start: "2026-08-17", end: "2026-08-28", note: "Family trip" }, { eid: "james.barnes", type: VacationType.OTHER, status: VacationStatus.APPROVED, start: "2026-03-26", end: "2026-03-27", note: "Personal day" }, ]; let vacationCount = 0; for (const v of vacationEntries) { const res = resourceMap.get(v.eid); if (!res) continue; const isApproved = v.status === VacationStatus.APPROVED; await prisma.vacation.create({ data: { resourceId: res.id, type: v.type, status: v.status, startDate: new Date(v.start), endDate: new Date(v.end), ...(v.note ? { note: v.note } : {}), requestedById: manager.id, ...(isApproved ? { approvedById: manager.id, approvedAt: new Date() } : {}), }, }); vacationCount++; } console.warn(`Vacations: ${vacationCount} created`); // ── Calculation Rules (default set) ────────────────────────────────────────── await prisma.calculationRule.createMany({ data: [ { name: "Urlaub — Person chargeable, Projekt nicht belastet", description: "Vacation days count toward chargeability but are not charged to the project.", triggerType: "VACATION", costEffect: "ZERO", chargeabilityEffect: "COUNT", priority: 0, isActive: true, }, { name: "Krankheit — Person chargeable, Projekt nicht belastet", description: "Sick days count toward chargeability but are not charged to the project.", triggerType: "SICK", costEffect: "ZERO", chargeabilityEffect: "COUNT", priority: 0, isActive: true, }, { name: "Feiertag — kein Effekt", description: "Public holidays are neither chargeable nor charged to projects.", triggerType: "PUBLIC_HOLIDAY", costEffect: "ZERO", chargeabilityEffect: "SKIP", priority: 0, isActive: true, }, ], }); console.warn("Calculation rules: 3 default rules created"); console.warn("Seed complete!"); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => { void prisma.$disconnect(); });