108 lines
6.3 KiB
TypeScript
108 lines
6.3 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
||
import bcrypt from 'bcryptjs';
|
||
import { zonedTimeToUtc, zonedParts } from '../lib/slots';
|
||
|
||
const db = new PrismaClient();
|
||
const TZ = process.env.SITE_TIMEZONE || 'UTC';
|
||
|
||
const SERVICES = [
|
||
{ id: 'svc-consult', slug: 'consultation', name: 'Initial Consultation', durationMin: 30, priceCents: 7500, description: 'A focused 30-minute session to assess your goals and plan next steps.' },
|
||
{ id: 'svc-therapy', slug: 'therapy-session', name: 'Therapy Session', durationMin: 60, priceCents: 12000, description: 'A full one-hour hands-on therapy session with a senior specialist.' },
|
||
{ id: 'svc-training', slug: 'personal-training', name: 'Personal Training', durationMin: 45, priceCents: 6000, description: 'A 45-minute one-to-one training session tailored to your routine.' },
|
||
];
|
||
|
||
const STAFF = [
|
||
{ id: 'staff-alex', slug: 'alex-rivera', name: 'Alex Rivera', title: 'Senior Specialist', avatarColor: '#4f46e5', bio: 'Twelve years of clinical practice and a calm, methodical approach.', services: ['svc-consult', 'svc-therapy', 'svc-training'] },
|
||
{ id: 'staff-sam', slug: 'sam-patel', name: 'Sam Patel', title: 'Wellness Coach', avatarColor: '#0d9488', bio: 'Strength and mobility coach focused on sustainable progress.', services: ['svc-consult', 'svc-training'] },
|
||
];
|
||
|
||
// Mon–Fri, 09:00–17:00 (weekday 1..5).
|
||
const WEEKDAY_HOURS = [1, 2, 3, 4, 5].map((weekday) => ({ weekday, startMin: 9 * 60, endMin: 17 * 60 }));
|
||
|
||
/** A wall-clock date `offsetDays` from today (in the site timezone) at hour:minute. */
|
||
function at(offsetDays: number, hour: number, minute = 0): Date {
|
||
const nowParts = zonedParts(new Date(), TZ);
|
||
const base = new Date(Date.UTC(nowParts.year, nowParts.month - 1, nowParts.day + offsetDays));
|
||
return zonedTimeToUtc({ year: base.getUTCFullYear(), month: base.getUTCMonth() + 1, day: base.getUTCDate() }, hour * 60 + minute, TZ);
|
||
}
|
||
|
||
async function main() {
|
||
const password = await bcrypt.hash('password', 10);
|
||
|
||
const admin = await db.user.upsert({
|
||
where: { email: 'admin@example.com' },
|
||
update: { role: 'admin' },
|
||
create: { email: 'admin@example.com', name: 'Booking Admin', role: 'admin', passwordHash: password },
|
||
});
|
||
const customer = await db.user.upsert({
|
||
where: { email: 'user@example.com' },
|
||
update: {},
|
||
create: { email: 'user@example.com', name: 'Demo Customer', role: 'user', passwordHash: password },
|
||
});
|
||
|
||
await db.subscription.upsert({ where: { userId: admin.id }, update: {}, create: { userId: admin.id, plan: 'pro', status: 'active', provider: 'mock' } });
|
||
await db.subscription.upsert({ where: { userId: customer.id }, update: {}, create: { userId: customer.id, plan: 'free', status: 'active', provider: 'mock' } });
|
||
|
||
const serviceIds = new Map<string, string>();
|
||
for (const s of SERVICES) {
|
||
const row = await db.service.upsert({
|
||
where: { slug: s.slug },
|
||
update: { name: s.name, description: s.description, durationMin: s.durationMin, priceCents: s.priceCents, active: true },
|
||
create: { id: s.id, slug: s.slug, name: s.name, description: s.description, durationMin: s.durationMin, priceCents: s.priceCents },
|
||
});
|
||
serviceIds.set(s.id, row.id);
|
||
}
|
||
|
||
const staffIds = new Map<string, string>();
|
||
for (const s of STAFF) {
|
||
const row = await db.staff.upsert({
|
||
where: { slug: s.slug },
|
||
update: { name: s.name, title: s.title, bio: s.bio, avatarColor: s.avatarColor },
|
||
create: { id: s.id, slug: s.slug, name: s.name, title: s.title, bio: s.bio, avatarColor: s.avatarColor },
|
||
});
|
||
staffIds.set(s.id, row.id);
|
||
|
||
// Reset this member's schedule and service offering.
|
||
await db.staffService.deleteMany({ where: { staffId: row.id } });
|
||
await db.workingHour.deleteMany({ where: { staffId: row.id } });
|
||
await db.staffService.createMany({ data: s.services.map((sid) => ({ staffId: row.id, serviceId: serviceIds.get(sid)! })) });
|
||
await db.workingHour.createMany({ data: WEEKDAY_HOURS.map((h) => ({ staffId: row.id, ...h })) });
|
||
}
|
||
|
||
// Refresh the demo bookings so they always sit around "today".
|
||
const svc = (id: string) => serviceIds.get(id)!;
|
||
const who = (id: string) => staffIds.get(id)!;
|
||
await db.booking.deleteMany({});
|
||
await db.booking.createMany({
|
||
data: [
|
||
{ serviceId: svc('svc-therapy'), staffId: who('staff-alex'), userId: customer.id, startsAt: at(0, 14, 0), endsAt: at(0, 15, 0), status: 'confirmed', customerName: 'Demo Customer', customerEmail: customer.email, notes: 'First visit.' },
|
||
{ serviceId: svc('svc-consult'), staffId: who('staff-sam'), userId: customer.id, startsAt: at(1, 10, 0), endsAt: at(1, 10, 30), status: 'pending', customerName: 'Demo Customer', customerEmail: customer.email },
|
||
{ serviceId: svc('svc-therapy'), staffId: who('staff-alex'), startsAt: at(2, 11, 0), endsAt: at(2, 12, 0), status: 'confirmed', customerName: 'Jordan Lee', customerEmail: 'jordan.lee@example.com', notes: 'Follow-up session.' },
|
||
{ serviceId: svc('svc-training'), staffId: who('staff-sam'), startsAt: at(3, 9, 30), endsAt: at(3, 10, 15), status: 'pending', customerName: 'Priya Nair', customerEmail: 'priya.nair@example.com' },
|
||
{ serviceId: svc('svc-consult'), staffId: who('staff-alex'), startsAt: at(-1, 9, 30), endsAt: at(-1, 10, 0), status: 'completed', customerName: 'Jordan Lee', customerEmail: 'jordan.lee@example.com' },
|
||
{ serviceId: svc('svc-therapy'), staffId: who('staff-sam'), startsAt: at(-1, 15, 0), endsAt: at(-1, 16, 0), status: 'no_show', customerName: 'Casey Brooks', customerEmail: 'casey.brooks@example.com' },
|
||
{ serviceId: svc('svc-training'), staffId: who('staff-alex'), startsAt: at(-3, 12, 0), endsAt: at(-3, 12, 45), status: 'cancelled', customerName: 'Morgan Diaz', customerEmail: 'morgan.diaz@example.com' },
|
||
],
|
||
});
|
||
|
||
if ((await db.auditLog.count()) === 0) {
|
||
await db.auditLog.createMany({
|
||
data: [
|
||
{ actorId: admin.id, action: 'seed', target: 'system', meta: 'Initial demo data' },
|
||
{ actorId: admin.id, action: 'catalog.loaded', target: `${SERVICES.length} services, ${STAFF.length} staff` },
|
||
],
|
||
});
|
||
}
|
||
|
||
console.log(`Seeded booking: ${admin.email} (admin), ${customer.email} (customer) — password: "password" (timezone: ${TZ})`);
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error(e);
|
||
process.exit(1);
|
||
})
|
||
.finally(async () => {
|
||
await db.$disconnect();
|
||
});
|