import "dotenv/config"; import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "../src/generated/prisma/client"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! }); const prisma = new PrismaClient({ adapter }); const STAGES = [ { name: "Brief Intake", slug: "brief-intake", order: 1, isCriticalGate: false, isOptional: false, description: "Receive and review the project brief from HP", estimatedDays: 1, }, { name: "File Delivery", slug: "file-delivery", order: 2, isCriticalGate: false, isOptional: false, description: "Receive source files (CAD, textures, reference materials)", estimatedDays: 1, }, { name: "Model Prep", slug: "model-prep", order: 3, isCriticalGate: true, isOptional: false, description: "Prepare 3D models for rendering pipeline", estimatedDays: 5, }, { name: "Early Images", slug: "early-images", order: 4, isCriticalGate: false, isOptional: true, description: "Optional early preview renders for client feedback", estimatedDays: 1, }, { name: "Catalog Images", slug: "catalog-images", order: 5, isCriticalGate: true, isOptional: false, description: "Standard catalog product imagery", estimatedDays: 1, }, { name: "Hero Images", slug: "hero-images", order: 6, isCriticalGate: false, isOptional: false, description: "High-impact hero product shots", estimatedDays: 2, }, { name: "Packaging Images", slug: "packaging-images", order: 7, isCriticalGate: false, isOptional: false, description: "Product packaging renders", estimatedDays: 2, }, { name: "Photocomps", slug: "photocomps", order: 8, isCriticalGate: false, isOptional: false, description: "Photo composite renders with lifestyle backgrounds", estimatedDays: 3, }, { name: "360 Spin Animations", slug: "360-spin-animations", order: 9, isCriticalGate: false, isOptional: false, description: "Interactive 360-degree product spin animations", estimatedDays: 3.5, }, { name: "Dynamic Spin", slug: "dynamic-spin", order: 10, isCriticalGate: false, isOptional: false, description: "Dynamic animated product spins with effects", estimatedDays: 7, }, ]; // Dependencies: [stageSlug, prerequisiteSlug] const DEPENDENCIES: [string, string][] = [ // File Delivery depends on Brief Intake ["file-delivery", "brief-intake"], // Model Prep depends on Brief Intake and File Delivery ["model-prep", "brief-intake"], ["model-prep", "file-delivery"], // Early Images depends on Model Prep (optional stage) ["early-images", "model-prep"], // Catalog Images depends on Model Prep (critical gate) ["catalog-images", "model-prep"], // Stages 6-10 all depend on Catalog Images (critical gate) ["hero-images", "catalog-images"], ["packaging-images", "catalog-images"], ["photocomps", "catalog-images"], ["360-spin-animations", "catalog-images"], ["dynamic-spin", "catalog-images"], ]; async function main() { console.log("Seeding pipeline stage templates..."); // Upsert stages const stageMap = new Map(); for (const stage of STAGES) { const created = await prisma.pipelineStageTemplate.upsert({ where: { slug: stage.slug }, update: stage, create: stage, }); stageMap.set(created.slug, created.id); } console.log(`Created/updated ${STAGES.length} pipeline stages`); // Clear existing dependencies and recreate await prisma.pipelineStageDependency.deleteMany(); for (const [stageSlug, prerequisiteSlug] of DEPENDENCIES) { const stageId = stageMap.get(stageSlug)!; const prerequisiteId = stageMap.get(prerequisiteSlug)!; await prisma.pipelineStageDependency.create({ data: { stageId, prerequisiteId }, }); } console.log(`Created ${DEPENDENCIES.length} stage dependencies`); // Seed dev organization and user (for local development with DEV_BYPASS_AUTH) const devOrg = await prisma.organization.upsert({ where: { id: "dev-org-001" }, update: {}, create: { id: "dev-org-001", name: "Dev Organization", domain: "dev.localhost", }, }); await prisma.user.upsert({ where: { id: "dev-user-001" }, update: { department: "CG Production", role: "ADMIN" }, create: { id: "dev-user-001", name: "Dev User", email: "dev@localhost", role: "ADMIN", department: "CG Production", organizationId: devOrg.id, }, }); // ─── Phase 6: Seed Team Members ─────────────────────── console.log("Seeding team members..."); const TEAM_MEMBERS: { id: string; name: string; email: string; role: "ADMIN" | "PRODUCER" | "ARTIST"; department: string; maxCapacity: number; }[] = [ // Producers // CGI Stills Artists { id: "user-artist-001", name: "Aditya Varma", email: "aditya.varma@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-002", name: "Ameya Bhagwat", email: "ameya.bhagwat@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-004", name: "Amit Sharma", email: "amit.sharma@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 }, { id: "user-artist-006", name: "Ankit Kumar", email: "ankit.kumar@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-010", name: "Bharat Bhushan", email: "bharat.bhushan@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-011", name: "Eric Rodriguez", email: "eric.rodriguez@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 8 }, { id: "user-artist-013", name: "Ishan Aneja", email: "ishan.aneja@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 }, { id: "user-artist-014", name: "Jinesh Thacker", email: "jinesh.thacker@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-015", name: "Juan Garcia", email: "juan.garcia@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-016", name: "Krishna Nand", email: "krishna.nand@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 4 }, { id: "user-artist-019", name: "Nizam P", email: "nizam.p@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 }, { id: "user-artist-021", name: "Prateek Kaushik", email: "prateek.kaushik@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 8 }, { id: "user-artist-025", name: "Xavier Plasso", email: "xavier.plasso@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 }, { id: "user-artist-026", name: "Yash Vaidya", email: "yash.vaidya@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 4 }, // CGI Animation Artists { id: "user-artist-003", name: "Ameya Kandivkar", email: "ameya.kandivkar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-007", name: "Ankit Kumar Gupta", email: "ankit.gupta@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-009", name: "Babon Ghosh", email: "babon.ghosh@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-012", name: "Hujef Bagwan", email: "hujef.bagwan@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-018", name: "Niteen Veer", email: "niteen.veer@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-020", name: "Pankaj Duragkar", email: "pankaj.duragkar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, { id: "user-artist-022", name: "Sandeep Sidhu", email: "sandeep.sidhu@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 8 }, { id: "user-artist-024", name: "Sonu Kumar", email: "sonu.kumar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 }, // Model Prep Artists { id: "user-artist-005", name: "Anantha Krishnan", email: "anantha.krishnan@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 8 }, { id: "user-artist-008", name: "Arun Prakash", email: "arun.prakash@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 5 }, { id: "user-artist-017", name: "Nijil Rajithan", email: "nijil.rajithan@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 5 }, { id: "user-artist-023", name: "Soham Baviskar", email: "soham.baviskar@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 4 }, ]; for (const member of TEAM_MEMBERS) { await prisma.user.upsert({ where: { id: member.id }, update: { name: member.name, department: member.department, maxCapacity: member.maxCapacity, role: member.role }, create: { id: member.id, name: member.name, email: member.email, role: member.role, department: member.department, maxCapacity: member.maxCapacity, organizationId: devOrg.id, }, }); } console.log(`Created/updated ${TEAM_MEMBERS.length} team members`); console.log("Created dev organization and users"); // ─── Phase 6: Seed Skills ───────────────────────────── console.log("Seeding skills..."); const SKILLS = [ "Model Prep", "Lighting", "Photocomp", "Animation", ]; const skillMap = new Map(); for (const name of SKILLS) { const skill = await prisma.skill.upsert({ where: { name }, update: {}, create: { name }, }); skillMap.set(skill.name, skill.id); } console.log(`Created/updated ${SKILLS.length} skills`); // Map stage templates to recommended skills // Format: [stageSlug, [skillName, importance (1-3)]] const STAGE_SKILL_MAP: [string, [string, number][]][] = [ ["brief-intake", []], ["file-delivery", []], ["model-prep", [["Model Prep", 3]]], ["early-images", [["Lighting", 2]]], ["catalog-images", [["Lighting", 3]]], ["hero-images", [["Lighting", 3]]], ["packaging-images", [["Lighting", 2]]], ["photocomps", [["Photocomp", 3], ["Lighting", 1]]], ["360-spin-animations", [["Animation", 3], ["Lighting", 2]]], ["dynamic-spin", [["Animation", 3], ["Lighting", 2]]], ]; // Clear existing stage skill requirements await prisma.stageSkillRequirement.deleteMany(); for (const [slug, skillReqs] of STAGE_SKILL_MAP) { const templateId = stageMap.get(slug); if (!templateId || skillReqs.length === 0) continue; for (const [skillName, importance] of skillReqs) { const skillId = skillMap.get(skillName); if (!skillId) continue; await prisma.stageSkillRequirement.create({ data: { stageTemplateId: templateId, skillId, importance }, }); } } const totalReqs = STAGE_SKILL_MAP.reduce((sum, [, reqs]) => sum + reqs.length, 0); console.log(`Created ${totalReqs} stage skill requirements`); // ─── Phase 6: Assign Skills to Team Members ─────────── console.log("Assigning skills to team members..."); // Clear existing user skills await prisma.userSkill.deleteMany(); // [userId, [skillName, level]] const USER_SKILLS: [string, [string, "JUNIOR" | "INTERMEDIATE" | "SENIOR" | "LEAD"][]][] = [ // ── CGI Stills ──────────────────────────────────────── // Aditya Varma — Senior, Photocomps specialty ["user-artist-001", [["Lighting", "SENIOR"], ["Photocomp", "SENIOR"]]], // Ameya Bhagwat — Senior ["user-artist-002", [["Lighting", "SENIOR"]]], // Amit Sharma — Intermediate ["user-artist-004", [["Lighting", "INTERMEDIATE"]]], // Ankit Kumar — Senior ["user-artist-006", [["Lighting", "SENIOR"]]], // Bharat Bhushan — Senior ["user-artist-010", [["Lighting", "SENIOR"]]], // Eric Rodriguez — Stills Team Lead ["user-artist-011", [["Lighting", "LEAD"]]], // Ishan Aneja — Intermediate ["user-artist-013", [["Lighting", "INTERMEDIATE"]]], // Jinesh Thacker — Senior ["user-artist-014", [["Lighting", "SENIOR"]]], // Juan Garcia — Senior ["user-artist-015", [["Lighting", "SENIOR"]]], // Krishna Nand — Entry/Junior ["user-artist-016", [["Lighting", "JUNIOR"]]], // Nizam P — Intermediate ["user-artist-019", [["Lighting", "INTERMEDIATE"]]], // Prateek Kaushik — Stills Team Lead ["user-artist-021", [["Lighting", "LEAD"]]], // Xavier Plasso — Senior ["user-artist-025", [["Lighting", "SENIOR"]]], // Yash Vaidya — Entry/Junior ["user-artist-026", [["Lighting", "JUNIOR"]]], // ── CGI Animation ───────────────────────────────────── // Ameya Kandivkar — Intermediate ["user-artist-003", [["Animation", "INTERMEDIATE"]]], // Ankit Kumar Gupta — Intermediate ["user-artist-007", [["Animation", "INTERMEDIATE"]]], // Babon Ghosh — Intermediate ["user-artist-009", [["Animation", "INTERMEDIATE"]]], // Hujef Bagwan — Intermediate ["user-artist-012", [["Animation", "INTERMEDIATE"]]], // Niteen Veer — Intermediate ["user-artist-018", [["Animation", "INTERMEDIATE"]]], // Pankaj Duragkar — Intermediate ["user-artist-020", [["Animation", "INTERMEDIATE"]]], // Sandeep Sidhu — Animation Team Lead ["user-artist-022", [["Animation", "LEAD"]]], // Sonu Kumar — Intermediate ["user-artist-024", [["Animation", "INTERMEDIATE"]]], // ── Model Prep ──────────────────────────────────────── // Anantha Krishnan — Model Prep Team Lead ["user-artist-005", [["Model Prep", "LEAD"]]], // Arun Prakash — Intermediate ["user-artist-008", [["Model Prep", "INTERMEDIATE"]]], // Nijil Rajithan — Intermediate ["user-artist-017", [["Model Prep", "INTERMEDIATE"]]], // Soham Baviskar — Entry/Junior ["user-artist-023", [["Model Prep", "JUNIOR"]]], ]; let userSkillCount = 0; for (const [userId, skills] of USER_SKILLS) { for (const [skillName, level] of skills) { const skillId = skillMap.get(skillName); if (!skillId) continue; await prisma.userSkill.create({ data: { userId, skillId, level }, }); userSkillCount++; } } console.log(`Created ${userSkillCount} user skill assignments`); // ─── Phase 6: Seed Sample Assignments ───────────────── console.log("Seeding sample stage assignments..."); // Get active project stages to assign const activeStages = await prisma.deliverableStage.findMany({ where: { status: { in: ["IN_PROGRESS", "IN_REVIEW", "NOT_STARTED"] }, deliverable: { project: { organizationId: devOrg.id, status: "ACTIVE" } }, }, include: { template: true }, take: 60, // grab a decent sample orderBy: { createdAt: "asc" }, }); // Map stages to appropriate artists by stage type const stageToArtists: Record = { "model-prep": ["user-artist-005", "user-artist-008", "user-artist-017", "user-artist-023"], "early-images": ["user-artist-002", "user-artist-006", "user-artist-010"], "catalog-images": ["user-artist-011", "user-artist-021", "user-artist-014", "user-artist-015", "user-artist-002"], "hero-images": ["user-artist-011", "user-artist-021", "user-artist-001", "user-artist-002"], "packaging-images": ["user-artist-006", "user-artist-010", "user-artist-013", "user-artist-019"], "photocomps": ["user-artist-001", "user-artist-015", "user-artist-014"], "360-spin-animations": ["user-artist-022", "user-artist-003", "user-artist-009", "user-artist-024"], "dynamic-spin": ["user-artist-022", "user-artist-020", "user-artist-018", "user-artist-007"], }; // Clear existing assignments (to allow re-runs) await prisma.stageAssignment.deleteMany(); let assignmentCount = 0; const artistAssignmentCounts = new Map(); for (const stage of activeStages) { const candidates = stageToArtists[stage.template.slug]; if (!candidates || candidates.length === 0) continue; // Pick the artist with the least assignments so far let bestArtist = candidates[0]; let lowestCount = artistAssignmentCounts.get(bestArtist) || 0; for (const candidate of candidates) { const count = artistAssignmentCounts.get(candidate) || 0; if (count < lowestCount) { bestArtist = candidate; lowestCount = count; } } // Don't exceed maxCapacity (roughly) const member = TEAM_MEMBERS.find((m) => m.id === bestArtist); if (member && lowestCount >= member.maxCapacity) continue; try { await prisma.stageAssignment.create({ data: { deliverableStageId: stage.id, userId: bestArtist, role: "LEAD", }, }); artistAssignmentCounts.set(bestArtist, lowestCount + 1); assignmentCount++; } catch { // Skip duplicate assignments } } console.log(`Created ${assignmentCount} stage assignments`); // ─── Phase 2: Seed RBAC Default Permissions ────────── console.log("Seeding RBAC default permissions..."); const DEFAULT_PERMISSIONS: Record = { ADMIN: [ "PROJECT_CREATE", "PROJECT_UPDATE", "PROJECT_DELETE", "PROJECT_VIEW", "DELIVERABLE_CREATE", "DELIVERABLE_UPDATE", "DELIVERABLE_DELETE", "STAGE_UPDATE_STATUS", "STAGE_ASSIGN", "STAGE_SCHEDULE", "REVISION_CREATE", "REVISION_REVIEW", "COMMENT_CREATE", "COMMENT_DELETE_ANY", "PIPELINE_MANAGE", "USER_MANAGE", "ROLE_MANAGE", "ORG_SETTINGS", "AUTOMATION_MANAGE", "FIELD_CUSTOMIZE", ], PRODUCER: [ "PROJECT_CREATE", "PROJECT_UPDATE", "PROJECT_VIEW", "DELIVERABLE_CREATE", "DELIVERABLE_UPDATE", "DELIVERABLE_DELETE", "STAGE_UPDATE_STATUS", "STAGE_ASSIGN", "STAGE_SCHEDULE", "REVISION_CREATE", "REVISION_REVIEW", "COMMENT_CREATE", "COMMENT_DELETE_ANY", "AUTOMATION_MANAGE", ], ARTIST: [ "PROJECT_VIEW", "STAGE_UPDATE_STATUS", "REVISION_CREATE", "COMMENT_CREATE", ], }; // Clear existing and re-seed await prisma.orgRolePermission.deleteMany({ where: { organizationId: devOrg.id }, }); const permData: { organizationId: string; role: any; permission: any }[] = []; for (const [role, permissions] of Object.entries(DEFAULT_PERMISSIONS)) { for (const permission of permissions) { permData.push({ organizationId: devOrg.id, role, permission }); } } await prisma.orgRolePermission.createMany({ data: permData }); console.log(`Created ${permData.length} role permission entries for dev org`); console.log("Seed complete!"); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });