Add operational planning view for uncovered sites
Introduces a new API endpoint `/api/operational-planning/uncovered-sites` that queries for sites not fully covered by assigned guards on a given date, returning sites with partial or no coverage. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/sshIJbn
This commit is contained in:
parent
144a281657
commit
283b24bcb6
@ -5,7 +5,7 @@ import { setupAuth as setupReplitAuth, isAuthenticated as isAuthenticatedReplit
|
|||||||
import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./localAuth";
|
import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./localAuth";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema, contractParameters } from "@shared/schema";
|
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";
|
import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval, startOfDay, isSameDay, parseISO, format } from "date-fns";
|
||||||
|
|
||||||
// Determina quale sistema auth usare basandosi sull'ambiente
|
// Determina quale sistema auth usare basandosi sull'ambiente
|
||||||
@ -654,6 +654,97 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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<number>`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 =============
|
// ============= CERTIFICATION ROUTES =============
|
||||||
app.post("/api/certifications", isAuthenticated, async (req, res) => {
|
app.post("/api/certifications", isAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user