/** * Demo data for local development: two venues, a user of every role, a paired terminal, a catalog with stock, open * tabs and some paid ones. Wipes the database first. * * pnpm db:seed */ import 'dotenv/config'; import { createHash, randomUUID } from 'crypto'; import { hashPassword } from '../src/auth/auth.service'; import { createPrismaClient } from '../src/prisma/prisma.service'; import { ensureDefaultCustomers } from '../src/customers/default-customers'; import { createdFieldTimes } from '../src/ledger/product-fields'; const prisma = createPrismaClient(); const minutesAgo = (minutes: number) => new Date(Date.now() - minutes * 60 * 1000); const CATALOG = [ { name: 'Jupiler', category: 'Bier', priceInCents: 280, buyPriceInCents: 110, stock: 96, desiredStock: 144 }, { name: 'Duvel', category: 'Bier', priceInCents: 450, buyPriceInCents: 190, stock: 24, desiredStock: 48 }, { name: 'Cola', category: 'Frisdrank', priceInCents: 250, buyPriceInCents: 80, stock: 6, desiredStock: 48 }, { name: 'Spa rood', category: 'Frisdrank', priceInCents: 230, buyPriceInCents: 70, stock: 30, desiredStock: 48 }, { name: 'Koffie', category: 'Warm', priceInCents: 250, buyPriceInCents: null, stock: null, desiredStock: null }, { name: 'Chips', category: null, priceInCents: 200, buyPriceInCents: 60, stock: 18, desiredStock: 30 }, ]; /** A tab's drinks: [catalog index, quantity, minutes ago] per order line. */ const TABS: { customerName: string; lines: [number, number, number][] }[] = [ { customerName: 'Jan', lines: [[0, 4, 90], [5, 1, 90], [1, 2, 20]] }, { customerName: 'Marie', lines: [[2, 2, 45], [3, 1, 45]] }, { customerName: 'Thomas', lines: [[1, 3, 120], [0, 2, 60]] }, { customerName: 'Lisa', lines: [[4, 1, 10]] }, ]; async function main() { await prisma.productImage.deleteMany(); await prisma.venue.deleteMany(); const venue = await prisma.venue.create({ data: { name: 'KoolTap Demo Bar', cashPaymentEmails: [] } }); await ensureDefaultCustomers(prisma, venue.id); const festival = await prisma.venue.create({ data: { name: 'KoolTap Festival Bar' } }); await ensureDefaultCustomers(prisma, festival.id); const users = [ { venueId: venue.id, email: 'admin@example.com', password: 'Admin123!', name: 'Eigenaar', role: 'admin' as const }, { venueId: venue.id, email: 'manager@example.com', password: 'Manager123!', name: 'Bartender', role: 'manager' as const }, // Above the venues: views one to act in it as its admin { venueId: null, email: 'superadmin@example.com', password: 'Super123!', name: 'KoolTap', role: 'superadmin' as const }, { venueId: festival.id, email: 'festival@example.com', password: 'Admin123!', name: 'Festival', role: 'admin' as const }, ]; for (const { password, ...user } of users) { await prisma.user.create({ data: { ...user, passwordHash: await hashPassword(password) } }); } const manager = await prisma.user.findUniqueOrThrow({ where: { email: 'manager@example.com' } }); // A known token, so `scripts/terminal-simulator.ts` can sync as this terminal without pairing const token = 'kt_demo-terminal-token'; const device = await prisma.device.create({ data: { venueId: venue.id, name: 'Bar terminal 1', tokenHash: createHash('sha256').update(token).digest('hex'), tokenPrefix: token.slice(0, 8), tokenExpiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), lastSeenAt: minutesAgo(1), }, }); const now = new Date(); const products = []; for (const { stock, ...fields } of CATALOG) { const product = await prisma.product.create({ data: { id: randomUUID(), venueId: venue.id, ...fields, stock, countedAt: stock === null ? null : minutesAgo(24 * 60), lowStockThreshold: stock === null ? null : 12, fieldUpdatedAt: createdFieldTimes({ ...fields, archived: false }, now), updatedAt: now, }, }); products.push(product); if (stock !== null) { await prisma.stockMovement.create({ data: { venueId: venue.id, productId: product.id, type: 'count', quantity: stock, stockAfter: stock, userId: manager.id, createdAt: minutesAgo(24 * 60) }, }); } } await prisma.customer.createMany({ data: ['Jan', 'Marie', 'Thomas'].map((name, index) => ({ venueId: venue.id, name, nameKey: name.toLowerCase(), email: `${name.toLowerCase()}@example.com`, position: index + 1, tabOpenedAt: now, })), }); // Open tabs, and for each customer one paid tab from last week for (const [index, { customerName, lines }] of TABS.entries()) { const tab = await prisma.tab.create({ data: { id: randomUUID(), venueId: venue.id, customerName, openedAt: minutesAgo(180), version: lines.length, updatedAt: now }, }); await prisma.tabUnit.createMany({ data: lines.flatMap(([productIndex, quantity, ago]) => units(venue.id, products[productIndex], quantity, minutesAgo(ago), randomUUID(), { tabId: tab.id })) }); const settledAt = minutesAgo((index + 1) * 24 * 60 * 2); const product = products[index % products.length]; const settlement = await prisma.settlement.create({ data: { id: randomUUID(), venueId: venue.id, tabId: tab.id, customerName, settledAt, paymentMethod: index % 2 === 0 ? 'cash' : 'payconiq', amountInCents: 3 * product.priceInCents, deviceId: device.id, }, }); await prisma.tabUnit.createMany({ data: units(venue.id, product, 3, minutesAgo((index + 1) * 24 * 60 * 2 + 60), randomUUID(), { settlementId: settlement.id }) }); } console.log('Seeded the demo database. Sign in with:'); for (const user of users) console.log(` ${user.role.padEnd(10)} ${user.email} / ${user.password}`); console.log(`Terminal token for the simulator: ${token}`); } function units( venueId: string, product: { id: string; name: string; priceInCents: number }, quantity: number, addedAt: Date, lineId: string, where: { tabId?: string; settlementId?: string }, ) { const eventId = randomUUID(); return Array.from({ length: quantity }, (_, n) => ({ id: `${eventId}:${n}`, venueId, ...where, lineId, productId: product.id, productName: product.name, unitPriceInCents: product.priceInCents, orderId: `order-${addedAt.getTime()}`, addedAt, })); } main() .catch((error) => { console.error(error); process.exitCode = 1; }) .finally(() => prisma.$disconnect());