Add contract start and end dates for sites and validate shifts
Implement contract start/end date validation for sites and enforce shift creation within contract boundaries on the server. Add contract status display to the client. 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/FlO7tHX
This commit is contained in:
parent
c8b273d9a6
commit
e4d3ab514c
4
.replit
4
.replit
@ -31,6 +31,10 @@ externalPort = 3002
|
|||||||
localPort = 43267
|
localPort = 43267
|
||||||
externalPort = 3003
|
externalPort = 3003
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 43839
|
||||||
|
externalPort = 4200
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "5000"
|
PORT = "5000"
|
||||||
|
|
||||||
|
|||||||
@ -142,6 +142,32 @@ export default function Sites() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Funzione per determinare lo stato del contratto
|
||||||
|
const getContractStatus = (site: Site): "active" | "expiring" | "expired" | "none" => {
|
||||||
|
if (!site.contractStartDate || !site.contractEndDate) return "none";
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const startDate = new Date(site.contractStartDate);
|
||||||
|
const endDate = new Date(site.contractEndDate);
|
||||||
|
|
||||||
|
if (today < startDate) return "none"; // Contratto non ancora iniziato
|
||||||
|
if (today > endDate) return "expired";
|
||||||
|
|
||||||
|
// Calcola i giorni rimanenti
|
||||||
|
const daysLeft = Math.ceil((endDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (daysLeft <= 30) return "expiring"; // In scadenza se mancano 30 giorni o meno
|
||||||
|
|
||||||
|
return "active";
|
||||||
|
};
|
||||||
|
|
||||||
|
const contractStatusLabels = {
|
||||||
|
active: { label: "Contratto Attivo", variant: "default" as const },
|
||||||
|
expiring: { label: "In Scadenza", variant: "outline" as const },
|
||||||
|
expired: { label: "Scaduto", variant: "destructive" as const },
|
||||||
|
none: { label: "Nessun Contratto", variant: "secondary" as const },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -679,12 +705,28 @@ export default function Sites() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<div>
|
<div className="flex flex-wrap gap-2">
|
||||||
<Badge variant="outline">
|
<Badge variant="outline">
|
||||||
{shiftTypeLabels[site.shiftType]}
|
{shiftTypeLabels[site.shiftType]}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{(() => {
|
||||||
|
const status = getContractStatus(site);
|
||||||
|
const statusInfo = contractStatusLabels[status];
|
||||||
|
return (
|
||||||
|
<Badge variant={statusInfo.variant} data-testid={`badge-contract-status-${site.id}`}>
|
||||||
|
{statusInfo.label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{site.contractReference && (
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
Contratto: {site.contractReference}
|
||||||
|
{site.contractEndDate && ` • Scade: ${new Date(site.contractEndDate).toLocaleDateString('it-IT')}`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-1 text-sm">
|
<div className="space-y-1 text-sm">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Users className="h-4 w-4 text-muted-foreground" />
|
<Users className="h-4 w-4 text-muted-foreground" />
|
||||||
|
|||||||
@ -944,7 +944,20 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(400).json({ message: "Missing required fields" });
|
return res.status(400).json({ message: "Missing required fields" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert and validate dates
|
// Verifica stato contratto del sito
|
||||||
|
const site = await storage.getSite(req.body.siteId);
|
||||||
|
if (!site) {
|
||||||
|
return res.status(404).json({ message: "Sito non trovato" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controllo validità contratto - richiesto per creare turni
|
||||||
|
if (!site.contractStartDate || !site.contractEndDate) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Impossibile creare turno: il sito "${site.name}" non ha un contratto attivo`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert and validate shift dates first
|
||||||
const startTime = new Date(req.body.startTime);
|
const startTime = new Date(req.body.startTime);
|
||||||
const endTime = new Date(req.body.endTime);
|
const endTime = new Date(req.body.endTime);
|
||||||
|
|
||||||
@ -952,6 +965,30 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(400).json({ message: "Invalid date format" });
|
return res.status(400).json({ message: "Invalid date format" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalizza date contratto a giorno intero (00:00 - 23:59)
|
||||||
|
const contractStart = new Date(site.contractStartDate);
|
||||||
|
contractStart.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const contractEnd = new Date(site.contractEndDate);
|
||||||
|
contractEnd.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
// Normalizza data turno a giorno (per confronto)
|
||||||
|
const shiftDate = new Date(startTime);
|
||||||
|
shiftDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Verifica che il turno sia dentro il periodo contrattuale
|
||||||
|
if (shiftDate > contractEnd) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Impossibile creare turno: il contratto per il sito "${site.name}" scade il ${new Date(site.contractEndDate).toLocaleDateString('it-IT')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shiftDate < contractStart) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Impossibile creare turno: il contratto per il sito "${site.name}" inizia il ${new Date(site.contractStartDate).toLocaleDateString('it-IT')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Validate and transform the request body
|
// Validate and transform the request body
|
||||||
const validatedData = insertShiftSchema.parse({
|
const validatedData = insertShiftSchema.parse({
|
||||||
siteId: req.body.siteId,
|
siteId: req.body.siteId,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user