From 0b64fd2f086c627c361d72a4448c6cb7a17d205b Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Fri, 24 Oct 2025 15:36:54 +0000 Subject: [PATCH] Add functionality to copy weekly shift assignments to the following week Introduce a new POST API endpoint `/api/shift-assignments/copy-week` to duplicate existing shift assignments and their associated shifts for a specified location and week, automatically adjusting dates by adding 7 days. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/EDxr1e6 --- server/routes.ts | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/server/routes.ts b/server/routes.ts index eea961b..bd20f37 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1337,6 +1337,129 @@ export async function registerRoutes(app: Express): Promise { } }); + // Copy weekly shift assignments to next week + app.post("/api/shift-assignments/copy-week", isAuthenticated, async (req, res) => { + try { + const { weekStart, location } = req.body; + + if (!weekStart || !location) { + return res.status(400).json({ message: "Missing required fields: weekStart, location" }); + } + + // Parse week start date + const [year, month, day] = weekStart.split("-").map(Number); + if (!year || !month || !day) { + return res.status(400).json({ message: "Invalid weekStart format. Expected YYYY-MM-DD" }); + } + + // Calculate week boundaries (Monday to Sunday) + const weekStartDate = new Date(year, month - 1, day, 0, 0, 0, 0); + const weekEndDate = new Date(year, month - 1, day + 6, 23, 59, 59, 999); + + console.log("📋 Copying weekly shifts:", { + weekStart: weekStartDate.toISOString(), + weekEnd: weekEndDate.toISOString(), + location + }); + + // Transaction: copy all shifts and assignments + const result = await db.transaction(async (tx) => { + // 1. Find all shifts in the source week filtered by location + const sourceShifts = await tx + .select({ + shift: shifts, + site: sites + }) + .from(shifts) + .innerJoin(sites, eq(shifts.siteId, sites.id)) + .where( + and( + gte(shifts.startTime, weekStartDate), + lte(shifts.startTime, weekEndDate), + eq(sites.location, location) + ) + ); + + if (sourceShifts.length === 0) { + throw new Error("Nessun turno trovato nella settimana selezionata"); + } + + console.log(`📋 Found ${sourceShifts.length} shifts to copy`); + + let copiedShiftsCount = 0; + let copiedAssignmentsCount = 0; + + // 2. For each shift, copy to next week (+7 days) + for (const { shift: sourceShift, site } of sourceShifts) { + // Calculate new dates (+7 days) + const newStartTime = new Date(sourceShift.startTime); + newStartTime.setDate(newStartTime.getDate() + 7); + + const newEndTime = new Date(sourceShift.endTime); + newEndTime.setDate(newEndTime.getDate() + 7); + + // Create new shift + const [newShift] = await tx + .insert(shifts) + .values({ + siteId: sourceShift.siteId, + startTime: newStartTime, + endTime: newEndTime, + status: "planned", + vehicleId: sourceShift.vehicleId, + notes: sourceShift.notes, + }) + .returning(); + + copiedShiftsCount++; + + // 3. Copy all assignments for this shift + const sourceAssignments = await tx + .select() + .from(shiftAssignments) + .where(eq(shiftAssignments.shiftId, sourceShift.id)); + + for (const sourceAssignment of sourceAssignments) { + // Calculate new planned times (+7 days) + const newPlannedStart = new Date(sourceAssignment.plannedStartTime); + newPlannedStart.setDate(newPlannedStart.getDate() + 7); + + const newPlannedEnd = new Date(sourceAssignment.plannedEndTime); + newPlannedEnd.setDate(newPlannedEnd.getDate() + 7); + + // Create new assignment + await tx + .insert(shiftAssignments) + .values({ + shiftId: newShift.id, + guardId: sourceAssignment.guardId, + plannedStartTime: newPlannedStart, + plannedEndTime: newPlannedEnd, + isArmedOnDuty: sourceAssignment.isArmedOnDuty, + assignedVehicleId: sourceAssignment.assignedVehicleId, + }); + + copiedAssignmentsCount++; + } + } + + return { copiedShiftsCount, copiedAssignmentsCount }; + }); + + res.json({ + message: `Settimana copiata con successo: ${result.copiedShiftsCount} turni, ${result.copiedAssignmentsCount} assegnazioni`, + copiedShifts: result.copiedShiftsCount, + copiedAssignments: result.copiedAssignmentsCount, + }); + } catch (error: any) { + console.error("❌ Error copying weekly shifts:", error); + res.status(500).json({ + message: error.message || "Errore durante la copia dei turni settimanali", + error: String(error) + }); + } + }); + // Assign guard to site/date with specific time slot (supports multi-day assignments) app.post("/api/general-planning/assign-guard", isAuthenticated, async (req, res) => { try {