149 lines
6.3 KiB
TypeScript
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());
|