Add CCNL (Contratto Collettivo Nazionale di Lavoro) validation logic on the server-side to check shift durations against defined contract parameters. This includes updating the client to handle potential violations and display them to the user via an alert dialog. The server now exposes a new API endpoint (/api/shifts/validate-ccnl) for this validation. Additionally, seeding script has been updated to include default contract parameters. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 42d8028a-fa71-4ec2-938c-e43eedf7df01 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/42d8028a-fa71-4ec2-938c-e43eedf7df01/IdDfihe
256 lines
8.2 KiB
TypeScript
256 lines
8.2 KiB
TypeScript
import { db } from "./db";
|
|
import { users, guards, sites, vehicles, contractParameters } from "@shared/schema";
|
|
import { eq } from "drizzle-orm";
|
|
import bcrypt from "bcrypt";
|
|
|
|
async function seed() {
|
|
console.log("🌱 Avvio seed database multi-sede...");
|
|
|
|
// Create CCNL contract parameters
|
|
console.log("📋 Creazione parametri contrattuali CCNL...");
|
|
const existingParams = await db.select().from(contractParameters).limit(1);
|
|
|
|
if (existingParams.length === 0) {
|
|
await db.insert(contractParameters).values({
|
|
contractType: "CCNL Vigilanza Privata",
|
|
maxHoursPerDay: 9,
|
|
maxOvertimePerDay: 2,
|
|
maxHoursPerWeek: 48,
|
|
maxOvertimePerWeek: 8,
|
|
minDailyRestHours: 11,
|
|
minDailyRestHoursReduced: 9,
|
|
maxDailyRestReductionsPerMonth: 3,
|
|
maxDailyRestReductionsPerYear: 20,
|
|
minWeeklyRestHours: 24,
|
|
maxNightHoursPerWeek: 40,
|
|
pauseMinutesIfOver6Hours: 30,
|
|
holidayPayIncrease: 30,
|
|
nightPayIncrease: 15,
|
|
overtimePayIncrease: 20,
|
|
mealVoucherEnabled: true,
|
|
mealVoucherAfterHours: 6,
|
|
mealVoucherAmount: 8
|
|
});
|
|
console.log(" ✓ Parametri CCNL creati");
|
|
} else {
|
|
console.log(" ✓ Parametri CCNL già esistenti");
|
|
}
|
|
|
|
// Locations
|
|
const locations = ["roccapiemonte", "milano", "roma"] as const;
|
|
const locationNames = {
|
|
roccapiemonte: "Roccapiemonte",
|
|
milano: "Milano",
|
|
roma: "Roma"
|
|
};
|
|
|
|
// Cleanup existing data (optional - comment out to preserve existing data)
|
|
// await db.delete(guards);
|
|
// await db.delete(sites);
|
|
// await db.delete(vehicles);
|
|
|
|
console.log("👥 Creazione guardie per ogni sede...");
|
|
|
|
// Create 10 guards per location
|
|
const guardNames = [
|
|
"Marco Rossi", "Luca Bianchi", "Giuseppe Verdi", "Francesco Romano",
|
|
"Alessandro Russo", "Andrea Marino", "Matteo Ferrari", "Lorenzo Conti",
|
|
"Davide Ricci", "Simone Moretti"
|
|
];
|
|
|
|
for (const location of locations) {
|
|
for (let i = 0; i < 10; i++) {
|
|
const fullName = guardNames[i];
|
|
const [firstName, ...lastNameParts] = fullName.split(" ");
|
|
const lastName = lastNameParts.join(" ");
|
|
const email = `${fullName.toLowerCase().replace(" ", ".")}@${location}.vt.alfacom.it`;
|
|
const badgeNumber = `${location.substring(0, 3).toUpperCase()}${String(i + 1).padStart(3, "0")}`;
|
|
|
|
// Check if user exists
|
|
const existingUser = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email))
|
|
.limit(1);
|
|
|
|
let userId: string;
|
|
|
|
if (existingUser.length > 0) {
|
|
userId = existingUser[0].id;
|
|
console.log(` ✓ Utente esistente: ${email}`);
|
|
} else {
|
|
// Create user
|
|
const hashedPassword = await bcrypt.hash("guard123", 10);
|
|
const [newUser] = await db
|
|
.insert(users)
|
|
.values({
|
|
email,
|
|
firstName,
|
|
lastName,
|
|
passwordHash: hashedPassword,
|
|
role: "guard"
|
|
})
|
|
.returning();
|
|
userId = newUser.id;
|
|
console.log(` + Creato utente: ${email}`);
|
|
}
|
|
|
|
// Check if guard exists
|
|
const existingGuard = await db
|
|
.select()
|
|
.from(guards)
|
|
.where(eq(guards.badgeNumber, badgeNumber))
|
|
.limit(1);
|
|
|
|
if (existingGuard.length === 0) {
|
|
await db.insert(guards).values({
|
|
userId,
|
|
badgeNumber,
|
|
phoneNumber: `+39 ${330 + i} ${Math.floor(Math.random() * 1000000)}`,
|
|
location,
|
|
isArmed: i % 3 === 0, // 1 su 3 è armato
|
|
hasFireSafety: i % 2 === 0, // 1 su 2 ha antincendio
|
|
hasFirstAid: i % 4 === 0, // 1 su 4 ha primo soccorso
|
|
hasDriverLicense: i % 2 === 1, // 1 su 2 ha patente
|
|
languages: i === 0 ? ["italiano", "inglese"] : ["italiano"]
|
|
});
|
|
console.log(` + Creata guardia: ${badgeNumber} - ${name} (${locationNames[location]})`);
|
|
} else {
|
|
console.log(` ✓ Guardia esistente: ${badgeNumber}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log("\n🏢 Creazione clienti per ogni sede...");
|
|
|
|
// Create 10 clients per location
|
|
const companyNames = [
|
|
"Banca Centrale", "Ospedale San Marco", "Centro Commerciale Europa",
|
|
"Uffici Postali", "Museo Arte Moderna", "Palazzo Comunale",
|
|
"Stazione Ferroviaria", "Aeroporto Internazionale", "Università Statale",
|
|
"Tribunale Civile"
|
|
];
|
|
|
|
for (const location of locations) {
|
|
for (let i = 0; i < 10; i++) {
|
|
const companyName = companyNames[i];
|
|
const email = `${companyName.toLowerCase().replace(/ /g, ".")}@${location}.clienti.vt.it`;
|
|
|
|
// Check if client user exists
|
|
const existingClient = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email))
|
|
.limit(1);
|
|
|
|
let clientId: string;
|
|
|
|
if (existingClient.length > 0) {
|
|
clientId = existingClient[0].id;
|
|
console.log(` ✓ Cliente esistente: ${email}`);
|
|
} else {
|
|
const hashedPassword = await bcrypt.hash("client123", 10);
|
|
const [newClient] = await db
|
|
.insert(users)
|
|
.values({
|
|
email,
|
|
firstName: companyName,
|
|
lastName: locationNames[location],
|
|
passwordHash: hashedPassword,
|
|
role: "client"
|
|
})
|
|
.returning();
|
|
clientId = newClient.id;
|
|
console.log(` + Creato cliente: ${email}`);
|
|
}
|
|
|
|
// Check if site exists
|
|
const siteName = `${companyName} - ${locationNames[location]}`;
|
|
const existingSite = await db
|
|
.select()
|
|
.from(sites)
|
|
.where(eq(sites.name, siteName))
|
|
.limit(1);
|
|
|
|
if (existingSite.length === 0) {
|
|
const shiftTypes = ["fixed_post", "patrol", "night_inspection", "quick_response"] as const;
|
|
await db.insert(sites).values({
|
|
name: siteName,
|
|
address: `Via ${companyName} ${i + 1}, ${locationNames[location]}`,
|
|
clientId,
|
|
location,
|
|
shiftType: shiftTypes[i % 4],
|
|
minGuards: Math.floor(Math.random() * 3) + 1,
|
|
requiresArmed: i % 3 === 0,
|
|
requiresDriverLicense: i % 4 === 0,
|
|
isActive: true
|
|
});
|
|
console.log(` + Creato sito: ${siteName}`);
|
|
} else {
|
|
console.log(` ✓ Sito esistente: ${siteName}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log("\n🚗 Creazione automezzi per ogni sede...");
|
|
|
|
// Create vehicles per location
|
|
const vehicleBrands = [
|
|
{ brand: "Fiat", model: "Punto", type: "car" },
|
|
{ brand: "Volkswagen", model: "Polo", type: "car" },
|
|
{ brand: "Ford", model: "Transit", type: "van" },
|
|
{ brand: "Mercedes", model: "Sprinter", type: "van" },
|
|
{ brand: "BMW", model: "GS 750", type: "motorcycle" },
|
|
] as const;
|
|
|
|
for (const location of locations) {
|
|
for (let i = 0; i < 5; i++) {
|
|
const vehicle = vehicleBrands[i];
|
|
const licensePlate = `${location.substring(0, 2).toUpperCase()}${String(Math.floor(Math.random() * 1000)).padStart(3, "0")}${String.fromCharCode(65 + Math.floor(Math.random() * 26))}${String.fromCharCode(65 + Math.floor(Math.random() * 26))}`;
|
|
|
|
// Check if vehicle exists
|
|
const existingVehicle = await db
|
|
.select()
|
|
.from(vehicles)
|
|
.where(eq(vehicles.licensePlate, licensePlate))
|
|
.limit(1);
|
|
|
|
if (existingVehicle.length === 0) {
|
|
await db.insert(vehicles).values({
|
|
licensePlate,
|
|
brand: vehicle.brand,
|
|
model: vehicle.model,
|
|
vehicleType: vehicle.type,
|
|
year: 2018 + Math.floor(Math.random() * 6),
|
|
location,
|
|
status: i === 0 ? "in_use" : "available",
|
|
mileage: Math.floor(Math.random() * 100000) + 10000
|
|
});
|
|
console.log(` + Creato automezzo: ${licensePlate} - ${vehicle.brand} ${vehicle.model} (${locationNames[location]})`);
|
|
} else {
|
|
console.log(` ✓ Automezzo esistente: ${licensePlate}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log("\n✅ Seed completato!");
|
|
console.log(`
|
|
📊 Riepilogo:
|
|
- 30 guardie totali (10 per sede)
|
|
- 30 siti/clienti totali (10 per sede)
|
|
- 15 automezzi totali (5 per sede)
|
|
|
|
🔐 Credenziali:
|
|
- Guardie: *.guardia@[sede].vt.alfacom.it / guard123
|
|
- Clienti: *@[sede].clienti.vt.it / client123
|
|
- Admin: admin@vt.alfacom.it / admin123
|
|
`);
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
seed().catch((error) => {
|
|
console.error("❌ Errore seed:", error);
|
|
process.exit(1);
|
|
});
|