diff --git a/server/routes.ts b/server/routes.ts index aca930a..762d16f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -5,7 +5,7 @@ import { setupAuth as setupReplitAuth, isAuthenticated as isAuthenticatedReplit import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./localAuth"; import { db } from "./db"; import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema, contractParameters } from "@shared/schema"; -import { eq, and, gte, lte, desc, asc } from "drizzle-orm"; +import { eq, and, gte, lte, desc, asc, ne, sql } from "drizzle-orm"; import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval, startOfDay, isSameDay, parseISO, format } from "date-fns"; // Determina quale sistema auth usare basandosi sull'ambiente @@ -654,6 +654,97 @@ export async function registerRoutes(app: Express): Promise { } }); + // Endpoint per ottenere siti non completamente coperti per una data + app.get("/api/operational-planning/uncovered-sites", isAuthenticated, async (req, res) => { + try { + const dateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd"); + + // Imposta inizio e fine giornata in UTC + const startOfDay = new Date(dateStr + "T00:00:00.000Z"); + const endOfDay = new Date(dateStr + "T23:59:59.999Z"); + + // Ottieni tutti i siti attivi + const allSites = await db + .select() + .from(sites) + .where(eq(sites.isActive, true)); + + // Ottieni turni del giorno con assegnazioni + const dayShifts = await db + .select({ + shift: shifts, + assignmentCount: sql`count(${shiftAssignments.id})::int` + }) + .from(shifts) + .leftJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId)) + .where( + and( + gte(shifts.startTime, startOfDay), + lte(shifts.startTime, endOfDay), + ne(shifts.status, "cancelled") + ) + ) + .groupBy(shifts.id); + + // Calcola copertura per ogni sito + const sitesWithCoverage = allSites.map((site: any) => { + const siteShifts = dayShifts.filter((s: any) => s.shift.siteId === site.id); + + // Verifica copertura per ogni turno + const shiftsWithCoverage = siteShifts.map((s: any) => ({ + id: s.shift.id, + startTime: s.shift.startTime, + endTime: s.shift.endTime, + assignedGuardsCount: s.assignmentCount, + requiredGuards: site.minGuards, + isCovered: s.assignmentCount >= site.minGuards, + isPartial: s.assignmentCount > 0 && s.assignmentCount < site.minGuards + })); + + // Un sito è completamente coperto solo se TUTTI i turni hanno il numero minimo di guardie + const allShiftsCovered = siteShifts.length > 0 && shiftsWithCoverage.every((s: any) => s.isCovered); + + // Un sito è parzialmente coperto se ha turni ma non tutti sono completamente coperti + const hasPartialCoverage = siteShifts.length > 0 && !allShiftsCovered && shiftsWithCoverage.some((s: any) => s.assignedGuardsCount > 0); + + // Calcola totale guardie assegnate per info + const totalAssignedGuards = siteShifts.reduce((sum: number, s: any) => sum + s.assignmentCount, 0); + + return { + ...site, + isCovered: allShiftsCovered, + isPartiallyCovered: hasPartialCoverage, + totalAssignedGuards, + requiredGuards: site.minGuards, + shiftsCount: siteShifts.length, + shifts: shiftsWithCoverage + }; + }); + + // Filtra solo siti non completamente coperti + const uncoveredSites = sitesWithCoverage.filter( + (site: any) => !site.isCovered + ); + + // Ordina: parzialmente coperti prima, poi non coperti + const sortedUncoveredSites = uncoveredSites.sort((a: any, b: any) => { + if (a.isPartiallyCovered && !b.isPartiallyCovered) return -1; + if (!a.isPartiallyCovered && b.isPartiallyCovered) return 1; + return a.name.localeCompare(b.name); + }); + + res.json({ + date: dateStr, + uncoveredSites: sortedUncoveredSites, + totalSites: allSites.length, + totalUncovered: uncoveredSites.length + }); + } catch (error) { + console.error("Error fetching uncovered sites:", error); + res.status(500).json({ message: "Failed to fetch uncovered sites", error: String(error) }); + } + }); + // ============= CERTIFICATION ROUTES ============= app.post("/api/certifications", isAuthenticated, async (req, res) => { try {