Files
XBid/prisma/seed.ts
2026-08-18 12:11:20 +02:00

290 lines
11 KiB
TypeScript

import { config } from "dotenv";
config();
import { PrismaClient, JobStatus } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import Decimal from "decimal.js";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const prisma = new PrismaClient({ adapter } as any);
async function main() {
console.log("🌱 Seeding database...");
// --- Rate Cards ---
const rateCardsData = [
{ name: "VFX Supervisor", department: "Leadership", defaultDailyRate: 1200, hoursPerDay: 8 },
{ name: "CG Supervisor", department: "Leadership", defaultDailyRate: 1100, hoursPerDay: 8 },
{ name: "Senior Compositor", department: "Compositing", defaultDailyRate: 950, hoursPerDay: 8 },
{ name: "Mid Compositor", department: "Compositing", defaultDailyRate: 750, hoursPerDay: 8 },
{ name: "Junior Compositor", department: "Compositing", defaultDailyRate: 500, hoursPerDay: 8 },
{ name: "Senior Animator", department: "Animation", defaultDailyRate: 900, hoursPerDay: 8 },
{ name: "Mid Animator", department: "Animation", defaultDailyRate: 700, hoursPerDay: 8 },
{ name: "Senior Modeller", department: "3D Art", defaultDailyRate: 850, hoursPerDay: 8 },
{ name: "Texture Artist", department: "3D Art", defaultDailyRate: 750, hoursPerDay: 8 },
{ name: "Lighting TD", department: "Lighting", defaultDailyRate: 900, hoursPerDay: 8 },
{ name: "FX Artist", department: "FX", defaultDailyRate: 900, hoursPerDay: 8 },
{ name: "Matchmove Artist", department: "Tracking", defaultDailyRate: 650, hoursPerDay: 8 },
{ name: "Roto Artist", department: "Prep", defaultDailyRate: 450, hoursPerDay: 8 },
{ name: "Paint Artist", department: "Prep", defaultDailyRate: 500, hoursPerDay: 8 },
{ name: "Pipeline TD", department: "Pipeline", defaultDailyRate: 1000, hoursPerDay: 8 },
];
const rateCards = await Promise.all(
rateCardsData.map((rc) =>
prisma.rateCard.upsert({
where: { id: `seed-rc-${rc.name.toLowerCase().replace(/\s+/g, "-")}` },
create: {
id: `seed-rc-${rc.name.toLowerCase().replace(/\s+/g, "-")}`,
name: rc.name,
department: rc.department,
defaultDailyRate: new Decimal(rc.defaultDailyRate),
hoursPerDay: new Decimal(rc.hoursPerDay),
currency: "ZAR",
isActive: true,
},
update: {},
})
)
);
console.log(` ✓ ${rateCards.length} rate cards`);
// --- Clients ---
const client1 = await prisma.client.upsert({
where: { id: "seed-client-01" },
create: {
id: "seed-client-01",
companyName: "Acme Studios",
contactPerson: "Jane Reynolds",
email: "jane@acmestudios.com",
phone: "+1 310 555 0100",
billingAddress: "1234 Sunset Blvd\nLos Angeles, CA 90028",
vatNumber: "US-123456789",
},
update: {},
});
const client2 = await prisma.client.upsert({
where: { id: "seed-client-02" },
create: {
id: "seed-client-02",
companyName: "Nebula Films",
contactPerson: "Tom Kavanagh",
email: "tom@nebulafilms.co.uk",
phone: "+44 20 7946 0100",
billingAddress: "22 Soho Square\nLondon, W1D 4QE",
},
update: {},
});
console.log(" ✓ 2 clients");
// --- Jobs ---
const job1 = await prisma.job.upsert({
where: { id: "seed-job-01" },
create: {
id: "seed-job-01",
projectTitle: "Feature Film VFX Package",
projectCode: "ACME-2024-001",
clientId: client1.id,
status: "DRAFT" as JobStatus,
currency: "ZAR",
markupPercent: new Decimal(10),
taxPercent: new Decimal(15),
discountPercent: new Decimal(0),
description:
"Full VFX package for Acme Studios' upcoming feature film including creature work, environments, and practical enhancement.",
termsAndConditions:
"Payment terms: 50% on approval, 50% on delivery. Prices valid for 30 days. Rates exclude VAT where applicable.",
},
update: {},
});
const shot1 = await prisma.shot.upsert({
where: { id: "seed-shot-01" },
create: {
id: "seed-shot-01",
jobId: job1.id,
shotCode: "SH0010",
durationSeconds: 4,
complexityMultiplier: new Decimal(1.5),
description: "Hero creature reveal — CG creature integration in live action environment",
sortOrder: 0,
},
update: {},
});
const shot2 = await prisma.shot.upsert({
where: { id: "seed-shot-02" },
create: {
id: "seed-shot-02",
jobId: job1.id,
shotCode: "SH0020",
durationSeconds: 2,
complexityMultiplier: new Decimal(1.0),
description: "Establishing environment — CG extension of practical set",
sortOrder: 1,
},
update: {},
});
const seniorComp = rateCards.find((r) => r.name === "Senior Compositor")!;
const lightingTD = rateCards.find((r) => r.name === "Lighting TD")!;
const fxArtist = rateCards.find((r) => r.name === "FX Artist")!;
const rotoArtist = rateCards.find((r) => r.name === "Roto Artist")!;
// Line items for shot 1
const lineItemsShot1 = [
{ taskName: "Roto / Paint", rateCardId: rotoArtist.id, artistRole: rotoArtist.name, dailyRate: 450, hoursPerDay: 8, numArtists: 1, numDays: 3, sortOrder: 0 },
{ taskName: "Lighting & Rendering", rateCardId: lightingTD.id, artistRole: lightingTD.name, dailyRate: 900, hoursPerDay: 8, numArtists: 1, numDays: 5, sortOrder: 1 },
{ taskName: "FX Simulation", rateCardId: fxArtist.id, artistRole: fxArtist.name, dailyRate: 900, hoursPerDay: 8, numArtists: 1, numDays: 4, sortOrder: 2 },
{ taskName: "Compositing", rateCardId: seniorComp.id, artistRole: seniorComp.name, dailyRate: 950, hoursPerDay: 8, numArtists: 1, numDays: 7, sortOrder: 3 },
];
for (const li of lineItemsShot1) {
await prisma.lineItem.upsert({
where: { id: `seed-li-s1-${li.sortOrder}` },
create: {
id: `seed-li-s1-${li.sortOrder}`,
shotId: shot1.id,
taskName: li.taskName,
rateCardId: li.rateCardId,
artistRole: li.artistRole,
dailyRate: new Decimal(li.dailyRate),
hoursPerDay: new Decimal(li.hoursPerDay),
numArtists: new Decimal(li.numArtists),
numDays: new Decimal(li.numDays),
quantityHours: new Decimal(0),
markupPercent: new Decimal(0),
discountPercent: new Decimal(0),
taxPercent: new Decimal(0),
hourlyRate: new Decimal(li.dailyRate / li.hoursPerDay),
subtotal: new Decimal(li.dailyRate * li.numDays * li.numArtists * 1.5),
markupAmount: new Decimal(0),
discountAmount: new Decimal(0),
taxAmount: new Decimal(0),
total: new Decimal(li.dailyRate * li.numDays * li.numArtists * 1.5),
sortOrder: li.sortOrder,
},
update: {},
});
}
// Line items for shot 2
const midComp = rateCards.find((r) => r.name === "Mid Compositor")!;
const lineItemsShot2 = [
{ taskName: "Roto / Paint", rateCardId: rotoArtist.id, artistRole: rotoArtist.name, dailyRate: 450, hoursPerDay: 8, numArtists: 2, numDays: 2, sortOrder: 0 },
{ taskName: "Compositing", rateCardId: midComp.id, artistRole: midComp.name, dailyRate: 750, hoursPerDay: 8, numArtists: 1, numDays: 4, sortOrder: 1 },
];
for (const li of lineItemsShot2) {
await prisma.lineItem.upsert({
where: { id: `seed-li-s2-${li.sortOrder}` },
create: {
id: `seed-li-s2-${li.sortOrder}`,
shotId: shot2.id,
taskName: li.taskName,
rateCardId: li.rateCardId,
artistRole: li.artistRole,
dailyRate: new Decimal(li.dailyRate),
hoursPerDay: new Decimal(li.hoursPerDay),
numArtists: new Decimal(li.numArtists),
numDays: new Decimal(li.numDays),
quantityHours: new Decimal(0),
markupPercent: new Decimal(0),
discountPercent: new Decimal(0),
taxPercent: new Decimal(0),
hourlyRate: new Decimal(li.dailyRate / li.hoursPerDay),
subtotal: new Decimal(li.dailyRate * li.numDays * li.numArtists),
markupAmount: new Decimal(0),
discountAmount: new Decimal(0),
taxAmount: new Decimal(0),
total: new Decimal(li.dailyRate * li.numDays * li.numArtists),
sortOrder: li.sortOrder,
},
update: {},
});
}
console.log(" ✓ 1 job with 2 shots and line items");
// --- Asset example on job 1 ---
const asset1 = await prisma.asset.upsert({
where: { id: "seed-asset-01" },
create: {
id: "seed-asset-01",
jobId: job1.id,
assetName: "Hero Creature",
assetType: "Character",
complexityMultiplier: new Decimal(2.0),
description: "Full CG creature, hero quality, 4K textures",
sortOrder: 0,
},
update: {},
});
const seniorModeller = rateCards.find((r) => r.name === "Senior Modeller")!;
const textureArtist = rateCards.find((r) => r.name === "Texture Artist")!;
const assetLineItems = [
{ taskName: "High Res Modelling", rateCardId: seniorModeller.id, artistRole: seniorModeller.name, dailyRate: 850, numDays: 8, numArtists: 1, sortOrder: 0 },
{ taskName: "Texturing & Look Dev", rateCardId: textureArtist.id, artistRole: textureArtist.name, dailyRate: 750, numDays: 5, numArtists: 1, sortOrder: 1 },
{ taskName: "Rigging", rateCardId: seniorModeller.id, artistRole: "Rigging Artist", dailyRate: 850, numDays: 6, numArtists: 1, sortOrder: 2 },
];
for (const li of assetLineItems) {
await prisma.lineItem.upsert({
where: { id: `seed-li-a1-${li.sortOrder}` },
create: {
id: `seed-li-a1-${li.sortOrder}`,
assetId: asset1.id,
taskName: li.taskName,
rateCardId: li.rateCardId,
artistRole: li.artistRole,
dailyRate: new Decimal(li.dailyRate),
hoursPerDay: new Decimal(8),
numArtists: new Decimal(li.numArtists),
numDays: new Decimal(li.numDays),
quantityHours: new Decimal(0),
markupPercent: new Decimal(0),
discountPercent: new Decimal(0),
taxPercent: new Decimal(0),
hourlyRate: new Decimal(li.dailyRate / 8),
subtotal: new Decimal(li.dailyRate * li.numDays * li.numArtists * 2.0),
markupAmount: new Decimal(0),
discountAmount: new Decimal(0),
taxAmount: new Decimal(0),
total: new Decimal(li.dailyRate * li.numDays * li.numArtists * 2.0),
sortOrder: li.sortOrder,
},
update: {},
});
}
console.log(" ✓ 1 asset with line items");
// Second job for client 2 (sent)
await prisma.job.upsert({
where: { id: "seed-job-02" },
create: {
id: "seed-job-02",
projectTitle: "TV Commercial Package",
projectCode: "NEBULA-2024-001",
clientId: client2.id,
status: "SENT" as JobStatus,
currency: "ZAR",
markupPercent: new Decimal(15),
taxPercent: new Decimal(15),
discountPercent: new Decimal(0),
description: "30-second commercial with full CG product visualization and environment.",
},
update: {},
});
console.log(" ✓ 1 additional job (sent)");
console.log("✅ Seeding complete!");
}
main()
.catch((e) => { console.error(e); process.exit(1); })
.finally(async () => { await prisma.$disconnect(); });