import { Prisma } from "@prisma/client"; import { UserFacingError } from "./action-error"; import { getNotdienstDays } from "./notdienst-days"; import { todayKey } from "./staff/calculations"; export async function loadNotdienstDays( db: Pick, kitaId: string, year: number, month: number, ) { const closures = await db.termin.findMany({ where: { kitaId, status: "CONFIRMED", type: { in: ["SCHLIESSTAG", "TEAMTAG"] }, // Include closures crossing the month boundary and Berlin midnight. startDate: { lt: new Date(Date.UTC(year, month, 1)) }, endDate: { gte: new Date(Date.UTC(year, month - 1, 1) - 86400000) }, }, select: { startDate: true, endDate: true }, }); return getNotdienstDays(year, month, closures); } export async function assertChildrenRemovable( tx: Prisma.TransactionClient, kitaId: string, familyId: string, childIds: string[], ) { if (childIds.length === 0) return; // Coordinate with the FK locks taken by concurrent assignment writes. await tx.$queryRaw`SELECT id FROM children WHERE "kitaId" = ${kitaId} AND "familyId" = ${familyId} AND id IN (${Prisma.join(childIds)}) ORDER BY id FOR UPDATE`; const obligation = await tx.notdienstAssignment.findFirst({ where: { kitaId, childId: { in: childIds }, child: { kitaId, familyId }, OR: [ { date: { gte: new Date(`${todayKey()}T00:00:00Z`) }, plan: { kitaId, status: "PUBLISHED" }, }, { alarms: { some: { kitaId, activeAssignmentId: { not: null } } } }, ], }, select: { id: true }, }); if (obligation) { throw new UserFacingError( "Das Kind kann nicht entfernt werden, solange veröffentlichte Notdienste oder aktive Alarme bestehen. Bitte zuerst die Einteilung mit der Koordination klären.", ); } }