initial booking

This commit is contained in:
Ola Malmgren
2026-05-22 10:50:48 +02:00
commit 4d705a1005
77 changed files with 13827 additions and 0 deletions

121
prisma/seed.ts Normal file
View File

@@ -0,0 +1,121 @@
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
// -- Settings singleton --
await prisma.settings.upsert({
where: { id: 'singleton' },
update: {},
create: { id: 'singleton', pickupEnabled: true },
});
console.log('✓ Settings singleton ensured');
// -- Initial admin --
const email = (process.env.SEED_ADMIN_EMAIL ?? 'admin@example.com').toLowerCase();
const password = process.env.SEED_ADMIN_PASSWORD ?? 'change-me';
const name = process.env.SEED_ADMIN_NAME ?? 'Admin';
const passwordHash = await bcrypt.hash(password, 12);
await prisma.admin.upsert({
where: { email },
update: {},
create: { email, name, passwordHash },
});
console.log(`✓ Admin ensured: ${email}`);
// -- Products (example LPG cylinders) --
const products = [
{
sku: 'P6',
nameSv: 'Gasoltub P6',
nameEn: 'LPG cylinder P6',
descriptionSv: '6 kg gasol — för mindre kök och campingkök.',
descriptionEn: '6 kg LPG — for smaller stoves and camping cooktops.',
priceOre: 39900, // 399 kr
sortOrder: 10,
},
{
sku: 'P11',
nameSv: 'Gasoltub P11',
nameEn: 'LPG cylinder P11',
descriptionSv: '11 kg gasol — vanligaste storleken för matlagning på läger.',
descriptionEn: '11 kg LPG — most common size for cooking at camps.',
priceOre: 64900, // 649 kr
sortOrder: 20,
},
{
sku: 'P19',
nameSv: 'Gasoltub P19',
nameEn: 'LPG cylinder P19',
descriptionSv: '19 kg gasol — för större kök eller längre vistelse.',
descriptionEn: '19 kg LPG — for larger kitchens or longer stays.',
priceOre: 99900, // 999 kr
sortOrder: 30,
},
];
for (const p of products) {
await prisma.product.upsert({
where: { sku: p.sku },
update: {
nameSv: p.nameSv,
nameEn: p.nameEn,
descriptionSv: p.descriptionSv,
descriptionEn: p.descriptionEn,
priceOre: p.priceOre,
sortOrder: p.sortOrder,
},
create: { ...p, active: true, vatBp: 2500 },
});
}
console.log(`✓ Seeded ${products.length} products`);
// -- Pickup slots (example: 3 days × 2 slots) --
const existing = await prisma.pickupSlot.count();
if (existing === 0) {
const baseDay = new Date();
baseDay.setUTCHours(8, 0, 0, 0);
// Place slots ~6 months out so they don't expire immediately.
baseDay.setUTCMonth(baseDay.getUTCMonth() + 6);
const slots = [
{ offsetDays: 0, startH: 9, endH: 12, labelSv: 'Dag 1 — Förmiddag', labelEn: 'Day 1 — Morning' },
{ offsetDays: 0, startH: 13, endH: 17, labelSv: 'Dag 1 — Eftermiddag', labelEn: 'Day 1 — Afternoon' },
{ offsetDays: 1, startH: 9, endH: 12, labelSv: 'Dag 2 — Förmiddag', labelEn: 'Day 2 — Morning' },
{ offsetDays: 1, startH: 13, endH: 17, labelSv: 'Dag 2 — Eftermiddag', labelEn: 'Day 2 — Afternoon' },
{ offsetDays: 2, startH: 9, endH: 12, labelSv: 'Dag 3 — Förmiddag', labelEn: 'Day 3 — Morning' },
];
for (const s of slots) {
const startsAt = new Date(baseDay);
startsAt.setUTCDate(baseDay.getUTCDate() + s.offsetDays);
startsAt.setUTCHours(s.startH, 0, 0, 0);
const endsAt = new Date(startsAt);
endsAt.setUTCHours(s.endH, 0, 0, 0);
await prisma.pickupSlot.create({
data: {
labelSv: s.labelSv,
labelEn: s.labelEn,
startsAt,
endsAt,
capacity: 50,
active: true,
},
});
}
console.log(`✓ Seeded ${slots.length} pickup slots`);
} else {
console.log(`• Pickup slots already exist (${existing}) — skipping`);
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});