import "dotenv/config" import pg from "pg" import { PrismaPg } from "@prisma/adapter-pg" import { PrismaClient } from "../src/generated/prisma/client.ts" const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) const adapter = new PrismaPg(pool) const prisma = new PrismaClient({ adapter }) function daysFromNow(days: number): Date { return new Date(Date.now() + days * 86400000) } function daysAgo(days: number): Date { return new Date(Date.now() - days * 86400000) } async function main() { // ── Organisation ── const org = await prisma.organization.upsert({ where: { slug: "demo-charity" }, update: { bankName: "Barclays", bankSortCode: "20-00-00", bankAccountNo: "12345678", bankAccountName: "Charity Right", }, create: { name: "Charity Right", slug: "demo-charity", country: "UK", timezone: "Europe/London", bankName: "Barclays", bankSortCode: "20-00-00", bankAccountNo: "12345678", bankAccountName: "Charity Right", refPrefix: "DEMO", primaryColor: "#1e40af", }, }) // ── Admin user ── await prisma.user.upsert({ where: { email: "admin@charityright.org" }, update: {}, create: { email: "admin@charityright.org", name: "Azreen Jamal", role: "org_admin", organizationId: org.id, }, }) // ── Events ── const galaEvent = await prisma.event.upsert({ where: { organizationId_slug: { organizationId: org.id, slug: "ramadan-gala-2026" } }, update: { name: "Ramadan Gala 2026", eventDate: daysFromNow(14), goalAmount: 5000000 }, create: { name: "Ramadan Gala 2026", slug: "ramadan-gala-2026", description: "Annual fundraising gala dinner — all proceeds support orphan education in Bangladesh, Pakistan, and Syria.", eventDate: daysFromNow(14), location: "Bradford Hilton, Hall Lane, BD1 4QR", goalAmount: 5000000, // £50,000 currency: "GBP", status: "active", organizationId: org.id, }, }) const eidEvent = await prisma.event.upsert({ where: { organizationId_slug: { organizationId: org.id, slug: "eid-community-lunch-2026" } }, update: {}, create: { name: "Eid Community Lunch 2026", slug: "eid-community-lunch-2026", description: "Community lunch and fundraiser for local food bank programme.", eventDate: daysFromNow(45), location: "East London Mosque, Whitechapel Road, E1 1JX", goalAmount: 1500000, // £15,000 currency: "GBP", status: "active", organizationId: org.id, }, }) // ── QR Sources for Gala ── const qrCodes = [ { label: "Table 1 - Ahmed", volunteerName: "Ahmed Khan", tableName: "Table 1", code: "gala-tbl1" }, { label: "Table 2 - Fatima", volunteerName: "Fatima Patel", tableName: "Table 2", code: "gala-tbl2" }, { label: "Table 3 - Yusuf", volunteerName: "Yusuf Ali", tableName: "Table 3", code: "gala-tbl3" }, { label: "Table 4 - Khadijah", volunteerName: "Khadijah Begum", tableName: "Table 4", code: "gala-tbl4" }, { label: "Table 5 - Omar", volunteerName: "Omar Malik", tableName: "Table 5", code: "gala-tbl5" }, { label: "Main Entrance", volunteerName: null, tableName: null, code: "gala-entrance" }, { label: "Stage Banner", volunteerName: null, tableName: null, code: "gala-stage" }, { label: "Online Link", volunteerName: null, tableName: null, code: "gala-online" }, ] const qrSourceIds: Record = {} for (const qr of qrCodes) { const source = await prisma.qrSource.upsert({ where: { code: qr.code }, update: { label: qr.label, volunteerName: qr.volunteerName, scanCount: Math.floor(Math.random() * 40) + 5 }, create: { label: qr.label, code: qr.code, volunteerName: qr.volunteerName, tableName: qr.tableName, eventId: galaEvent.id, scanCount: Math.floor(Math.random() * 40) + 5, }, }) qrSourceIds[qr.code] = source.id } // ── QR Sources for Eid ── const eidQrs = [ { label: "Registration Desk", volunteerName: "Ibrahim Hassan", tableName: null, code: "eid-reg" }, { label: "Online Link", volunteerName: null, tableName: null, code: "eid-online" }, ] for (const qr of eidQrs) { await prisma.qrSource.upsert({ where: { code: qr.code }, update: {}, create: { label: qr.label, code: qr.code, volunteerName: qr.volunteerName, tableName: qr.tableName, eventId: eidEvent.id, scanCount: Math.floor(Math.random() * 10) + 2, }, }) } // ── Sample Pledges ── const samplePledges = [ // Paid pledges { name: "Sarah Khan", email: "sarah@example.com", phone: "07700900001", amount: 10000, rail: "bank", status: "paid", giftAid: true, qr: "gala-tbl1", daysAgo: 5 }, { name: "Ali Hassan", email: "ali.hassan@gmail.com", phone: "07700900002", amount: 25000, rail: "bank", status: "paid", giftAid: false, qr: "gala-tbl1", daysAgo: 4 }, { name: "Amina Begum", email: "amina.b@hotmail.com", phone: "", amount: 5000, rail: "card", status: "paid", giftAid: true, qr: "gala-tbl2", daysAgo: 3 }, { name: "Mohammed Raza", email: "m.raza@outlook.com", phone: "07700900004", amount: 50000, rail: "gocardless", status: "paid", giftAid: true, qr: "gala-stage", daysAgo: 6 }, { name: "Zainab Ahmed", email: "zainab@example.com", phone: "", amount: 10000, rail: "bank", status: "paid", giftAid: false, qr: "gala-tbl3", daysAgo: 7 }, { name: "Hassan Malik", email: "hassan.malik@gmail.com", phone: "07700900006", amount: 20000, rail: "card", status: "paid", giftAid: true, qr: "gala-entrance", daysAgo: 2 }, // Initiated (payment in progress) { name: "Ruqayyah Patel", email: "ruqayyah@example.com", phone: "07700900007", amount: 15000, rail: "bank", status: "initiated", giftAid: true, qr: "gala-tbl4", daysAgo: 1 }, { name: "Ibrahim Shah", email: "ibrahim.shah@gmail.com", phone: "", amount: 10000, rail: "gocardless", status: "initiated", giftAid: false, qr: "gala-tbl5", daysAgo: 1 }, // New pledges (just created) { name: "Maryam Siddiqui", email: "maryam.s@yahoo.com", phone: "07700900009", amount: 5000, rail: "bank", status: "new", giftAid: false, qr: "gala-tbl2", daysAgo: 0 }, { name: "Usman Chaudhry", email: "usman.c@gmail.com", phone: "", amount: 100000, rail: "bank", status: "new", giftAid: true, qr: "gala-entrance", daysAgo: 0 }, { name: "Aisha Rahman", email: "aisha.r@hotmail.com", phone: "07700900011", amount: 7500, rail: "card", status: "new", giftAid: true, qr: "gala-online", daysAgo: 0 }, { name: null, email: "anon.donor@gmail.com", phone: "", amount: 20000, rail: "bank", status: "new", giftAid: false, qr: "gala-tbl3", daysAgo: 0 }, // Overdue { name: "Tariq Hussain", email: "tariq.h@example.com", phone: "07700900013", amount: 25000, rail: "bank", status: "overdue", giftAid: true, qr: "gala-tbl1", daysAgo: 12 }, { name: "Nadia Akhtar", email: "nadia.a@outlook.com", phone: "", amount: 10000, rail: "bank", status: "overdue", giftAid: false, qr: "gala-tbl5", daysAgo: 10 }, // Cancelled { name: "Omar Farooq", email: "omar.f@gmail.com", phone: "07700900015", amount: 5000, rail: "card", status: "cancelled", giftAid: false, qr: "gala-tbl4", daysAgo: 8 }, // FPX pledge (Malaysian donor) { name: "Ahmad bin Abdullah", email: "ahmad@example.my", phone: "+60123456789", amount: 50000, rail: "fpx", status: "paid", giftAid: false, qr: "gala-online", daysAgo: 3 }, // Eid event pledges { name: "Hafsa Nawaz", email: "hafsa@example.com", phone: "07700900017", amount: 5000, rail: "bank", status: "new", giftAid: true, qr: null, daysAgo: 1 }, { name: "Bilal Iqbal", email: "bilal.i@gmail.com", phone: "", amount: 10000, rail: "gocardless", status: "paid", giftAid: false, qr: null, daysAgo: 5 }, ] let pledgeIndex = 0 for (const p of samplePledges) { pledgeIndex++ const ref = `DEMO-SEED${String(pledgeIndex).padStart(2, "0")}-${Math.floor(p.amount / 100)}` const isEid = p.qr === null const eventId = isEid ? eidEvent.id : galaEvent.id const createdAt = daysAgo(p.daysAgo) const paidAt = p.status === "paid" ? daysAgo(Math.max(p.daysAgo - 1, 0)) : null // Skip if reference already exists const existing = await prisma.pledge.findUnique({ where: { reference: ref } }) if (existing) continue const pledge = await prisma.pledge.create({ data: { reference: ref, amountPence: p.amount, currency: "GBP", rail: p.rail, status: p.status, donorName: p.name, donorEmail: p.email || null, donorPhone: p.phone || null, giftAid: p.giftAid, eventId, qrSourceId: p.qr ? qrSourceIds[p.qr] || null : null, organizationId: org.id, createdAt, paidAt, cancelledAt: p.status === "cancelled" ? daysAgo(p.daysAgo - 1) : null, }, }) // Payment instruction for bank transfers if (p.rail === "bank") { await prisma.paymentInstruction.create({ data: { pledgeId: pledge.id, bankReference: ref, bankDetails: { bankName: "Barclays", sortCode: "20-00-00", accountNo: "12345678", accountName: "Charity Right", }, }, }) } // Payment record for paid pledges if (p.status === "paid") { await prisma.payment.create({ data: { pledgeId: pledge.id, provider: p.rail === "gocardless" ? "gocardless" : p.rail === "card" || p.rail === "fpx" ? "stripe" : "bank", providerRef: p.rail === "bank" ? null : `sim_${pledge.id.slice(0, 8)}`, amountPence: p.amount, status: "confirmed", matchedBy: p.rail === "bank" ? "auto" : "webhook", receivedAt: paidAt, }, }) } // Reminders for non-paid pledges if (["new", "initiated", "overdue"].includes(p.status)) { const steps = [ { step: 0, delayDays: 0, key: "instructions" }, { step: 1, delayDays: 2, key: "gentle_nudge" }, { step: 2, delayDays: 7, key: "urgency_impact" }, { step: 3, delayDays: 14, key: "final_reminder" }, ] for (const s of steps) { const scheduledAt = new Date(createdAt.getTime() + s.delayDays * 86400000) const isSent = scheduledAt < new Date() && p.status !== "new" await prisma.reminder.create({ data: { pledgeId: pledge.id, step: s.step, channel: "email", scheduledAt, status: p.status === "overdue" && s.step <= 2 ? "sent" : isSent ? "sent" : "pending", sentAt: isSent ? scheduledAt : null, payload: { templateKey: s.key }, }, }) } } // Analytics events await prisma.analyticsEvent.create({ data: { eventType: "pledge_completed", pledgeId: pledge.id, eventId, qrSourceId: p.qr ? qrSourceIds[p.qr] || null : null, metadata: { amountPence: p.amount, rail: p.rail }, createdAt, }, }) } // ── Funnel analytics (scans → starts → completions) ── const funnelEvents = [ ...Array.from({ length: 45 }, () => ({ eventType: "pledge_start", eventId: galaEvent.id })), ...Array.from({ length: 8 }, () => ({ eventType: "pledge_start", eventId: eidEvent.id })), ...Array.from({ length: 12 }, () => ({ eventType: "instruction_copy_clicked", eventId: galaEvent.id })), ...Array.from({ length: 6 }, () => ({ eventType: "i_paid_clicked", eventId: galaEvent.id })), ] for (const fe of funnelEvents) { await prisma.analyticsEvent.create({ data: { eventType: fe.eventType, eventId: fe.eventId, createdAt: daysAgo(Math.floor(Math.random() * 7)), }, }) } // Count totals const pledgeCount = await prisma.pledge.count({ where: { organizationId: org.id } }) const totalAmount = await prisma.pledge.aggregate({ where: { organizationId: org.id }, _sum: { amountPence: true } }) console.log("✅ Seed data created") console.log(` Org: ${org.name} (${org.slug})`) console.log(` Events: ${galaEvent.name}, ${eidEvent.name}`) console.log(` QR Codes: ${qrCodes.length + eidQrs.length}`) console.log(` Pledges: ${pledgeCount} (£${((totalAmount._sum.amountPence || 0) / 100).toLocaleString()})`) } main() .catch(console.error) .finally(async () => { await prisma.$disconnect() await pool.end() })