feat: seed a starter annual task-template calendar
TaskTemplate had zero seed data - a fresh deployment showed a completely empty recurring-task list with no hint of what to even put there. Seeds 12 YEARLY templates, one per month, covering the standard German-beekeeping annual cycle (Frühjahrsdurchsicht, Schwarmkontrolle, Einfütterung, Varroa-Restentmilbung/Oxalsäure, Wintersitz-Kontrolle, etc.) so a new beekeeper gets a ready-made starting point instead of a blank page. Runs once (guarded by a taskTemplate.count() check, same idempotency shape as the existing admin-user seed) and only ever adds rows - admins can edit or deactivate any of them afterward. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
c4f970f071
commit
81986b3275
1 file changed
+161
@@ -30,11 +30,172 @@ function printLoginBanner(email: string, password: string): void {
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
// Duplicated from src/task-templates/recurrence.ts rather than imported:
|
||||
// tsconfig.seed.json scopes this build to rootDir "./prisma" only (its own
|
||||
// isolated compile, separate from the main Nest build), so a cross-rootDir
|
||||
// import would fail to compile. Only the YEARLY case is needed here.
|
||||
function nextYearlyOccurrenceUtc(
|
||||
month: number,
|
||||
dayOfMonth: number,
|
||||
from: Date,
|
||||
): Date {
|
||||
const year = from.getUTCFullYear();
|
||||
const clampedDay = (y: number): number =>
|
||||
Math.min(dayOfMonth, new Date(Date.UTC(y, month, 0)).getUTCDate());
|
||||
const today = new Date(
|
||||
Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate()),
|
||||
);
|
||||
const candidate = new Date(Date.UTC(year, month - 1, clampedDay(year)));
|
||||
if (candidate.getTime() >= today.getTime()) {
|
||||
return candidate;
|
||||
}
|
||||
return new Date(Date.UTC(year + 1, month - 1, clampedDay(year + 1)));
|
||||
}
|
||||
|
||||
interface DefaultTaskTemplate {
|
||||
title: string;
|
||||
description: string;
|
||||
priority: 'LOW' | 'NORMAL' | 'HIGH' | 'URGENT';
|
||||
month: number;
|
||||
dayOfMonth: number;
|
||||
}
|
||||
|
||||
// The standard German-beekeeping annual cycle, one flagship task per month -
|
||||
// gives a new deployment a ready-made starter calendar instead of a
|
||||
// completely empty task-template list. Admins can edit/deactivate any of
|
||||
// these afterward; this only ever runs once (see the count() guard below).
|
||||
const DEFAULT_TASK_TEMPLATES: DefaultTaskTemplate[] = [
|
||||
{
|
||||
title: 'Wintersitz kontrollieren',
|
||||
description:
|
||||
'Ruhige Kontrolle von außen: Flugloch frei, Gewicht per Anheben schätzen, auf Mäuse-/Spechtschäden achten. Beute möglichst nicht öffnen.',
|
||||
priority: 'NORMAL',
|
||||
month: 1,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Gemülldiagnose',
|
||||
description:
|
||||
'Windel auswerten: natürlicher Varroa-Totenfall, Bienensitz und Lage der Wintertraube anhand des Gemülls einschätzen.',
|
||||
priority: 'NORMAL',
|
||||
month: 2,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Frühjahrsdurchsicht',
|
||||
description:
|
||||
'Erste Innendurchsicht bei ca. 15°C und ruhigem Wetter: Brutnest, Futtervorrat, Volksstärke und Weiselrichtigkeit prüfen.',
|
||||
priority: 'HIGH',
|
||||
month: 3,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Volk erweitern',
|
||||
description:
|
||||
'Zargen/Rähmchen nach Volksstärke erweitern, Baurahmen einhängen, Platz für die Frühtracht schaffen.',
|
||||
priority: 'NORMAL',
|
||||
month: 4,
|
||||
dayOfMonth: 1,
|
||||
},
|
||||
{
|
||||
title: 'Schwarmkontrolle beginnen',
|
||||
description:
|
||||
'Wöchentlich auf Weiselzellen kontrollieren, bis zum Ende der Schwarmzeit fortführen. Maßnahmen bei Bedarf: Ableger bilden, Zellen brechen.',
|
||||
priority: 'HIGH',
|
||||
month: 5,
|
||||
dayOfMonth: 1,
|
||||
},
|
||||
{
|
||||
title: 'Honigernte vorbereiten',
|
||||
description:
|
||||
'Reifegrad der Frühtracht prüfen (Spritzprobe), Schleuderplan und Gerätereinigung vorbereiten.',
|
||||
priority: 'NORMAL',
|
||||
month: 6,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Varroa-Kontrolle nach der Ernte',
|
||||
description:
|
||||
'Milbenbefall messen (z. B. Puderzuckermethode oder natürlicher Totenfall) und Behandlung planen, sobald die Honigräume abgeräumt sind.',
|
||||
priority: 'HIGH',
|
||||
month: 7,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Einfütterung beginnen',
|
||||
description:
|
||||
'Wintervorrat auffüttern (Zielgewicht je nach Beutensystem), Futteraufnahme nach ein bis zwei Wochen kontrollieren.',
|
||||
priority: 'HIGH',
|
||||
month: 8,
|
||||
dayOfMonth: 1,
|
||||
},
|
||||
{
|
||||
title: 'Varroa-Restentmilbung / Winterfestigkeit prüfen',
|
||||
description:
|
||||
'Zweite Varroabehandlung durchführen, Volksstärke und Weiselrichtigkeit für den Winter beurteilen.',
|
||||
priority: 'HIGH',
|
||||
month: 9,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Fütterung abschließen, Mäusegitter anbringen',
|
||||
description:
|
||||
'Letzte Futtergabe, Fluglochkeile bzw. Mäusegitter montieren, Bienenstand winterfest machen.',
|
||||
priority: 'NORMAL',
|
||||
month: 10,
|
||||
dayOfMonth: 1,
|
||||
},
|
||||
{
|
||||
title: 'Winterbehandlung (Oxalsäure)',
|
||||
description:
|
||||
'Brutfreiheit abwarten und Träufel- oder Sprühbehandlung gegen Varroa durchführen.',
|
||||
priority: 'HIGH',
|
||||
month: 11,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
{
|
||||
title: 'Jahresrückblick und Gerätewartung',
|
||||
description:
|
||||
'Saison auswerten, Zargen und Rähmchen reinigen/reparieren, Material für die kommende Saison planen.',
|
||||
priority: 'LOW',
|
||||
month: 12,
|
||||
dayOfMonth: 15,
|
||||
},
|
||||
];
|
||||
|
||||
async function seedDefaultTaskTemplates(prisma: PrismaClient): Promise<void> {
|
||||
const existingCount = await prisma.taskTemplate.count();
|
||||
if (existingCount > 0) {
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
await prisma.taskTemplate.createMany({
|
||||
data: DEFAULT_TASK_TEMPLATES.map((template) => ({
|
||||
title: template.title,
|
||||
description: template.description,
|
||||
priority: template.priority,
|
||||
recurrenceType: 'YEARLY',
|
||||
recurrenceMonth: template.month,
|
||||
recurrenceDayOfMonth: template.dayOfMonth,
|
||||
nextDueDate: nextYearlyOccurrenceUtc(
|
||||
template.month,
|
||||
template.dayOfMonth,
|
||||
now,
|
||||
),
|
||||
})),
|
||||
});
|
||||
console.log(
|
||||
`Seed: ${DEFAULT_TASK_TEMPLATES.length} Standard-Aufgabenvorlagen angelegt.`,
|
||||
);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const prisma = new PrismaClient({
|
||||
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
|
||||
});
|
||||
|
||||
await seedDefaultTaskTemplates(prisma);
|
||||
|
||||
const email = process.env.SEED_ADMIN_EMAIL ?? DEFAULT_ADMIN_EMAIL;
|
||||
const password = process.env.SEED_ADMIN_PASSWORD ?? generateRandomPassword();
|
||||
const existing = await prisma.user.findUnique({ where: { email } });
|
||||
|
||||
Reference in new issue
Block a user