Add location filtering and meal voucher settings
Implements multi-location filtering on Dashboard and Shifts pages, adds meal voucher configuration options in Parameters, and introduces multi-location seeding in server/seed.ts. 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
This commit is contained in:
parent
e21b19c0e6
commit
34221555d8
4
.replit
4
.replit
@ -23,10 +23,6 @@ externalPort = 3001
|
||||
localPort = 41343
|
||||
externalPort = 3000
|
||||
|
||||
[[ports]]
|
||||
localPort = 41607
|
||||
externalPort = 3003
|
||||
|
||||
[[ports]]
|
||||
localPort = 42175
|
||||
externalPort = 3002
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { KPICard } from "@/components/kpi-card";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { StatusBadge } from "@/components/status-badge";
|
||||
import { Users, Calendar, MapPin, AlertTriangle, Clock, CheckCircle } from "lucide-react";
|
||||
import { Users, Calendar, MapPin, AlertTriangle, Clock, CheckCircle, Building2 } from "lucide-react";
|
||||
import { ShiftWithDetails, GuardWithCertifications, Site } from "@shared/schema";
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { it } from "date-fns/locale";
|
||||
@ -11,6 +13,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function Dashboard() {
|
||||
const { user } = useAuth();
|
||||
const [selectedLocation, setSelectedLocation] = useState<string>("all");
|
||||
|
||||
const { data: shifts, isLoading: shiftsLoading } = useQuery<ShiftWithDetails[]>({
|
||||
queryKey: ["/api/shifts/active"],
|
||||
@ -24,26 +27,65 @@ export default function Dashboard() {
|
||||
queryKey: ["/api/sites"],
|
||||
});
|
||||
|
||||
// Filter data by location
|
||||
const filteredGuards = selectedLocation === "all"
|
||||
? guards
|
||||
: guards?.filter(g => g.location === selectedLocation);
|
||||
|
||||
const filteredSites = selectedLocation === "all"
|
||||
? sites
|
||||
: sites?.filter(s => s.location === selectedLocation);
|
||||
|
||||
const filteredShifts = selectedLocation === "all"
|
||||
? shifts
|
||||
: shifts?.filter(s => {
|
||||
const site = sites?.find(site => site.id === s.siteId);
|
||||
return site?.location === selectedLocation;
|
||||
});
|
||||
|
||||
// Calculate KPIs
|
||||
const activeShifts = shifts?.filter(s => s.status === "active").length || 0;
|
||||
const totalGuards = guards?.length || 0;
|
||||
const activeSites = sites?.filter(s => s.isActive).length || 0;
|
||||
const activeShifts = filteredShifts?.filter(s => s.status === "active").length || 0;
|
||||
const totalGuards = filteredGuards?.length || 0;
|
||||
const activeSites = filteredSites?.filter(s => s.isActive).length || 0;
|
||||
|
||||
// Expiring certifications (next 30 days)
|
||||
const expiringCerts = guards?.flatMap(g =>
|
||||
const expiringCerts = filteredGuards?.flatMap(g =>
|
||||
g.certifications.filter(c => c.status === "expiring_soon")
|
||||
).length || 0;
|
||||
|
||||
const isLoading = shiftsLoading || guardsLoading || sitesLoading;
|
||||
|
||||
const locationLabels: Record<string, string> = {
|
||||
all: "Tutte le Sedi",
|
||||
roccapiemonte: "Roccapiemonte",
|
||||
milano: "Milano",
|
||||
roma: "Roma"
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold mb-2">Dashboard Operativa</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Benvenuto, {user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Select value={selectedLocation} onValueChange={setSelectedLocation}>
|
||||
<SelectTrigger className="w-[200px]" data-testid="select-location">
|
||||
<SelectValue placeholder="Seleziona sede" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Tutte le Sedi</SelectItem>
|
||||
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
||||
<SelectItem value="milano">Milano</SelectItem>
|
||||
<SelectItem value="roma">Roma</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
@ -103,9 +145,9 @@ export default function Dashboard() {
|
||||
<Skeleton className="h-16" />
|
||||
<Skeleton className="h-16" />
|
||||
</div>
|
||||
) : shifts && shifts.length > 0 ? (
|
||||
) : filteredShifts && filteredShifts.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{shifts.slice(0, 5).map((shift) => (
|
||||
{filteredShifts.slice(0, 5).map((shift) => (
|
||||
<div
|
||||
key={shift.id}
|
||||
className="flex items-center justify-between p-3 rounded-md border hover-elevate"
|
||||
@ -148,9 +190,9 @@ export default function Dashboard() {
|
||||
<Skeleton className="h-16" />
|
||||
<Skeleton className="h-16" />
|
||||
</div>
|
||||
) : guards ? (
|
||||
) : filteredGuards ? (
|
||||
<div className="space-y-3">
|
||||
{guards
|
||||
{filteredGuards
|
||||
.flatMap(guard =>
|
||||
guard.certifications
|
||||
.filter(c => c.status === "expiring_soon" || c.status === "expired")
|
||||
@ -176,7 +218,7 @@ export default function Dashboard() {
|
||||
</StatusBadge>
|
||||
</div>
|
||||
))}
|
||||
{guards.flatMap(g => g.certifications.filter(c => c.status !== "valid")).length === 0 && (
|
||||
{filteredGuards.flatMap(g => g.certifications.filter(c => c.status !== "valid")).length === 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground justify-center py-8">
|
||||
<CheckCircle className="h-4 w-4 text-[hsl(140,60%,45%)]" />
|
||||
Tutte le certificazioni sono valide
|
||||
|
||||
@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Loader2, Save, Settings } from "lucide-react";
|
||||
import type { ContractParameters } from "@shared/schema";
|
||||
@ -345,6 +346,61 @@ export default function Parameters() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Buoni Pasto */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Buoni Pasto</CardTitle>
|
||||
<CardDescription>Configurazione ticket restaurant per turni superiori a soglia ore</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="mealVoucherEnabled">Buoni Pasto Attivi</Label>
|
||||
<Switch
|
||||
id="mealVoucherEnabled"
|
||||
checked={formData.mealVoucherEnabled ?? true}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, mealVoucherEnabled: checked })}
|
||||
disabled={!isEditing}
|
||||
data-testid="switch-meal-voucher-enabled"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Abilita emissione buoni pasto automatici
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mealVoucherAfterHours">Ore Minime per Buono Pasto</Label>
|
||||
<Input
|
||||
id="mealVoucherAfterHours"
|
||||
type="number"
|
||||
value={formData.mealVoucherAfterHours ?? 6}
|
||||
onChange={(e) => setFormData({ ...formData, mealVoucherAfterHours: parseInt(e.target.value) })}
|
||||
disabled={!isEditing || !formData.mealVoucherEnabled}
|
||||
data-testid="input-meal-voucher-after-hours"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ore di turno necessarie per diritto al buono
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mealVoucherAmount">Importo Buono Pasto (€)</Label>
|
||||
<Input
|
||||
id="mealVoucherAmount"
|
||||
type="number"
|
||||
value={formData.mealVoucherAmount ?? 8}
|
||||
onChange={(e) => setFormData({ ...formData, mealVoucherAmount: parseInt(e.target.value) })}
|
||||
disabled={!isEditing || !formData.mealVoucherEnabled}
|
||||
data-testid="input-meal-voucher-amount"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Valore nominale ticket (facoltativo)
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Tipo Contratto */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { insertShiftFormSchema } from "@shared/schema";
|
||||
import { Plus, Calendar, MapPin, Users, Clock, UserPlus, X, Shield, Car, Heart, Flame, Pencil } from "lucide-react";
|
||||
import { Plus, Calendar, MapPin, Users, Clock, UserPlus, X, Shield, Car, Heart, Flame, Pencil, Building2 } from "lucide-react";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { StatusBadge } from "@/components/status-badge";
|
||||
@ -24,6 +24,7 @@ export default function Shifts() {
|
||||
const [selectedShift, setSelectedShift] = useState<ShiftWithDetails | null>(null);
|
||||
const [isAssignDialogOpen, setIsAssignDialogOpen] = useState(false);
|
||||
const [editingShift, setEditingShift] = useState<ShiftWithDetails | null>(null);
|
||||
const [selectedLocation, setSelectedLocation] = useState<string>("all");
|
||||
|
||||
const { data: shifts, isLoading: shiftsLoading } = useQuery<ShiftWithDetails[]>({
|
||||
queryKey: ["/api/shifts"],
|
||||
@ -37,6 +38,22 @@ export default function Shifts() {
|
||||
queryKey: ["/api/guards"],
|
||||
});
|
||||
|
||||
// Filter data by location
|
||||
const filteredShifts = selectedLocation === "all"
|
||||
? shifts
|
||||
: shifts?.filter(s => {
|
||||
const site = sites?.find(site => site.id === s.siteId);
|
||||
return site?.location === selectedLocation;
|
||||
});
|
||||
|
||||
const filteredSites = selectedLocation === "all"
|
||||
? sites
|
||||
: sites?.filter(s => s.location === selectedLocation);
|
||||
|
||||
const filteredGuards = selectedLocation === "all"
|
||||
? guards
|
||||
: guards?.filter(g => g.location === selectedLocation);
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(insertShiftFormSchema),
|
||||
defaultValues: {
|
||||
@ -238,6 +255,21 @@ export default function Shifts() {
|
||||
Calendario 24/7 con assegnazione guardie
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Select value={selectedLocation} onValueChange={setSelectedLocation}>
|
||||
<SelectTrigger className="w-[180px]" data-testid="select-location-shifts">
|
||||
<SelectValue placeholder="Seleziona sede" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Tutte le Sedi</SelectItem>
|
||||
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
||||
<SelectItem value="milano">Milano</SelectItem>
|
||||
<SelectItem value="roma">Roma</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button data-testid="button-add-shift">
|
||||
@ -267,7 +299,7 @@ export default function Shifts() {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{sites?.map((site) => (
|
||||
{filteredSites?.map((site) => (
|
||||
<SelectItem key={site.id} value={site.id}>
|
||||
{site.name}
|
||||
</SelectItem>
|
||||
@ -345,6 +377,7 @@ export default function Shifts() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shiftsLoading ? (
|
||||
<div className="space-y-4">
|
||||
@ -352,9 +385,9 @@ export default function Shifts() {
|
||||
<Skeleton className="h-32" />
|
||||
<Skeleton className="h-32" />
|
||||
</div>
|
||||
) : shifts && shifts.length > 0 ? (
|
||||
) : filteredShifts && filteredShifts.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{shifts.map((shift) => (
|
||||
{filteredShifts.map((shift) => (
|
||||
<Card key={shift.id} className="hover-elevate" data-testid={`card-shift-${shift.id}`}>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
@ -493,7 +526,7 @@ export default function Shifts() {
|
||||
<h3 className="font-medium">Guardie Disponibili</h3>
|
||||
{guards && guards.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{guards.map((guard) => {
|
||||
{filteredGuards?.map((guard) => {
|
||||
const assigned = isGuardAssigned(guard.id);
|
||||
const canAssign = canGuardBeAssigned(guard);
|
||||
|
||||
@ -585,7 +618,7 @@ export default function Shifts() {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{sites?.map((site) => (
|
||||
{filteredSites?.map((site) => (
|
||||
<SelectItem key={site.id} value={site.id}>
|
||||
{site.name}
|
||||
</SelectItem>
|
||||
|
||||
225
server/seed.ts
Normal file
225
server/seed.ts
Normal file
@ -0,0 +1,225 @@
|
||||
import { db } from "./db";
|
||||
import { users, guards, sites, vehicles } from "@shared/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
async function seed() {
|
||||
console.log("🌱 Avvio seed database multi-sede...");
|
||||
|
||||
// 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);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user