Files
brammie15 314717ba3a
CI / build-and-test (push) Canceled after 0s
feat: move to V2
2026-10-29 11:21:52 +01:00

149 lines
6.3 KiB
TypeScript

/**
* 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());