Introduce new fields for contract reference, start/end dates, and service times in the `sites` schema and UI for managing site contracts. 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/UBH5igx
839 lines
28 KiB
TypeScript
839 lines
28 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { relations } from "drizzle-orm";
|
|
import {
|
|
pgTable,
|
|
varchar,
|
|
text,
|
|
timestamp,
|
|
index,
|
|
uniqueIndex,
|
|
jsonb,
|
|
boolean,
|
|
integer,
|
|
date,
|
|
pgEnum,
|
|
} from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
// ============= ENUMS =============
|
|
|
|
export const userRoleEnum = pgEnum("user_role", [
|
|
"admin",
|
|
"coordinator",
|
|
"guard",
|
|
"client",
|
|
]);
|
|
|
|
export const shiftTypeEnum = pgEnum("shift_type", [
|
|
"fixed_post", // Presidio fisso
|
|
"patrol", // Pattugliamento/ronde
|
|
"night_inspection", // Ispettorato notturno
|
|
"quick_response", // Pronto intervento
|
|
]);
|
|
|
|
export const shiftStatusEnum = pgEnum("shift_status", [
|
|
"planned",
|
|
"active",
|
|
"completed",
|
|
"cancelled",
|
|
]);
|
|
|
|
export const certificationStatusEnum = pgEnum("certification_status", [
|
|
"valid",
|
|
"expiring_soon", // < 30 days
|
|
"expired",
|
|
]);
|
|
|
|
export const shiftPreferenceEnum = pgEnum("shift_preference", [
|
|
"morning", // 06:00-14:00
|
|
"afternoon", // 14:00-22:00
|
|
"night", // 22:00-06:00
|
|
"any",
|
|
]);
|
|
|
|
export const absenceTypeEnum = pgEnum("absence_type", [
|
|
"sick_leave", // Malattia
|
|
"vacation", // Ferie
|
|
"personal_leave", // Permesso
|
|
"injury", // Infortunio
|
|
]);
|
|
|
|
export const trainingStatusEnum = pgEnum("training_status", [
|
|
"scheduled",
|
|
"completed",
|
|
"expired",
|
|
"cancelled",
|
|
]);
|
|
|
|
export const sitePreferenceTypeEnum = pgEnum("site_preference_type", [
|
|
"preferred", // Continuità - operatore preferito per questo sito
|
|
"blacklisted", // Non assegnare mai questo operatore a questo sito
|
|
]);
|
|
|
|
export const vehicleStatusEnum = pgEnum("vehicle_status", [
|
|
"available", // Disponibile
|
|
"in_use", // In uso
|
|
"maintenance", // In manutenzione
|
|
"out_of_service", // Fuori servizio
|
|
]);
|
|
|
|
export const vehicleTypeEnum = pgEnum("vehicle_type", [
|
|
"car", // Auto
|
|
"van", // Furgone
|
|
"motorcycle", // Moto
|
|
"suv", // SUV
|
|
]);
|
|
|
|
export const locationEnum = pgEnum("location", [
|
|
"roccapiemonte", // Sede Roccapiemonte (Salerno)
|
|
"milano", // Sede Milano
|
|
"roma", // Sede Roma
|
|
]);
|
|
|
|
// ============= SESSION & AUTH TABLES (Replit Auth) =============
|
|
|
|
// Session storage table - mandatory for Replit Auth
|
|
export const sessions = pgTable(
|
|
"sessions",
|
|
{
|
|
sid: varchar("sid").primaryKey(),
|
|
sess: jsonb("sess").notNull(),
|
|
expire: timestamp("expire").notNull(),
|
|
},
|
|
(table) => [index("IDX_session_expire").on(table.expire)]
|
|
);
|
|
|
|
// User storage table - mandatory for Replit Auth
|
|
export const users = pgTable("users", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
email: varchar("email").unique(),
|
|
firstName: varchar("first_name"),
|
|
lastName: varchar("last_name"),
|
|
profileImageUrl: varchar("profile_image_url"),
|
|
passwordHash: varchar("password_hash"), // For local auth - bcrypt hash
|
|
role: userRoleEnum("role").notNull().default("guard"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= GUARDS & CERTIFICATIONS =============
|
|
|
|
export const guards = pgTable("guards", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
badgeNumber: varchar("badge_number").notNull().unique(),
|
|
phoneNumber: varchar("phone_number"),
|
|
location: locationEnum("location").notNull().default("roccapiemonte"), // Sede di appartenenza
|
|
|
|
// Skills
|
|
isArmed: boolean("is_armed").default(false),
|
|
hasFireSafety: boolean("has_fire_safety").default(false),
|
|
hasFirstAid: boolean("has_first_aid").default(false),
|
|
hasDriverLicense: boolean("has_driver_license").default(false),
|
|
languages: text("languages").array(),
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
export const certifications = pgTable("certifications", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
type: varchar("type").notNull(), // porto_armi, medical_exam, training_course
|
|
name: varchar("name").notNull(),
|
|
issueDate: date("issue_date").notNull(),
|
|
expiryDate: date("expiry_date").notNull(),
|
|
status: certificationStatusEnum("status").notNull().default("valid"),
|
|
documentUrl: varchar("document_url"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// ============= VEHICLES (PARCO AUTOMEZZI) =============
|
|
|
|
export const vehicles = pgTable("vehicles", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
licensePlate: varchar("license_plate").notNull().unique(), // Targa
|
|
brand: varchar("brand").notNull(), // Marca (es: Fiat, Volkswagen)
|
|
model: varchar("model").notNull(), // Modello
|
|
vehicleType: vehicleTypeEnum("vehicle_type").notNull(),
|
|
year: integer("year"), // Anno immatricolazione
|
|
location: locationEnum("location").notNull().default("roccapiemonte"), // Sede di appartenenza
|
|
|
|
// Assegnazione
|
|
assignedGuardId: varchar("assigned_guard_id").references(() => guards.id, { onDelete: "set null" }),
|
|
|
|
// Stato e manutenzione
|
|
status: vehicleStatusEnum("status").notNull().default("available"),
|
|
lastMaintenanceDate: date("last_maintenance_date"),
|
|
nextMaintenanceDate: date("next_maintenance_date"),
|
|
mileage: integer("mileage"), // Chilometraggio
|
|
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_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
|
|
|
|
// Parametri specifici per tipo servizio
|
|
fixedPostHours: integer("fixed_post_hours"), // Ore presidio fisso (es. 8, 12)
|
|
patrolPassages: integer("patrol_passages"), // Numero passaggi pattugliamento (es. 3, 5)
|
|
inspectionFrequency: integer("inspection_frequency"), // Frequenza ispezioni in minuti
|
|
responseTimeMinutes: integer("response_time_minutes"), // Tempo risposta pronto intervento
|
|
|
|
isActive: boolean("is_active").default(true),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= SITES & CONTRACTS =============
|
|
|
|
export const sites = pgTable("sites", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: varchar("name").notNull(),
|
|
address: varchar("address").notNull(),
|
|
clientId: varchar("client_id").references(() => users.id),
|
|
location: locationEnum("location").notNull().default("roccapiemonte"), // Sede gestionale
|
|
|
|
// Service requirements
|
|
shiftType: shiftTypeEnum("shift_type").notNull(),
|
|
minGuards: integer("min_guards").notNull().default(1),
|
|
requiresArmed: boolean("requires_armed").default(false),
|
|
requiresDriverLicense: boolean("requires_driver_license").default(false),
|
|
|
|
// Orari servizio (formato HH:MM, es. "08:00", "20:00")
|
|
serviceStartTime: varchar("service_start_time"), // Orario inizio servizio
|
|
serviceEndTime: varchar("service_end_time"), // Orario fine servizio
|
|
|
|
// Dati contrattuali
|
|
contractReference: varchar("contract_reference"), // Riferimento/numero contratto
|
|
contractStartDate: date("contract_start_date"), // Data inizio contratto
|
|
contractEndDate: date("contract_end_date"), // Data fine contratto
|
|
|
|
// Coordinates for geofencing (future use)
|
|
latitude: varchar("latitude"),
|
|
longitude: varchar("longitude"),
|
|
|
|
isActive: boolean("is_active").default(true),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= SHIFTS & ASSIGNMENTS =============
|
|
|
|
export const shifts = pgTable("shifts", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
siteId: varchar("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
|
|
startTime: timestamp("start_time").notNull(),
|
|
endTime: timestamp("end_time").notNull(),
|
|
status: shiftStatusEnum("status").notNull().default("planned"),
|
|
|
|
// Veicolo assegnato al turno (opzionale)
|
|
vehicleId: varchar("vehicle_id").references(() => vehicles.id, { onDelete: "set null" }),
|
|
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
export const shiftAssignments = pgTable("shift_assignments", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
shiftId: varchar("shift_id").notNull().references(() => shifts.id, { onDelete: "cascade" }),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
|
|
assignedAt: timestamp("assigned_at").defaultNow(),
|
|
confirmedAt: timestamp("confirmed_at"),
|
|
|
|
// Actual check-in/out times
|
|
checkInTime: timestamp("check_in_time"),
|
|
checkOutTime: timestamp("check_out_time"),
|
|
});
|
|
|
|
// ============= CCNL SETTINGS =============
|
|
|
|
export const ccnlSettings = pgTable("ccnl_settings", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
key: varchar("key").notNull().unique(),
|
|
value: varchar("value").notNull(),
|
|
description: text("description"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= NOTIFICATIONS =============
|
|
|
|
export const notifications = pgTable("notifications", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
|
|
title: varchar("title").notNull(),
|
|
message: text("message").notNull(),
|
|
type: varchar("type").notNull(), // shift_assigned, certification_expiring, shift_reminder
|
|
|
|
isRead: boolean("is_read").default(false),
|
|
relatedEntityId: varchar("related_entity_id"), // shift_id, certification_id, etc.
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// ============= GUARD CONSTRAINTS & PREFERENCES =============
|
|
|
|
export const guardConstraints = pgTable("guard_constraints", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }).unique(),
|
|
|
|
// Preferenze turni
|
|
preferredShiftType: shiftPreferenceEnum("preferred_shift_type").default("any"),
|
|
|
|
// Limiti contrattuali personalizzati (se diversi dal CCNL base)
|
|
maxHoursPerDay: integer("max_hours_per_day").default(10), // 8 + 2 straordinario
|
|
maxHoursPerWeek: integer("max_hours_per_week").default(48), // 40 + 8 straordinario
|
|
|
|
// Giorni riposo preferiti (1=Lunedì...7=Domenica)
|
|
preferredDaysOff: integer("preferred_days_off").array(),
|
|
|
|
// Disponibilità festività
|
|
availableOnHolidays: boolean("available_on_holidays").default(true),
|
|
|
|
// Note aggiuntive
|
|
notes: text("notes"),
|
|
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= SITE PREFERENCES =============
|
|
|
|
export const sitePreferences = pgTable("site_preferences", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
siteId: varchar("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
|
|
// Preferenza: preferred = continuità, blacklisted = non assegnare mai
|
|
preference: sitePreferenceTypeEnum("preference").notNull(),
|
|
priority: integer("priority").default(0), // Più alto = più preferito
|
|
|
|
reason: text("reason"), // Motivazione preferenza/blacklist
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
}, (table) => ({
|
|
// Unique constraint: una sola preferenza per coppia sito-guardia
|
|
uniqueSiteGuard: uniqueIndex("unique_site_guard_preference").on(table.siteId, table.guardId),
|
|
}));
|
|
|
|
// ============= CONTRACT PARAMETERS (CCNL) =============
|
|
|
|
export const contractParameters = pgTable("contract_parameters", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
|
|
// Identificatore tipo contratto (default = CCNL Vigilanza Privata)
|
|
contractType: varchar("contract_type").notNull().unique().default("CCNL_VIGILANZA_2023"),
|
|
|
|
// Limiti orari
|
|
maxHoursPerDay: integer("max_hours_per_day").notNull().default(8),
|
|
maxOvertimePerDay: integer("max_overtime_per_day").notNull().default(2),
|
|
maxHoursPerWeek: integer("max_hours_per_week").notNull().default(40),
|
|
maxOvertimePerWeek: integer("max_overtime_per_week").notNull().default(8),
|
|
|
|
// Riposi obbligatori
|
|
minDailyRestHours: integer("min_daily_rest_hours").notNull().default(11),
|
|
minDailyRestHoursReduced: integer("min_daily_rest_hours_reduced").notNull().default(9), // Deroga CCNL
|
|
maxDailyRestReductionsPerMonth: integer("max_daily_rest_reductions_per_month").notNull().default(3),
|
|
maxDailyRestReductionsPerYear: integer("max_daily_rest_reductions_per_year").notNull().default(12),
|
|
minWeeklyRestHours: integer("min_weekly_rest_hours").notNull().default(24),
|
|
|
|
// Pause obbligatorie
|
|
pauseMinutesIfOver6Hours: integer("pause_minutes_if_over_6_hours").notNull().default(10),
|
|
|
|
// Buoni pasto
|
|
mealVoucherEnabled: boolean("meal_voucher_enabled").default(true), // Buoni pasto attivi
|
|
mealVoucherAfterHours: integer("meal_voucher_after_hours").default(6), // Ore minime per diritto buono pasto
|
|
mealVoucherAmount: integer("meal_voucher_amount").default(8), // Importo buono pasto in euro (facoltativo)
|
|
|
|
// Limiti notturni (22:00-06:00)
|
|
maxNightHoursPerWeek: integer("max_night_hours_per_week").default(48),
|
|
|
|
// Maggiorazioni (percentuali)
|
|
holidayPayIncrease: integer("holiday_pay_increase").default(30), // +30% festivi
|
|
nightPayIncrease: integer("night_pay_increase").default(20), // +20% notturni
|
|
overtimePayIncrease: integer("overtime_pay_increase").default(15), // +15% straordinari
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= TRAINING & COURSES =============
|
|
|
|
export const trainingCourses = pgTable("training_courses", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
|
|
courseName: varchar("course_name").notNull(),
|
|
courseType: varchar("course_type").notNull(), // "mandatory" | "optional"
|
|
|
|
scheduledDate: date("scheduled_date"),
|
|
completionDate: date("completion_date"),
|
|
expiryDate: date("expiry_date"), // Per corsi con scadenza (es. primo soccorso)
|
|
|
|
status: trainingStatusEnum("status").notNull().default("scheduled"),
|
|
certificateUrl: varchar("certificate_url"),
|
|
|
|
provider: varchar("provider"), // Ente formatore
|
|
hours: integer("hours"), // Ore corso
|
|
notes: text("notes"),
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// ============= HOLIDAYS =============
|
|
|
|
export const holidays = pgTable("holidays", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
|
|
name: varchar("name").notNull(), // "Natale", "Capodanno", "1 Maggio", etc.
|
|
date: date("date").notNull(),
|
|
year: integer("year").notNull(), // Anno specifico per tracking rotazioni
|
|
isNational: boolean("is_national").default(true), // Nazionale o regionale
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
}, (table) => ({
|
|
// Unique constraint: una festività per data e anno
|
|
uniqueDateYear: uniqueIndex("unique_holiday_date_year").on(table.date, table.year),
|
|
}));
|
|
|
|
// Join table per tracking rotazioni festività con integrità referenziale
|
|
export const holidayAssignments = pgTable("holiday_assignments", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
holidayId: varchar("holiday_id").notNull().references(() => holidays.id, { onDelete: "cascade" }),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
shiftId: varchar("shift_id").references(() => shifts.id, { onDelete: "set null" }), // Turno specifico lavorato
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
}, (table) => ({
|
|
// Unique constraint: una guardia non può essere assegnata due volte alla stessa festività
|
|
uniqueHolidayGuard: uniqueIndex("unique_holiday_guard").on(table.holidayId, table.guardId),
|
|
}));
|
|
|
|
// ============= ABSENCES & SUBSTITUTIONS =============
|
|
|
|
export const absences = pgTable("absences", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
|
|
type: absenceTypeEnum("type").notNull(),
|
|
startDate: date("start_date").notNull(),
|
|
endDate: date("end_date").notNull(),
|
|
|
|
// Documentazione
|
|
certificateUrl: varchar("certificate_url"), // Certificato medico, etc.
|
|
notes: text("notes"),
|
|
|
|
// Se approvata e serve sostituto
|
|
isApproved: boolean("is_approved").default(false),
|
|
needsSubstitute: boolean("needs_substitute").default(true),
|
|
substituteGuardId: varchar("substitute_guard_id").references(() => guards.id),
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
approvedAt: timestamp("approved_at"),
|
|
approvedBy: varchar("approved_by").references(() => users.id),
|
|
});
|
|
|
|
// Join table per tracking turni impattati da assenze (per sistema sostituzione)
|
|
export const absenceAffectedShifts = pgTable("absence_affected_shifts", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
absenceId: varchar("absence_id").notNull().references(() => absences.id, { onDelete: "cascade" }),
|
|
shiftId: varchar("shift_id").notNull().references(() => shifts.id, { onDelete: "cascade" }),
|
|
|
|
// Se sostituto trovato
|
|
isSubstituted: boolean("is_substituted").default(false),
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
}, (table) => ({
|
|
// Unique: uno shift non può essere impattato due volte dalla stessa assenza
|
|
uniqueAbsenceShift: uniqueIndex("unique_absence_shift").on(table.absenceId, table.shiftId),
|
|
}));
|
|
|
|
// ============= RELATIONS =============
|
|
|
|
export const usersRelations = relations(users, ({ one, many }) => ({
|
|
guard: one(guards, {
|
|
fields: [users.id],
|
|
references: [guards.userId],
|
|
}),
|
|
managedSites: many(sites),
|
|
notifications: many(notifications),
|
|
}));
|
|
|
|
export const guardsRelations = relations(guards, ({ one, many }) => ({
|
|
user: one(users, {
|
|
fields: [guards.userId],
|
|
references: [users.id],
|
|
}),
|
|
certifications: many(certifications),
|
|
shiftAssignments: many(shiftAssignments),
|
|
constraints: one(guardConstraints),
|
|
sitePreferences: many(sitePreferences),
|
|
trainingCourses: many(trainingCourses),
|
|
absences: many(absences),
|
|
assignedVehicles: many(vehicles),
|
|
}));
|
|
|
|
export const vehiclesRelations = relations(vehicles, ({ one }) => ({
|
|
assignedGuard: one(guards, {
|
|
fields: [vehicles.assignedGuardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const certificationsRelations = relations(certifications, ({ one }) => ({
|
|
guard: one(guards, {
|
|
fields: [certifications.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const sitesRelations = relations(sites, ({ one, many }) => ({
|
|
client: one(users, {
|
|
fields: [sites.clientId],
|
|
references: [users.id],
|
|
}),
|
|
shifts: many(shifts),
|
|
preferences: many(sitePreferences),
|
|
}));
|
|
|
|
export const shiftsRelations = relations(shifts, ({ one, many }) => ({
|
|
site: one(sites, {
|
|
fields: [shifts.siteId],
|
|
references: [sites.id],
|
|
}),
|
|
vehicle: one(vehicles, {
|
|
fields: [shifts.vehicleId],
|
|
references: [vehicles.id],
|
|
}),
|
|
assignments: many(shiftAssignments),
|
|
}));
|
|
|
|
export const shiftAssignmentsRelations = relations(shiftAssignments, ({ one }) => ({
|
|
shift: one(shifts, {
|
|
fields: [shiftAssignments.shiftId],
|
|
references: [shifts.id],
|
|
}),
|
|
guard: one(guards, {
|
|
fields: [shiftAssignments.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|
user: one(users, {
|
|
fields: [notifications.userId],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const guardConstraintsRelations = relations(guardConstraints, ({ one }) => ({
|
|
guard: one(guards, {
|
|
fields: [guardConstraints.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const sitePreferencesRelations = relations(sitePreferences, ({ one }) => ({
|
|
site: one(sites, {
|
|
fields: [sitePreferences.siteId],
|
|
references: [sites.id],
|
|
}),
|
|
guard: one(guards, {
|
|
fields: [sitePreferences.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const trainingCoursesRelations = relations(trainingCourses, ({ one }) => ({
|
|
guard: one(guards, {
|
|
fields: [trainingCourses.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const absencesRelations = relations(absences, ({ one, many }) => ({
|
|
guard: one(guards, {
|
|
fields: [absences.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
substituteGuard: one(guards, {
|
|
fields: [absences.substituteGuardId],
|
|
references: [guards.id],
|
|
}),
|
|
approver: one(users, {
|
|
fields: [absences.approvedBy],
|
|
references: [users.id],
|
|
}),
|
|
affectedShifts: many(absenceAffectedShifts),
|
|
}));
|
|
|
|
export const absenceAffectedShiftsRelations = relations(absenceAffectedShifts, ({ one }) => ({
|
|
absence: one(absences, {
|
|
fields: [absenceAffectedShifts.absenceId],
|
|
references: [absences.id],
|
|
}),
|
|
shift: one(shifts, {
|
|
fields: [absenceAffectedShifts.shiftId],
|
|
references: [shifts.id],
|
|
}),
|
|
}));
|
|
|
|
export const holidaysRelations = relations(holidays, ({ many }) => ({
|
|
assignments: many(holidayAssignments),
|
|
}));
|
|
|
|
export const holidayAssignmentsRelations = relations(holidayAssignments, ({ one }) => ({
|
|
holiday: one(holidays, {
|
|
fields: [holidayAssignments.holidayId],
|
|
references: [holidays.id],
|
|
}),
|
|
guard: one(guards, {
|
|
fields: [holidayAssignments.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
shift: one(shifts, {
|
|
fields: [holidayAssignments.shiftId],
|
|
references: [shifts.id],
|
|
}),
|
|
}));
|
|
|
|
// ============= INSERT SCHEMAS =============
|
|
|
|
export const insertUserSchema = createInsertSchema(users).pick({
|
|
email: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
profileImageUrl: true,
|
|
passwordHash: true,
|
|
role: true,
|
|
});
|
|
|
|
// Form schema with plain password (will be hashed on backend)
|
|
export const insertUserFormSchema = z.object({
|
|
email: z.string().email("Email non valida"),
|
|
firstName: z.string().min(1, "Nome obbligatorio"),
|
|
lastName: z.string().min(1, "Cognome obbligatorio"),
|
|
password: z.string().min(6, "Password minimo 6 caratteri"),
|
|
role: z.enum(["admin", "coordinator", "guard", "client"]).default("guard"),
|
|
});
|
|
|
|
// Update user form schema (password optional)
|
|
export const updateUserFormSchema = z.object({
|
|
email: z.string().email("Email non valida").optional(),
|
|
firstName: z.string().min(1, "Nome obbligatorio").optional(),
|
|
lastName: z.string().min(1, "Cognome obbligatorio").optional(),
|
|
password: z.string().min(6, "Password minimo 6 caratteri").optional(),
|
|
role: z.enum(["admin", "coordinator", "guard", "client"]).optional(),
|
|
});
|
|
|
|
export const insertGuardSchema = createInsertSchema(guards).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertCertificationSchema = createInsertSchema(certifications).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
status: true,
|
|
});
|
|
|
|
export const insertVehicleSchema = createInsertSchema(vehicles).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertServiceTypeSchema = createInsertSchema(serviceTypes).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertSiteSchema = createInsertSchema(sites).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertShiftSchema = createInsertSchema(shifts).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
// Form schema that accepts datetime strings and transforms to Date
|
|
export const insertShiftFormSchema = z.object({
|
|
siteId: z.string().min(1, "Sito obbligatorio"),
|
|
startTime: z.string().min(1, "Data inizio obbligatoria").refine((val) => !isNaN(new Date(val).getTime()), {
|
|
message: "Data inizio non valida",
|
|
}).transform((val) => new Date(val)),
|
|
endTime: z.string().min(1, "Data fine obbligatoria").refine((val) => !isNaN(new Date(val).getTime()), {
|
|
message: "Data fine non valida",
|
|
}).transform((val) => new Date(val)),
|
|
status: z.enum(["planned", "active", "completed", "cancelled"]).default("planned"),
|
|
});
|
|
|
|
export const insertShiftAssignmentSchema = createInsertSchema(shiftAssignments).omit({
|
|
id: true,
|
|
assignedAt: true,
|
|
});
|
|
|
|
export const insertCcnlSettingSchema = createInsertSchema(ccnlSettings).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertNotificationSchema = createInsertSchema(notifications).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertGuardConstraintsSchema = createInsertSchema(guardConstraints).omit({
|
|
id: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertSitePreferenceSchema = createInsertSchema(sitePreferences).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertContractParametersSchema = createInsertSchema(contractParameters).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertTrainingCourseSchema = createInsertSchema(trainingCourses).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertHolidaySchema = createInsertSchema(holidays).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertAbsenceSchema = createInsertSchema(absences).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
approvedAt: true,
|
|
});
|
|
|
|
export const insertHolidayAssignmentSchema = createInsertSchema(holidayAssignments).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertAbsenceAffectedShiftSchema = createInsertSchema(absenceAffectedShifts).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
// ============= TYPES =============
|
|
|
|
export type UpsertUser = typeof users.$inferInsert;
|
|
export type User = typeof users.$inferSelect;
|
|
|
|
export type InsertGuard = z.infer<typeof insertGuardSchema>;
|
|
export type Guard = typeof guards.$inferSelect;
|
|
|
|
export type InsertCertification = z.infer<typeof insertCertificationSchema>;
|
|
export type Certification = typeof certifications.$inferSelect;
|
|
|
|
export type InsertVehicle = z.infer<typeof insertVehicleSchema>;
|
|
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 Site = typeof sites.$inferSelect;
|
|
|
|
export type InsertShift = z.infer<typeof insertShiftSchema>;
|
|
export type Shift = typeof shifts.$inferSelect;
|
|
|
|
export type InsertShiftAssignment = z.infer<typeof insertShiftAssignmentSchema>;
|
|
export type ShiftAssignment = typeof shiftAssignments.$inferSelect;
|
|
|
|
export type InsertCcnlSetting = z.infer<typeof insertCcnlSettingSchema>;
|
|
export type CcnlSetting = typeof ccnlSettings.$inferSelect;
|
|
|
|
export type InsertNotification = z.infer<typeof insertNotificationSchema>;
|
|
export type Notification = typeof notifications.$inferSelect;
|
|
|
|
export type InsertGuardConstraints = z.infer<typeof insertGuardConstraintsSchema>;
|
|
export type GuardConstraints = typeof guardConstraints.$inferSelect;
|
|
|
|
export type InsertSitePreference = z.infer<typeof insertSitePreferenceSchema>;
|
|
export type SitePreference = typeof sitePreferences.$inferSelect;
|
|
|
|
export type InsertContractParameters = z.infer<typeof insertContractParametersSchema>;
|
|
export type ContractParameters = typeof contractParameters.$inferSelect;
|
|
|
|
export type InsertTrainingCourse = z.infer<typeof insertTrainingCourseSchema>;
|
|
export type TrainingCourse = typeof trainingCourses.$inferSelect;
|
|
|
|
export type InsertHoliday = z.infer<typeof insertHolidaySchema>;
|
|
export type Holiday = typeof holidays.$inferSelect;
|
|
|
|
export type InsertAbsence = z.infer<typeof insertAbsenceSchema>;
|
|
export type Absence = typeof absences.$inferSelect;
|
|
|
|
export type InsertHolidayAssignment = z.infer<typeof insertHolidayAssignmentSchema>;
|
|
export type HolidayAssignment = typeof holidayAssignments.$inferSelect;
|
|
|
|
export type InsertAbsenceAffectedShift = z.infer<typeof insertAbsenceAffectedShiftSchema>;
|
|
export type AbsenceAffectedShift = typeof absenceAffectedShifts.$inferSelect;
|
|
|
|
// ============= EXTENDED TYPES FOR FRONTEND =============
|
|
|
|
export type GuardWithCertifications = Guard & {
|
|
certifications: Certification[];
|
|
user?: User;
|
|
};
|
|
|
|
export type ShiftWithDetails = Shift & {
|
|
site: Site;
|
|
assignments: (ShiftAssignment & {
|
|
guard: GuardWithCertifications;
|
|
})[];
|
|
};
|
|
|
|
export type SiteWithShifts = Site & {
|
|
shifts: Shift[];
|
|
client?: User;
|
|
};
|
|
|
|
export type GuardWithDetails = Guard & {
|
|
user?: User;
|
|
certifications: Certification[];
|
|
constraints?: GuardConstraints;
|
|
trainingCourses: TrainingCourse[];
|
|
absences: Absence[];
|
|
};
|
|
|
|
export type AbsenceWithDetails = Absence & {
|
|
guard: Guard;
|
|
substituteGuard?: Guard;
|
|
approver?: User;
|
|
affectedShifts: (AbsenceAffectedShift & {
|
|
shift: Shift;
|
|
})[];
|
|
};
|