Compare commits

..

No commits in common. "cd986635e4a67a3ff5e36f9a41e8d928feb216f8" and "4693b782cb64b9e5963992771f283cb2e8f5d320" have entirely different histories.

10 changed files with 99 additions and 306 deletions

View File

@ -28,7 +28,7 @@ localPort = 42175
externalPort = 3002 externalPort = 3002
[[ports]] [[ports]]
localPort = 43267 localPort = 42403
externalPort = 3003 externalPort = 3003
[env] [env]

View File

@ -3,7 +3,7 @@ import { useQuery, useMutation } from "@tanstack/react-query";
import { queryClient, apiRequest } from "@/lib/queryClient"; import { queryClient, apiRequest } from "@/lib/queryClient";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { insertSiteSchema, insertServiceTypeSchema, type Site, type ServiceType } from "@shared/schema"; import { insertSiteSchema, type Site } from "@shared/schema";
import { z } from "zod"; import { z } from "zod";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -35,27 +35,34 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import * as LucideIcons from "lucide-react"; import { Building2, Shield, Eye, MapPin, Zap, Plus, Pencil } from "lucide-react";
import { MapPin, Plus, Pencil } from "lucide-react";
// Helper to get icon component from name const serviceTypeInfo = {
const getIconComponent = (iconName: string) => { fixed_post: {
const Icon = (LucideIcons as any)[iconName]; label: "Presidio Fisso",
return Icon || LucideIcons.Building2; description: "Guardia fissa presso una struttura",
}; icon: Building2,
color: "bg-blue-500/10 text-blue-500 border-blue-500/20"
// Helper to get color classes from color name },
const getColorClasses = (color: string) => { patrol: {
const colorMap: Record<string, string> = { label: "Pattugliamento",
blue: "bg-blue-500/10 text-blue-500 border-blue-500/20", description: "Ronde e controlli su area",
green: "bg-green-500/10 text-green-500 border-green-500/20", icon: Eye,
purple: "bg-purple-500/10 text-purple-500 border-purple-500/20", color: "bg-green-500/10 text-green-500 border-green-500/20"
orange: "bg-orange-500/10 text-orange-500 border-orange-500/20", },
red: "bg-red-500/10 text-red-500 border-red-500/20", night_inspection: {
yellow: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" label: "Ispettorato Notturno",
}; description: "Controlli notturni programmati",
return colorMap[color] || colorMap.blue; icon: Shield,
}; color: "bg-purple-500/10 text-purple-500 border-purple-500/20"
},
quick_response: {
label: "Pronto Intervento",
description: "Intervento rapido su chiamata",
icon: Zap,
color: "bg-orange-500/10 text-orange-500 border-orange-500/20"
}
} as const;
type SiteForm = z.infer<typeof insertSiteSchema>; type SiteForm = z.infer<typeof insertSiteSchema>;
@ -66,16 +73,10 @@ export default function Services() {
const [editDialogOpen, setEditDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false);
const [selectedSite, setSelectedSite] = useState<Site | null>(null); const [selectedSite, setSelectedSite] = useState<Site | null>(null);
const { data: sites = [], isLoading: isLoadingSites } = useQuery<Site[]>({ const { data: sites = [], isLoading } = useQuery<Site[]>({
queryKey: ["/api/sites"], queryKey: ["/api/sites"],
}); });
const { data: serviceTypes = [], isLoading: isLoadingServiceTypes } = useQuery<ServiceType[]>({
queryKey: ["/api/service-types"],
});
const isLoading = isLoadingSites || isLoadingServiceTypes;
const createForm = useForm<SiteForm>({ const createForm = useForm<SiteForm>({
resolver: zodResolver(insertSiteSchema), resolver: zodResolver(insertSiteSchema),
defaultValues: { defaultValues: {
@ -182,9 +183,9 @@ export default function Services() {
}; };
// Calculate statistics per service type // Calculate statistics per service type
const stats = serviceTypes.reduce((acc, serviceType) => { const stats = Object.keys(serviceTypeInfo).reduce((acc, type) => {
const sitesForType = sites.filter(s => s.shiftType === serviceType.code); const sitesForType = sites.filter(s => s.shiftType === type);
acc[serviceType.code] = { acc[type] = {
total: sitesForType.length, total: sitesForType.length,
active: sitesForType.filter(s => s.isActive).length, active: sitesForType.filter(s => s.isActive).length,
requiresArmed: sitesForType.filter(s => s.requiresArmed).length, requiresArmed: sitesForType.filter(s => s.requiresArmed).length,
@ -213,48 +214,56 @@ export default function Services() {
) : ( ) : (
<> <>
<div className="grid gap-6 md:grid-cols-2"> <div className="grid gap-6 md:grid-cols-2">
{serviceTypes.map((serviceType) => { {Object.entries(serviceTypeInfo).map(([type, info]) => {
const Icon = getIconComponent(serviceType.icon); const Icon = info.icon;
const stat = stats[serviceType.code] || { total: 0, active: 0, requiresArmed: 0, requiresDriver: 0 }; const stat = stats[type] || { total: 0, active: 0, requiresArmed: 0, requiresDriver: 0 };
return ( return (
<Card key={serviceType.id} data-testid={`card-service-${serviceType.code}`}> <Card key={type} data-testid={`card-service-${type}`}>
<CardHeader> <CardHeader>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${getColorClasses(serviceType.color)}`}> <div className={`p-2 rounded-lg ${info.color}`}>
<Icon className="h-6 w-6" /> <Icon className="h-6 w-6" />
</div> </div>
<div> <div>
<CardTitle className="text-xl">{serviceType.label}</CardTitle> <CardTitle className="text-xl">{info.label}</CardTitle>
<CardDescription className="mt-1">{serviceType.description}</CardDescription> <CardDescription className="mt-1">{info.description}</CardDescription>
</div> </div>
</div> </div>
<Button
size="sm"
onClick={() => handleCreateSite(type)}
data-testid={`button-add-site-${type}`}
>
<Plus className="h-4 w-4 mr-1" />
Aggiungi Sito
</Button>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-sm text-muted-foreground">Siti Totali</p> <p className="text-sm text-muted-foreground">Siti Totali</p>
<p className="text-2xl font-semibold" data-testid={`text-total-${serviceType.code}`}> <p className="text-2xl font-semibold" data-testid={`text-total-${type}`}>
{stat.total} {stat.total}
</p> </p>
</div> </div>
<div> <div>
<p className="text-sm text-muted-foreground">Attivi</p> <p className="text-sm text-muted-foreground">Attivi</p>
<p className="text-2xl font-semibold text-green-500" data-testid={`text-active-${serviceType.code}`}> <p className="text-2xl font-semibold text-green-500" data-testid={`text-active-${type}`}>
{stat.active} {stat.active}
</p> </p>
</div> </div>
<div> <div>
<p className="text-sm text-muted-foreground">Richiedono Armati</p> <p className="text-sm text-muted-foreground">Richiedono Armati</p>
<p className="text-lg font-semibold" data-testid={`text-armed-${serviceType.code}`}> <p className="text-lg font-semibold" data-testid={`text-armed-${type}`}>
{stat.requiresArmed} {stat.requiresArmed}
</p> </p>
</div> </div>
<div> <div>
<p className="text-sm text-muted-foreground">Richiedono Patente</p> <p className="text-sm text-muted-foreground">Richiedono Patente</p>
<p className="text-lg font-semibold" data-testid={`text-driver-${serviceType.code}`}> <p className="text-lg font-semibold" data-testid={`text-driver-${type}`}>
{stat.requiresDriver} {stat.requiresDriver}
</p> </p>
</div> </div>
@ -264,15 +273,15 @@ export default function Services() {
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<Badge variant="outline" className="font-normal"> <Badge variant="outline" className="font-normal">
<MapPin className="h-3 w-3 mr-1" /> <MapPin className="h-3 w-3 mr-1" />
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'roccapiemonte').length} Roccapiemonte {sites.filter(s => s.shiftType === type && s.location === 'roccapiemonte').length} Roccapiemonte
</Badge> </Badge>
<Badge variant="outline" className="font-normal"> <Badge variant="outline" className="font-normal">
<MapPin className="h-3 w-3 mr-1" /> <MapPin className="h-3 w-3 mr-1" />
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'milano').length} Milano {sites.filter(s => s.shiftType === type && s.location === 'milano').length} Milano
</Badge> </Badge>
<Badge variant="outline" className="font-normal"> <Badge variant="outline" className="font-normal">
<MapPin className="h-3 w-3 mr-1" /> <MapPin className="h-3 w-3 mr-1" />
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'roma').length} Roma {sites.filter(s => s.shiftType === type && s.location === 'roma').length} Roma
</Badge> </Badge>
</div> </div>
</div> </div>
@ -282,10 +291,10 @@ export default function Services() {
variant="ghost" variant="ghost"
size="sm" size="sm"
className="w-full mt-4" className="w-full mt-4"
onClick={() => setSelectedServiceType(selectedServiceType === serviceType.code ? null : serviceType.code)} onClick={() => setSelectedServiceType(selectedServiceType === type ? null : type)}
data-testid={`button-view-sites-${serviceType.code}`} data-testid={`button-view-sites-${type}`}
> >
{selectedServiceType === serviceType.code ? "Nascondi" : "Visualizza"} Siti ({stat.total}) {selectedServiceType === type ? "Nascondi" : "Visualizza"} Siti ({stat.total})
</Button> </Button>
)} )}
</CardContent> </CardContent>
@ -298,7 +307,7 @@ export default function Services() {
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
Siti {serviceTypes.find(st => st.code === selectedServiceType)?.label || ""} Siti {serviceTypeInfo[selectedServiceType as keyof typeof serviceTypeInfo].label}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
Lista completa dei siti con questo tipo di servizio Lista completa dei siti con questo tipo di servizio
@ -420,9 +429,10 @@ export default function Services() {
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
{serviceTypes.filter(st => st.isActive).map(st => ( <SelectItem value="fixed_post">Presidio Fisso</SelectItem>
<SelectItem key={st.id} value={st.code}>{st.label}</SelectItem> <SelectItem value="patrol">Pattugliamento</SelectItem>
))} <SelectItem value="night_inspection">Ispettorato Notturno</SelectItem>
<SelectItem value="quick_response">Pronto Intervento</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormMessage /> <FormMessage />
@ -606,9 +616,10 @@ export default function Services() {
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
{serviceTypes.filter(st => st.isActive).map(st => ( <SelectItem value="fixed_post">Presidio Fisso</SelectItem>
<SelectItem key={st.id} value={st.code}>{st.label}</SelectItem> <SelectItem value="patrol">Pattugliamento</SelectItem>
))} <SelectItem value="night_inspection">Ispettorato Notturno</SelectItem>
<SelectItem value="quick_response">Pronto Intervento</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormMessage /> <FormMessage />

View File

@ -511,18 +511,14 @@ export default function Vehicles() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Assegnato a</FormLabel> <FormLabel>Assegnato a</FormLabel>
<Select <Select onValueChange={field.onChange} value={field.value || ""} data-testid="select-create-guard">
onValueChange={(value) => field.onChange(value === "none" ? null : value)}
value={field.value || "none"}
data-testid="select-create-guard"
>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Seleziona guardia" /> <SelectValue placeholder="Seleziona guardia" />
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
<SelectItem value="none">Nessuno</SelectItem> <SelectItem value="">Nessuno</SelectItem>
{guards?.map(guard => ( {guards?.map(guard => (
<SelectItem key={guard.id} value={guard.id}> <SelectItem key={guard.id} value={guard.id}>
{guard.badgeNumber} {guard.badgeNumber}
@ -742,18 +738,14 @@ export default function Vehicles() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Assegnato a</FormLabel> <FormLabel>Assegnato a</FormLabel>
<Select <Select onValueChange={field.onChange} value={field.value || ""} data-testid="select-edit-guard">
onValueChange={(value) => field.onChange(value === "none" ? null : value)}
value={field.value || "none"}
data-testid="select-edit-guard"
>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Seleziona guardia" /> <SelectValue placeholder="Seleziona guardia" />
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
<SelectItem value="none">Nessuno</SelectItem> <SelectItem value="">Nessuno</SelectItem>
{guards?.map(guard => ( {guards?.map(guard => (
<SelectItem key={guard.id} value={guard.id}> <SelectItem key={guard.id} value={guard.id}>
{guard.badgeNumber} {guard.badgeNumber}

View File

@ -135,8 +135,7 @@ else
log_info "Creo backup: $BACKUP_FILE" log_info "Creo backup: $BACKUP_FILE"
# Backup con pg_dump (include schema e dati) # Backup con pg_dump (include schema e dati)
# Opzioni: --clean (DROP before CREATE), --if-exists (no error if not exists), --inserts (INSERT statements per compatibilità) if pg_dump "$DATABASE_URL" > "$BACKUP_FILE" 2>/dev/null; then
if pg_dump "$DATABASE_URL" --clean --if-exists --inserts > "$BACKUP_FILE"; then
# Comprimi backup # Comprimi backup
gzip "$BACKUP_FILE" gzip "$BACKUP_FILE"
BACKUP_FILE="${BACKUP_FILE}.gz" BACKUP_FILE="${BACKUP_FILE}.gz"
@ -148,7 +147,7 @@ else
log_info "Pulizia backup vecchi (mantengo ultimi 10)..." log_info "Pulizia backup vecchi (mantengo ultimi 10)..."
ls -t $BACKUP_DIR/*.sql.gz 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true ls -t $BACKUP_DIR/*.sql.gz 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true
else else
log_error "Backup database fallito! Verifica DATABASE_URL e permessi pg_dump" log_error "Backup database fallito!"
exit 1 exit 1
fi fi
fi fi

View File

@ -2,21 +2,11 @@
set -e set -e
# Script di deployment automatico per VigilanzaTurni # Script di deployment automatico per VigilanzaTurni
# Uso: # Uso: bash deploy/deploy.sh
# bash deploy/deploy.sh → deployment normale
# bash deploy/deploy.sh db → deployment + importa backup database da Git
APP_DIR="/var/www/vigilanza-turni" APP_DIR="/var/www/vigilanza-turni"
APP_NAME="vigilanza-turni" APP_NAME="vigilanza-turni"
BACKUP_DIR="/var/backups/vigilanza-turni" BACKUP_DIR="/var/backups/vigilanza-turni"
GIT_BACKUP_DIR="$APP_DIR/database-backups"
# Verifica parametro per import database
IMPORT_DB=false
if [ "$1" == "db" ]; then
IMPORT_DB=true
echo "🔄 Modalità: Deployment con import database da Git"
fi
echo "🚀 Deployment VigilanzaTurni - $(date)" echo "🚀 Deployment VigilanzaTurni - $(date)"
@ -29,54 +19,18 @@ if [ -d .git ]; then
git pull origin main || true git pull origin main || true
fi fi
# =================== BACKUP DATABASE ===================
echo "💾 Backup database pre-deployment..."
mkdir -p $BACKUP_DIR
BACKUP_FILE="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql"
# Load env vars # Load env vars
if [ -f .env ]; then if [ -f .env ]; then
source .env source .env
fi fi
# =================== IMPORT DATABASE DA GIT (se richiesto) =================== # Esegui backup PostgreSQL
if [ "$IMPORT_DB" = true ]; then if command -v pg_dump &> /dev/null; then
echo "📦 Import database da backup Git..."
# Trova l'ultimo backup nella directory git
LATEST_GIT_BACKUP=$(ls -t $GIT_BACKUP_DIR/*.sql.gz 2>/dev/null | head -1)
if [ -f "$LATEST_GIT_BACKUP" ]; then
echo "📥 Trovato backup: $LATEST_GIT_BACKUP"
# Controlla che le variabili siano definite
if [ -z "$PGDATABASE" ] || [ -z "$PGUSER" ]; then
echo "❌ Variabili DB non trovate nel .env, impossibile importare"
exit 1
fi
# Chiedi conferma (opzionale - rimuovi se vuoi import automatico)
echo "⚠️ ATTENZIONE: Questo sovrascriverà il database corrente!"
echo "Backup da importare: $(basename $LATEST_GIT_BACKUP)"
read -p "Continuare? (s/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Ss]$ ]]; then
# Decomprimi e importa
echo "🔄 Importazione database in corso..."
gunzip -c "$LATEST_GIT_BACKUP" | PGPASSWORD=$PGPASSWORD psql -h ${PGHOST:-localhost} -U $PGUSER --dbname=$PGDATABASE
echo "✅ Database importato con successo da Git!"
else
echo "❌ Import annullato dall'utente"
exit 1
fi
else
echo "❌ Nessun backup trovato in $GIT_BACKUP_DIR"
exit 1
fi
else
# =================== BACKUP DATABASE NORMALE ===================
echo "💾 Backup database pre-deployment..."
mkdir -p $BACKUP_DIR
BACKUP_FILE="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql"
# Esegui backup PostgreSQL
if command -v pg_dump &> /dev/null; then
# Controlla che le variabili siano definite # Controlla che le variabili siano definite
if [ -z "$PGDATABASE" ] || [ -z "$PGUSER" ]; then if [ -z "$PGDATABASE" ] || [ -z "$PGUSER" ]; then
echo "⚠️ Variabili DB non trovate nel .env, skip backup" echo "⚠️ Variabili DB non trovate nel .env, skip backup"
@ -92,15 +46,14 @@ else
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +30 -delete find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +30 -delete
echo "🧹 Backup vecchi eliminati (retention: 30 giorni)" echo "🧹 Backup vecchi eliminati (retention: 30 giorni)"
fi fi
else else
echo "⚠️ pg_dump non trovato, skip backup" echo "⚠️ pg_dump non trovato, skip backup"
fi
fi fi
# =================== BUILD & DEPLOY =================== # =================== BUILD & DEPLOY ===================
# Installa TUTTE le dipendenze (serve per build e migrations) # Installa TUTTE le dipendenze (serve per build e migrations)
echo "📥 Installazione dipendenze (include devDependencies)..." echo "📥 Installazione dipendenze (include devDependencies)..."
npm ci --include=dev npm ci
# Build frontend # Build frontend
echo "🏗️ Build frontend Vite..." echo "🏗️ Build frontend Vite..."

View File

@ -418,53 +418,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
}); });
// ============= SERVICE TYPE ROUTES =============
app.get("/api/service-types", isAuthenticated, async (req, res) => {
try {
const serviceTypes = await storage.getAllServiceTypes();
res.json(serviceTypes);
} catch (error) {
console.error("Error fetching service types:", error);
res.status(500).json({ message: "Failed to fetch service types" });
}
});
app.post("/api/service-types", isAuthenticated, async (req, res) => {
try {
const serviceType = await storage.createServiceType(req.body);
res.json(serviceType);
} catch (error) {
console.error("Error creating service type:", error);
res.status(500).json({ message: "Failed to create service type" });
}
});
app.patch("/api/service-types/:id", isAuthenticated, async (req, res) => {
try {
const serviceType = await storage.updateServiceType(req.params.id, req.body);
if (!serviceType) {
return res.status(404).json({ message: "Service type not found" });
}
res.json(serviceType);
} catch (error) {
console.error("Error updating service type:", error);
res.status(500).json({ message: "Failed to update service type" });
}
});
app.delete("/api/service-types/:id", isAuthenticated, async (req, res) => {
try {
const serviceType = await storage.deleteServiceType(req.params.id);
if (!serviceType) {
return res.status(404).json({ message: "Service type not found" });
}
res.json(serviceType);
} catch (error) {
console.error("Error deleting service type:", error);
res.status(500).json({ message: "Failed to delete service type" });
}
});
// ============= SITE ROUTES ============= // ============= SITE ROUTES =============
app.get("/api/sites", isAuthenticated, async (req, res) => { app.get("/api/sites", isAuthenticated, async (req, res) => {
try { try {

View File

@ -1,58 +1,11 @@
import { db } from "./db"; import { db } from "./db";
import { users, guards, sites, vehicles, contractParameters, serviceTypes } from "@shared/schema"; import { users, guards, sites, vehicles, contractParameters } from "@shared/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
async function seed() { async function seed() {
console.log("🌱 Avvio seed database multi-sede..."); 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 contract parameters // Create CCNL contract parameters
console.log("📋 Creazione parametri contrattuali CCNL..."); console.log("📋 Creazione parametri contrattuali CCNL...");
const existingParams = await db.select().from(contractParameters).limit(1); const existingParams = await db.select().from(contractParameters).limit(1);

View File

@ -16,7 +16,6 @@ import {
absences, absences,
absenceAffectedShifts, absenceAffectedShifts,
contractParameters, contractParameters,
serviceTypes,
type User, type User,
type UpsertUser, type UpsertUser,
type Guard, type Guard,
@ -49,8 +48,6 @@ import {
type InsertAbsenceAffectedShift, type InsertAbsenceAffectedShift,
type ContractParameters, type ContractParameters,
type InsertContractParameters, type InsertContractParameters,
type ServiceType,
type InsertServiceType,
} from "@shared/schema"; } from "@shared/schema";
import { db } from "./db"; import { db } from "./db";
import { eq, and, gte, lte, desc } from "drizzle-orm"; import { eq, and, gte, lte, desc } from "drizzle-orm";
@ -73,13 +70,6 @@ export interface IStorage {
createCertification(cert: InsertCertification): Promise<Certification>; createCertification(cert: InsertCertification): Promise<Certification>;
updateCertificationStatus(id: string, status: "valid" | "expiring_soon" | "expired"): Promise<void>; updateCertificationStatus(id: string, status: "valid" | "expiring_soon" | "expired"): Promise<void>;
// Service Type operations
getAllServiceTypes(): Promise<ServiceType[]>;
getServiceType(id: string): Promise<ServiceType | undefined>;
createServiceType(serviceType: InsertServiceType): Promise<ServiceType>;
updateServiceType(id: string, serviceType: Partial<InsertServiceType>): Promise<ServiceType | undefined>;
deleteServiceType(id: string): Promise<ServiceType | undefined>;
// Site operations // Site operations
getAllSites(): Promise<Site[]>; getAllSites(): Promise<Site[]>;
getSite(id: string): Promise<Site | undefined>; getSite(id: string): Promise<Site | undefined>;
@ -282,35 +272,6 @@ export class DatabaseStorage implements IStorage {
.where(eq(certifications.id, id)); .where(eq(certifications.id, id));
} }
// Service Type operations
async getAllServiceTypes(): Promise<ServiceType[]> {
return await db.select().from(serviceTypes).orderBy(desc(serviceTypes.createdAt));
}
async getServiceType(id: string): Promise<ServiceType | undefined> {
const [serviceType] = await db.select().from(serviceTypes).where(eq(serviceTypes.id, id));
return serviceType;
}
async createServiceType(serviceType: InsertServiceType): Promise<ServiceType> {
const [newServiceType] = await db.insert(serviceTypes).values(serviceType).returning();
return newServiceType;
}
async updateServiceType(id: string, serviceTypeData: Partial<InsertServiceType>): Promise<ServiceType | undefined> {
const [updated] = await db
.update(serviceTypes)
.set({ ...serviceTypeData, updatedAt: new Date() })
.where(eq(serviceTypes.id, id))
.returning();
return updated;
}
async deleteServiceType(id: string): Promise<ServiceType | undefined> {
const [deleted] = await db.delete(serviceTypes).where(eq(serviceTypes.id, id)).returning();
return deleted;
}
// Site operations // Site operations
async getAllSites(): Promise<Site[]> { async getAllSites(): Promise<Site[]> {
return await db.select().from(sites); return await db.select().from(sites);

View File

@ -174,20 +174,6 @@ export const vehicles = pgTable("vehicles", {
updatedAt: timestamp("updated_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow(),
}); });
// ============= SERVICE TYPES =============
export const serviceTypes = pgTable("service_types", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
code: varchar("code").notNull().unique(), // fixed_post, patrol, etc.
label: varchar("label").notNull(), // Presidio Fisso, Pattugliamento, etc.
description: text("description"), // Descrizione dettagliata
icon: varchar("icon").notNull().default("Building2"), // Nome icona Lucide
color: varchar("color").notNull().default("blue"), // blue, green, purple, orange
isActive: boolean("is_active").default(true),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// ============= SITES & CONTRACTS ============= // ============= SITES & CONTRACTS =============
export const sites = pgTable("sites", { export const sites = pgTable("sites", {
@ -623,12 +609,6 @@ export const insertVehicleSchema = createInsertSchema(vehicles).omit({
updatedAt: true, updatedAt: true,
}); });
export const insertServiceTypeSchema = createInsertSchema(serviceTypes).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertSiteSchema = createInsertSchema(sites).omit({ export const insertSiteSchema = createInsertSchema(sites).omit({
id: true, id: true,
createdAt: true, createdAt: true,
@ -719,9 +699,6 @@ export type Certification = typeof certifications.$inferSelect;
export type InsertVehicle = z.infer<typeof insertVehicleSchema>; export type InsertVehicle = z.infer<typeof insertVehicleSchema>;
export type Vehicle = typeof vehicles.$inferSelect; export type Vehicle = typeof vehicles.$inferSelect;
export type InsertServiceType = z.infer<typeof insertServiceTypeSchema>;
export type ServiceType = typeof serviceTypes.$inferSelect;
export type InsertSite = z.infer<typeof insertSiteSchema>; export type InsertSite = z.infer<typeof insertSiteSchema>;
export type Site = typeof sites.$inferSelect; export type Site = typeof sites.$inferSelect;

View File

@ -1,13 +1,7 @@
{ {
"version": "1.0.9", "version": "1.0.8",
"lastUpdate": "2025-10-17T09:18:14.391Z", "lastUpdate": "2025-10-17T08:36:23.963Z",
"changelog": [ "changelog": [
{
"version": "1.0.9",
"date": "2025-10-17",
"type": "patch",
"description": "Deployment automatico v1.0.9"
},
{ {
"version": "1.0.8", "version": "1.0.8",
"date": "2025-10-17", "date": "2025-10-17",