Compare commits

..

4 Commits

Author SHA1 Message Date
Marco Lanzara
24a1c81d6e 🚀 Release v1.0.30
- Tipo: patch
- Database backup: database-backups/vigilanzaturni_v1.0.30_20251022_071255.sql.gz
- Data: 2025-10-22 07:13:11
2025-10-22 07:13:11 +00:00
marco370
82442a5dd9 Add daily guard hour limit checks and improve shift assignment display
Implement validation for daily guard working hours, including overtime detection. Refactor shift assignment logic to prevent guards from appearing in shifts after their ordinary hours are completed, with an exception for displaying guards with overtime.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/Jxn533V
2025-10-21 17:43:31 +00:00
marco370
b1b320ab69 Update planning to enforce daily guard hour limits and improve assignment logic
Add daily hour limit checks during guard assignment and ensure guards with completed ordinary hours are not displayed unless they have overtime. Refactor guard availability query to refetch immediately on assignment success and update staleTime to 0.

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/Jxn533V
2025-10-21 17:38:29 +00:00
marco370
1c34d3f79e Enforce daily working hour limits for security guards
Implement daily hour limit checks for guard assignments based on CCNL regulations, preventing assignments that exceed 9 hours per day.

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/Jxn533V
2025-10-21 17:36:14 +00:00
5 changed files with 46 additions and 8 deletions

View File

@ -132,7 +132,7 @@ export default function GeneralPlanning() {
};
// Query per guardie disponibili (solo quando dialog è aperto)
const { data: availableGuards, isLoading: isLoadingGuards } = useQuery<GuardAvailability[]>({
const { data: availableGuards, isLoading: isLoadingGuards, refetch: refetchGuards } = useQuery<GuardAvailability[]>({
queryKey: ["/api/guards/availability", selectedCell?.siteId, selectedLocation, startTime, durationHours],
queryFn: async () => {
if (!selectedCell) return [];
@ -144,6 +144,7 @@ export default function GeneralPlanning() {
return response.json();
},
enabled: !!selectedCell, // Query attiva solo se dialog è aperto
staleTime: 0, // Dati sempre considerati stale, refetch ad ogni apertura dialog
});
// Mutation per eliminare assegnazione guardia
@ -174,19 +175,21 @@ export default function GeneralPlanning() {
mutationFn: async (data: { siteId: string; date: string; guardId: string; startTime: string; durationHours: number; consecutiveDays: number }) => {
return apiRequest("POST", "/api/general-planning/assign-guard", data);
},
onSuccess: () => {
onSuccess: async () => {
// Invalida cache planning generale
queryClient.invalidateQueries({ queryKey: ["/api/general-planning"] });
queryClient.invalidateQueries({ queryKey: ["/api/guards/availability"] });
await queryClient.invalidateQueries({ queryKey: ["/api/general-planning"] });
await queryClient.invalidateQueries({ queryKey: ["/api/guards/availability"] });
// Refetch immediatamente guardie disponibili per aggiornare lista
await refetchGuards();
toast({
title: "Guardia assegnata",
description: "La guardia è stata assegnata con successo",
});
// Reset form
// Reset solo guardia selezionata (NON chiudere dialog per vedere lista aggiornata)
setSelectedGuardId("");
setSelectedCell(null);
},
onError: (error: any) => {
// Parse error message from API response

View File

@ -1326,6 +1326,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
.from(shiftAssignments)
.where(eq(shiftAssignments.guardId, guard.id));
// Check for time overlaps
for (const existing of existingAssignments) {
const hasOverlap =
plannedStart < existing.plannedEndTime &&
@ -1338,6 +1339,34 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
}
// CCNL: Check daily hour limit (max 9h/day)
const maxDailyHours = 9;
let dailyHoursAlreadyAssigned = 0;
for (const existing of existingAssignments) {
// Check if assignment is on the same day
const existingDate = new Date(existing.plannedStartTime);
if (
existingDate.getUTCFullYear() === actualYear &&
existingDate.getUTCMonth() === actualMonth &&
existingDate.getUTCDate() === actualDay
) {
const assignmentHours = differenceInHours(
existing.plannedEndTime,
existing.plannedStartTime
);
dailyHoursAlreadyAssigned += assignmentHours;
}
}
// Check if new assignment would exceed daily limit
if (dailyHoursAlreadyAssigned + durationHours > maxDailyHours) {
throw new Error(
`Limite giornaliero superato: la guardia ha già ${dailyHoursAlreadyAssigned}h assegnate il ${shiftDate.toLocaleDateString('it-IT')}. ` +
`Aggiungendo ${durationHours}h si supererebbero le ${maxDailyHours}h massime giornaliere (CCNL).`
);
}
// Create assignment for this day
const [assignment] = await tx.insert(shiftAssignments).values({
shiftId: shift.id,

View File

@ -1,7 +1,13 @@
{
"version": "1.0.29",
"lastUpdate": "2025-10-21T17:19:38.090Z",
"version": "1.0.30",
"lastUpdate": "2025-10-22T07:13:11.868Z",
"changelog": [
{
"version": "1.0.30",
"date": "2025-10-22",
"type": "patch",
"description": "Deployment automatico v1.0.30"
},
{
"version": "1.0.29",
"date": "2025-10-21",