Implement API endpoints for managing CCNL settings and introduce a rules engine for validating shifts against these settings, enhancing compliance with labor regulations. 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/EEOXc3D
326 lines
11 KiB
TypeScript
326 lines
11 KiB
TypeScript
import { db } from "./db";
|
|
import { users, guards, sites, vehicles, contractParameters, serviceTypes, ccnlSettings } from "@shared/schema";
|
|
import { eq } from "drizzle-orm";
|
|
import bcrypt from "bcrypt";
|
|
|
|
async function seed() {
|
|
console.log("🌱 Avvio seed database multi-sede...");
|
|
|
|
// Create Service Types
|
|
console.log("📝 Creazione tipologie di servizi...");
|
|
const defaultServiceTypes = [
|
|
{
|
|
code: "fixed_post",
|
|
label: "Presidio Fisso",
|
|
description: "Guardia fissa presso una struttura",
|
|
icon: "Building2",
|
|
color: "blue",
|
|
isActive: true
|
|
},
|
|
{
|
|
code: "patrol",
|
|
label: "Pattugliamento",
|
|
description: "Ronde e controlli su area",
|
|
icon: "Eye",
|
|
color: "green",
|
|
isActive: true
|
|
},
|
|
{
|
|
code: "night_inspection",
|
|
label: "Ispettorato Notturno",
|
|
description: "Controlli notturni programmati",
|
|
icon: "Shield",
|
|
color: "purple",
|
|
isActive: true
|
|
},
|
|
{
|
|
code: "quick_response",
|
|
label: "Pronto Intervento",
|
|
description: "Intervento rapido su chiamata",
|
|
icon: "Zap",
|
|
color: "orange",
|
|
isActive: true
|
|
}
|
|
];
|
|
|
|
for (const serviceType of defaultServiceTypes) {
|
|
const existing = await db.select().from(serviceTypes).where(eq(serviceTypes.code, serviceType.code)).limit(1);
|
|
if (existing.length === 0) {
|
|
await db.insert(serviceTypes).values(serviceType);
|
|
console.log(` + Creata tipologia: ${serviceType.label}`);
|
|
} else {
|
|
console.log(` ✓ Tipologia esistente: ${serviceType.label}`);
|
|
}
|
|
}
|
|
|
|
// Create CCNL Settings
|
|
console.log("⚙️ Creazione impostazioni CCNL...");
|
|
const defaultCcnlSettings = [
|
|
{ key: "max_hours_per_week", value: "48", description: "Massimo ore lavorative settimanali" },
|
|
{ key: "max_hours_per_month", value: "190", description: "Massimo ore lavorative mensili" },
|
|
{ key: "min_rest_hours_between_shifts", value: "11", description: "Ore minime di riposo tra turni" },
|
|
{ key: "max_night_shifts_consecutive", value: "6", description: "Massimo turni notturni consecutivi" },
|
|
{ key: "max_days_consecutive", value: "6", description: "Massimo giorni lavorativi consecutivi" },
|
|
{ key: "min_weekly_rest_hours", value: "24", description: "Ore minime di riposo settimanale" },
|
|
{ key: "overtime_threshold_week", value: "40", description: "Soglia ore per straordinario settimanale" },
|
|
{ key: "penalty_if_overtime", value: "true", description: "Penalità se supera straordinario" },
|
|
];
|
|
|
|
for (const setting of defaultCcnlSettings) {
|
|
const existing = await db.select().from(ccnlSettings).where(eq(ccnlSettings.key, setting.key)).limit(1);
|
|
if (existing.length === 0) {
|
|
await db.insert(ccnlSettings).values(setting);
|
|
console.log(` + Creata impostazione: ${setting.key}`);
|
|
} else {
|
|
console.log(` ✓ Impostazione esistente: ${setting.key}`);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
});
|