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), // Anagrafica firstName: varchar("first_name").notNull(), lastName: varchar("last_name").notNull(), email: varchar("email"), 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 serviceTypeId: varchar("service_type_id").references(() => serviceTypes.id), shiftType: shiftTypeEnum("shift_type"), // Optional - can be derived from service type 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" }), // Planned shift times (when the guard is scheduled to work) plannedStartTime: timestamp("planned_start_time").notNull(), plannedEndTime: timestamp("planned_end_time").notNull(), assignedAt: timestamp("assigned_at").defaultNow(), confirmedAt: timestamp("confirmed_at"), // Actual check-in/out times (recorded when guard clocks in/out) 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, }).extend({ firstName: z.string().min(1, "Nome obbligatorio"), lastName: z.string().min(1, "Cognome obbligatorio"), email: z.string().email("Email non valida").optional().or(z.literal("")), badgeNumber: z.string().min(1, "Matricola obbligatoria"), phoneNumber: z.string().optional().or(z.literal("")), location: z.enum(["roccapiemonte", "milano", "roma"]), }); 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, }) .extend({ plannedStartTime: z.union([z.string(), z.date()], { required_error: "Orario inizio richiesto", invalid_type_error: "Orario inizio non valido", }).transform((val) => new Date(val)), plannedEndTime: z.union([z.string(), z.date()], { required_error: "Orario fine richiesto", invalid_type_error: "Orario fine non valido", }).transform((val) => new Date(val)), }) .refine((data) => data.plannedEndTime > data.plannedStartTime, { message: "L'orario di fine deve essere successivo all'orario di inizio", path: ["plannedEndTime"], }); 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; export type Guard = typeof guards.$inferSelect; export type InsertCertification = z.infer; export type Certification = typeof certifications.$inferSelect; export type InsertVehicle = z.infer; export type Vehicle = typeof vehicles.$inferSelect; export type InsertServiceType = z.infer; export type ServiceType = typeof serviceTypes.$inferSelect; export type InsertSite = z.infer; export type Site = typeof sites.$inferSelect; export type InsertShift = z.infer; export type Shift = typeof shifts.$inferSelect; export type InsertShiftAssignment = z.infer; export type ShiftAssignment = typeof shiftAssignments.$inferSelect; export type InsertCcnlSetting = z.infer; export type CcnlSetting = typeof ccnlSettings.$inferSelect; export type InsertNotification = z.infer; export type Notification = typeof notifications.$inferSelect; export type InsertGuardConstraints = z.infer; export type GuardConstraints = typeof guardConstraints.$inferSelect; export type InsertSitePreference = z.infer; export type SitePreference = typeof sitePreferences.$inferSelect; export type InsertContractParameters = z.infer; export type ContractParameters = typeof contractParameters.$inferSelect; export type InsertTrainingCourse = z.infer; export type TrainingCourse = typeof trainingCourses.$inferSelect; export type InsertHoliday = z.infer; export type Holiday = typeof holidays.$inferSelect; export type InsertAbsence = z.infer; export type Absence = typeof absences.$inferSelect; export type InsertHolidayAssignment = z.infer; export type HolidayAssignment = typeof holidayAssignments.$inferSelect; export type InsertAbsenceAffectedShift = z.infer; 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; })[]; }; // ============= DTOs FOR GENERAL PLANNING ============= // DTO per conflitto orario guardia export const guardConflictSchema = z.object({ from: z.date(), to: z.date(), siteName: z.string(), shiftId: z.string(), }); // DTO per disponibilità guardia con controllo conflitti orari export const guardAvailabilitySchema = z.object({ guardId: z.string(), guardName: z.string(), badgeNumber: z.string(), weeklyHoursRemaining: z.number(), weeklyHoursAssigned: z.number(), weeklyHoursMax: z.number(), isAvailable: z.boolean(), conflicts: z.array(guardConflictSchema), unavailabilityReasons: z.array(z.string()), }); export type GuardConflict = z.infer; export type GuardAvailability = z.infer; // DTO per creazione turno multi-giorno dal Planning Generale export const createMultiDayShiftSchema = z.object({ siteId: z.string(), startDate: z.string(), // YYYY-MM-DD days: z.number().min(1).max(7), guardId: z.string(), shiftType: z.enum(["fixed_post", "patrol", "night_inspection", "quick_response"]).optional(), }); export type CreateMultiDayShiftRequest = z.infer;