Files
CapaKraken/packages/db/src/seed-holiday-calendars.ts
T

206 lines
5.5 KiB
TypeScript

import { PrismaClient, type HolidayCalendarEntry } from "@prisma/client";
import { buildHolidayCalendarSeedDefinitions } from "./holiday-calendar-seed-data.js";
import { loadWorkspaceEnv } from "./load-workspace-env.js";
loadWorkspaceEnv();
const prisma = new PrismaClient();
const YEARS = [2026, 2027];
const SEED_SOURCE = "seed:holiday-calendars:2026-2027";
type ExistingCalendar = {
id: string;
entries: Pick<HolidayCalendarEntry, "id" | "date" | "source">[];
};
function toUtcDate(isoDate: string): Date {
return new Date(`${isoDate}T00:00:00.000Z`);
}
function dateKey(value: Date): string {
return value.toISOString().slice(0, 10);
}
async function findScopedCalendar(input: {
countryId: string;
scopeType: "COUNTRY" | "STATE" | "CITY";
stateCode?: string | undefined;
metroCityId?: string | undefined;
}): Promise<ExistingCalendar | null> {
return prisma.holidayCalendar.findFirst({
where: {
countryId: input.countryId,
scopeType: input.scopeType,
stateCode: input.scopeType === "STATE" ? input.stateCode ?? null : null,
metroCityId: input.scopeType === "CITY" ? input.metroCityId ?? null : null,
},
select: {
id: true,
entries: {
select: {
id: true,
date: true,
source: true,
},
},
},
});
}
async function main() {
console.log("Seeding holiday calendars for 2026-2027...");
const countries = await prisma.country.findMany({
select: {
id: true,
code: true,
name: true,
metroCities: {
select: {
id: true,
name: true,
},
},
},
orderBy: { code: "asc" },
});
const activeGermanStatesRows = await prisma.resource.findMany({
where: {
isActive: true,
country: { code: "DE" },
federalState: { not: null },
},
select: { federalState: true },
distinct: ["federalState"],
});
const definitions = buildHolidayCalendarSeedDefinitions({
availableCountryCodes: countries.map((country) => country.code),
availableCitiesByCountry: Object.fromEntries(
countries.map((country) => [
country.code,
country.metroCities.map((city) => city.name),
]),
),
activeGermanStates: activeGermanStatesRows
.map((row) => row.federalState)
.filter((stateCode): stateCode is string => Boolean(stateCode)),
years: YEARS,
});
const countryByCode = new Map(countries.map((country) => [country.code, country]));
const cityByCountryAndName = new Map(
countries.flatMap((country) =>
country.metroCities.map((city) => [`${country.code}:${city.name}`, city] as const),
),
);
let createdCalendars = 0;
let reusedCalendars = 0;
let createdEntries = 0;
let updatedEntries = 0;
let skippedManualEntries = 0;
for (const definition of definitions) {
const country = countryByCode.get(definition.countryCode);
if (!country) {
continue;
}
const metroCity = definition.cityName
? cityByCountryAndName.get(`${definition.countryCode}:${definition.cityName}`)
: null;
if (definition.scopeType === "CITY" && !metroCity) {
console.warn(
`Skipping city calendar ${definition.name}: city ${definition.cityName ?? "?"} not found.`,
);
continue;
}
let calendar = await findScopedCalendar({
countryId: country.id,
scopeType: definition.scopeType,
stateCode: definition.stateCode,
metroCityId: metroCity?.id,
});
if (!calendar) {
calendar = await prisma.holidayCalendar.create({
data: {
name: definition.name,
scopeType: definition.scopeType,
countryId: country.id,
stateCode: definition.scopeType === "STATE" ? definition.stateCode ?? null : null,
metroCityId: definition.scopeType === "CITY" ? metroCity?.id ?? null : null,
priority: definition.priority,
isActive: true,
},
select: {
id: true,
entries: {
select: {
id: true,
date: true,
source: true,
},
},
},
});
createdCalendars += 1;
} else {
reusedCalendars += 1;
}
const entriesByDate = new Map(calendar.entries.map((entry) => [dateKey(entry.date), entry]));
for (const entry of definition.entries) {
const existingEntry = entriesByDate.get(entry.date);
if (!existingEntry) {
await prisma.holidayCalendarEntry.create({
data: {
holidayCalendarId: calendar.id,
date: toUtcDate(entry.date),
name: entry.name,
isRecurringAnnual: entry.isRecurringAnnual,
source: SEED_SOURCE,
},
});
createdEntries += 1;
continue;
}
if (existingEntry.source && existingEntry.source !== SEED_SOURCE) {
skippedManualEntries += 1;
continue;
}
await prisma.holidayCalendarEntry.update({
where: { id: existingEntry.id },
data: {
name: entry.name,
isRecurringAnnual: entry.isRecurringAnnual,
source: SEED_SOURCE,
},
});
updatedEntries += 1;
}
}
console.log(` calendars created: ${createdCalendars}`);
console.log(` calendars reused: ${reusedCalendars}`);
console.log(` entries created: ${createdEntries}`);
console.log(` entries updated: ${updatedEntries}`);
console.log(` manual entries preserved: ${skippedManualEntries}`);
}
main()
.catch((error) => {
console.error(error);
process.exit(1);
})
.finally(() => prisma.$disconnect());