290 lines
11 KiB
TypeScript
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(); });
|