Parametri Specifici
{
const rawWeekStart = req.query.weekStart as string || format(new Date(), "yyyy-MM-dd");
const normalizedWeekStart = rawWeekStart.split("/")[0];
- // Valida la data
- const parsedWeekStart = parseISO(normalizedWeekStart);
- if (!isValid(parsedWeekStart)) {
+ // ✅ CORRETTO: Valida date con regex, NON parseISO
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
+ if (!dateRegex.test(normalizedWeekStart)) {
return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
}
- const weekStartDate = format(parsedWeekStart, "yyyy-MM-dd");
+ // ✅ CORRETTO: Costruisci Date da componenti per evitare timezone shift
+ const [year, month, day] = normalizedWeekStart.split("-").map(Number);
+ const parsedWeekStart = new Date(year, month - 1, day, 0, 0, 0, 0);
+
+ const weekStartDate = normalizedWeekStart;
// Ottieni location dalla query (default: roccapiemonte)
const location = req.query.location as string || "roccapiemonte";
- // Calcola fine settimana (weekStart + 6 giorni)
- const weekEndDate = format(addDays(parsedWeekStart, 6), "yyyy-MM-dd");
+ // Calcola fine settimana (weekStart + 6 giorni) usando componenti
+ const tempWeekEnd = new Date(year, month - 1, day + 6, 23, 59, 59, 999);
+ const weekEndYear = tempWeekEnd.getFullYear();
+ const weekEndMonth = tempWeekEnd.getMonth() + 1;
+ const weekEndDay = tempWeekEnd.getDate();
+ const weekEndDate = `${weekEndYear}-${String(weekEndMonth).padStart(2, '0')}-${String(weekEndDay).padStart(2, '0')}`;
- // Timestamp per filtro contratti
- const weekStartTimestampForContract = new Date(weekStartDate);
- const weekEndTimestampForContract = new Date(weekEndDate);
+ // ✅ CORRETTO: Timestamp da componenti per query database
+ const weekStartTimestampForContract = new Date(year, month - 1, day, 0, 0, 0, 0);
+ const weekEndTimestampForContract = new Date(weekEndYear, weekEndMonth - 1, weekEndDay, 23, 59, 59, 999);
// Ottieni tutti i siti attivi della sede con contratto valido nelle date della settimana
const activeSites = await db
@@ -917,11 +925,9 @@ export async function registerRoutes(app: Express): Promise {
);
// Ottieni tutti i turni della settimana per la sede
- const weekStartTimestamp = new Date(weekStartDate);
- weekStartTimestamp.setHours(0, 0, 0, 0);
-
- const weekEndTimestamp = new Date(weekEndDate);
- weekEndTimestamp.setHours(23, 59, 59, 999);
+ // ✅ CORRETTO: Usa timestamp già creati correttamente sopra
+ const weekStartTimestamp = weekStartTimestampForContract;
+ const weekEndTimestamp = weekEndTimestampForContract;
const weekShifts = await db
.select({
@@ -971,14 +977,15 @@ export async function registerRoutes(app: Express): Promise {
const weekData = [];
for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
- const currentDay = addDays(parsedWeekStart, dayOffset);
- const dayStr = format(currentDay, "yyyy-MM-dd");
+ // ✅ CORRETTO: Calcola date usando componenti per evitare timezone shift
+ const currentDayTimestamp = new Date(year, month - 1, day + dayOffset, 0, 0, 0, 0);
+ const currentYear = currentDayTimestamp.getFullYear();
+ const currentMonth = currentDayTimestamp.getMonth() + 1;
+ const currentDay_num = currentDayTimestamp.getDate();
+ const dayStr = `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(currentDay_num).padStart(2, '0')}`;
- const dayStartTimestamp = new Date(dayStr);
- dayStartTimestamp.setHours(0, 0, 0, 0);
-
- const dayEndTimestamp = new Date(dayStr);
- dayEndTimestamp.setHours(23, 59, 59, 999);
+ const dayStartTimestamp = new Date(currentYear, currentMonth - 1, currentDay_num, 0, 0, 0, 0);
+ const dayEndTimestamp = new Date(currentYear, currentMonth - 1, currentDay_num, 23, 59, 59, 999);
const sitesData = activeSites.map(({ sites: site, service_types: serviceType }: any) => {
// Trova turni del giorno per questo sito
diff --git a/shared/schema.ts b/shared/schema.ts
index 7ec9919..9b9bbd2 100644
--- a/shared/schema.ts
+++ b/shared/schema.ts
@@ -91,6 +91,11 @@ export const locationEnum = pgEnum("location", [
"roma", // Sede Roma
]);
+export const serviceClassificationEnum = pgEnum("service_classification", [
+ "fisso", // Presidio fisso - Planning Fissi
+ "mobile", // Pattuglie/ronde/interventi - Planning Mobile
+]);
+
// ============= SESSION & AUTH TABLES (Replit Auth) =============
// Session storage table - mandatory for Replit Auth
@@ -189,6 +194,9 @@ export const serviceTypes = pgTable("service_types", {
icon: varchar("icon").notNull().default("Building2"), // Nome icona Lucide
color: varchar("color").notNull().default("blue"), // blue, green, purple, orange
+ // ✅ NUOVO: Classificazione servizio - determina quale planning usare
+ classification: serviceClassificationEnum("classification").notNull().default("fisso"),
+
// Parametri specifici per tipo servizio
fixedPostHours: integer("fixed_post_hours"), // Ore presidio fisso (es. 8, 12)
patrolPassages: integer("patrol_passages"), // Numero passaggi pattugliamento (es. 3, 5)