const mongoose = require('mongoose'); const Schema = mongoose.Schema; // Schema per le coordinate const CoordinatesSchema = new Schema({ lat: { type: Number, required: true }, lng: { type: Number, required: true } }, { _id: false }); // Schema per località const LocationSchema = new Schema({ city: { type: String, required: true, trim: true }, address: { type: String, trim: true }, province: { type: String, trim: true }, coordinates: { type: CoordinatesSchema, required: true } }, { _id: false }); const RideRequestSchema = new Schema({ idapp: { type: String, required: true, index: true }, rideId: { type: Schema.Types.ObjectId, ref: 'Ride', required: true, index: true }, passengerId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true }, driverId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true }, message: { type: String, trim: true, maxlength: 500 }, pickupPoint: { type: LocationSchema }, dropoffPoint: { type: LocationSchema }, useOriginalRoute: { type: Boolean, default: true // true = usa partenza/destinazione originali del ride }, seatsRequested: { type: Number, required: true, min: 1, default: 1 }, hasLuggage: { type: Boolean, default: false }, luggageSize: { type: String, enum: ['small', 'medium', 'large'], default: 'small' }, hasPackages: { type: Boolean, default: false }, packageDescription: { type: String, trim: true, maxlength: 200 }, hasPets: { type: Boolean, default: false }, petType: { type: String, trim: true }, petSize: { type: String, enum: ['small', 'medium', 'large'] }, specialNeeds: { type: String, trim: true, maxlength: 300 }, status: { type: String, enum: ['pending', 'accepted', 'rejected', 'cancelled', 'expired', 'completed'], default: 'pending', index: true }, responseMessage: { type: String, trim: true, maxlength: 500 }, respondedAt: { type: Date }, contribution: { agreed: { type: Boolean, default: false }, contribTypeId: { type: Schema.Types.ObjectId, ref: 'Contribtype' }, amount: { type: Number, min: 0 }, notes: { type: String, trim: true } }, cancelledBy: { type: String, enum: ['passenger', 'driver'] }, cancellationReason: { type: String, trim: true }, cancelledAt: { type: Date }, completedAt: { type: Date }, feedbackGiven: { type: Boolean, default: false } }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }); // Indici composti per ricerche ottimizzate RideRequestSchema.index({ rideId: 1, status: 1 }); RideRequestSchema.index({ passengerId: 1, status: 1 }); RideRequestSchema.index({ driverId: 1, status: 1 }); RideRequestSchema.index({ idapp: 1, createdAt: -1 }); // Virtual per verificare se la richiesta può essere cancellata RideRequestSchema.virtual('canCancel').get(function() { return ['pending', 'accepted'].includes(this.status); }); // Virtual per verificare se è in attesa RideRequestSchema.virtual('isPending').get(function() { return this.status === 'pending'; }); // Metodo per accettare la richiesta RideRequestSchema.methods.accept = async function(responseMessage = '') { this.status = 'accepted'; this.responseMessage = responseMessage; this.respondedAt = new Date(); // Aggiorna il ride con il passeggero confermato const Ride = mongoose.model('Ride'); const ride = await Ride.findById(this.rideId); if (ride) { ride.confirmedPassengers.push({ userId: this.passengerId, seats: this.seatsRequested, pickupPoint: this.pickupPoint || ride.departure, dropoffPoint: this.dropoffPoint || ride.destination, confirmedAt: new Date() }); await ride.updateAvailableSeats(); } return this.save(); }; // Metodo per rifiutare la richiesta RideRequestSchema.methods.reject = function(responseMessage = '') { this.status = 'rejected'; this.responseMessage = responseMessage; this.respondedAt = new Date(); return this.save(); }; // Metodo per cancellare la richiesta RideRequestSchema.methods.cancel = async function(cancelledBy, reason = '') { this.status = 'cancelled'; this.cancelledBy = cancelledBy; this.cancellationReason = reason; this.cancelledAt = new Date(); // Se era accettata, rimuovi il passeggero dal ride if (this.status === 'accepted') { const Ride = mongoose.model('Ride'); const ride = await Ride.findById(this.rideId); if (ride) { ride.confirmedPassengers = ride.confirmedPassengers.filter( p => p.userId.toString() !== this.passengerId.toString() ); await ride.updateAvailableSeats(); } } return this.save(); }; // Metodo statico per ottenere richieste pendenti di un conducente RideRequestSchema.statics.getPendingForDriver = function(idapp, driverId) { return this.find({ idapp, driverId, status: 'pending' }) .populate('passengerId', 'username name surname email') .populate('rideId', 'departure destination dateTime') .sort({ createdAt: -1 }); }; // Metodo statico per ottenere richieste di un passeggero RideRequestSchema.statics.getByPassenger = function(idapp, passengerId, status = null) { const query = { idapp, passengerId }; if (status) { query.status = status; } return this.find(query) .populate('rideId') .populate('driverId', 'username name surname') .sort({ createdAt: -1 }); }; // Pre-save hook per validazioni RideRequestSchema.pre('save', async function(next) { if (this.isNew) { // Verifica che il ride esista e abbia posti disponibili const Ride = mongoose.model('Ride'); const ride = await Ride.findById(this.rideId); if (!ride) { throw new Error('Viaggio non trovato'); } if (ride.type === 'offer' && ride.passengers.available < this.seatsRequested) { throw new Error('Posti non sufficienti per questo viaggio'); } if (ride.userId.toString() === this.passengerId.toString()) { throw new Error('Non puoi richiedere un passaggio per il tuo stesso viaggio'); } // Imposta il driverId dal ride this.driverId = ride.userId; } next(); }); const RideRequest = mongoose.model('RideRequest', RideRequestSchema); module.exports = RideRequest;