580 lines
25 KiB
TypeScript
580 lines
25 KiB
TypeScript
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<string, string>();
|
|
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
|
|
{ id: "user-producer-001", name: "Bohdana Phillips", email: "bohdana.phillips@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-002", name: "Gaurav Singh", email: "gaurav.singh@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-003", name: "Ishan Jinsi", email: "ishan.jinsi@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-004", name: "Manan Aggarwal", email: "manan.aggarwal@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-005", name: "Mohd Iqbal", email: "mohd.iqbal@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-006", name: "Neha Sayana", email: "neha.sayana@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
{ id: "user-producer-007", name: "Vivek Singh", email: "vivek.singh@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
|
|
// 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<string, string>();
|
|
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<string, string[]> = {
|
|
"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<string, number>();
|
|
|
|
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<string, string[]> = {
|
|
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`);
|
|
|
|
// ─── Phase 8: Seed HP CG Standard Dynamic Pipeline Template ──
|
|
console.log("Seeding HP CG Standard dynamic pipeline template...");
|
|
|
|
const HP_CG_TEMPLATE_ID = "pipeline-hp-cg-standard";
|
|
|
|
const hpCgTemplate = await prisma.pipelineTemplate.upsert({
|
|
where: { id: HP_CG_TEMPLATE_ID },
|
|
update: {
|
|
name: "HP CG Standard",
|
|
description: "Standard HP CG production pipeline with 10 stages",
|
|
isDefault: true,
|
|
isArchived: false,
|
|
},
|
|
create: {
|
|
id: HP_CG_TEMPLATE_ID,
|
|
name: "HP CG Standard",
|
|
description: "Standard HP CG production pipeline with 10 stages",
|
|
organizationId: devOrg.id,
|
|
isDefault: true,
|
|
isArchived: false,
|
|
},
|
|
});
|
|
|
|
const HP_CG_STAGES = [
|
|
{ id: "stage-def-brief-intake", name: "Brief Intake", slug: "brief-intake", order: 1, isCriticalGate: true, isOptional: false, description: "Receive and review the project brief from HP", estimatedDays: 1 },
|
|
{ id: "stage-def-file-delivery", name: "File Delivery", slug: "file-delivery", order: 2, isCriticalGate: false, isOptional: true, description: "Receive source files (CAD, textures, reference materials)", estimatedDays: 1 },
|
|
{ id: "stage-def-model-prep", name: "Model Prep", slug: "model-prep", order: 3, isCriticalGate: true, isOptional: false, description: "Prepare 3D models for rendering pipeline", estimatedDays: 5 },
|
|
{ id: "stage-def-early-images", name: "Early Images", slug: "early-images", order: 4, isCriticalGate: false, isOptional: true, description: "Optional early preview renders for client feedback", estimatedDays: 1 },
|
|
{ id: "stage-def-catalog-images", name: "Catalog Images", slug: "catalog-images", order: 5, isCriticalGate: true, isOptional: false, description: "Standard catalog product imagery", estimatedDays: 1 },
|
|
{ id: "stage-def-hero-images", name: "Hero Images", slug: "hero-images", order: 6, isCriticalGate: false, isOptional: false, description: "High-impact hero product shots", estimatedDays: 2 },
|
|
{ id: "stage-def-packaging-images", name: "Packaging Images", slug: "packaging-images", order: 7, isCriticalGate: false, isOptional: false, description: "Product packaging renders", estimatedDays: 2 },
|
|
{ id: "stage-def-photocomps", name: "Photocomps", slug: "photocomps", order: 8, isCriticalGate: false, isOptional: false, description: "Photo composite renders with lifestyle backgrounds", estimatedDays: 3 },
|
|
{ id: "stage-def-360-spin", name: "360 Spin Animations", slug: "360-spin-animations", order: 9, isCriticalGate: false, isOptional: false, description: "Interactive 360-degree product spin animations", estimatedDays: 3.5 },
|
|
{ id: "stage-def-dynamic-spin", name: "Dynamic Spin", slug: "dynamic-spin", order: 10, isCriticalGate: false, isOptional: false, description: "Dynamic animated product spins with effects", estimatedDays: 7 },
|
|
{ id: "stage-def-misc", name: "Misc", slug: "misc", order: 11, isCriticalGate: false, isOptional: true, description: "Screenswaps, logo updates, file renaming, etc", estimatedDays: 2 },
|
|
];
|
|
|
|
const hpCgStageMap = new Map<string, string>();
|
|
for (const stage of HP_CG_STAGES) {
|
|
const created = await prisma.pipelineStageDefinition.upsert({
|
|
where: { pipelineId_slug: { pipelineId: hpCgTemplate.id, slug: stage.slug } },
|
|
update: { name: stage.name, order: stage.order, isCriticalGate: stage.isCriticalGate, isOptional: stage.isOptional, description: stage.description, estimatedDays: stage.estimatedDays },
|
|
create: { id: stage.id, pipelineId: hpCgTemplate.id, name: stage.name, slug: stage.slug, order: stage.order, isCriticalGate: stage.isCriticalGate, isOptional: stage.isOptional, description: stage.description, estimatedDays: stage.estimatedDays },
|
|
});
|
|
hpCgStageMap.set(stage.slug, created.id);
|
|
}
|
|
|
|
console.log(`Created/updated ${HP_CG_STAGES.length} HP CG Standard pipeline stages`);
|
|
|
|
// Clear and recreate HP CG Standard dependencies
|
|
await prisma.pipelineStageDependencyV2.deleteMany({
|
|
where: { stage: { pipelineId: hpCgTemplate.id } },
|
|
});
|
|
|
|
const HP_CG_DEPS: [string, string][] = [
|
|
["file-delivery", "brief-intake"],
|
|
["model-prep", "brief-intake"],
|
|
["model-prep", "file-delivery"],
|
|
["early-images", "model-prep"],
|
|
["catalog-images", "model-prep"],
|
|
["hero-images", "catalog-images"],
|
|
["packaging-images", "catalog-images"],
|
|
["photocomps", "catalog-images"],
|
|
["360-spin-animations", "catalog-images"],
|
|
["dynamic-spin", "catalog-images"],
|
|
["misc", "catalog-images"],
|
|
];
|
|
|
|
for (const [stageSlug, prereqSlug] of HP_CG_DEPS) {
|
|
const stageId = hpCgStageMap.get(stageSlug)!;
|
|
const prerequisiteId = hpCgStageMap.get(prereqSlug)!;
|
|
await prisma.pipelineStageDependencyV2.create({
|
|
data: { stageId, prerequisiteId },
|
|
});
|
|
}
|
|
|
|
console.log(`Created ${HP_CG_DEPS.length} HP CG Standard stage dependencies`);
|
|
|
|
console.log("Seed complete!");
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|