/** * Car Rental Tools * Implements searchCars and bookCar MCP tools */ import { generateCarOptions, getCarById } from '../data/cars.js'; import { generatePNR } from '../data/pnr.js'; import { storage } from '../session/storage.js'; import { logger } from '../utils/logger.js'; import { ValidationError, BookingError } from '../utils/errors.js'; import { validateRequired, validateString, validateFutureDate } from '../validation/validators.js'; /** * Search for available car rentals * @param {Object} params - Search parameters * @param {string} sessionId - Session identifier * @returns {Promise} Search results */ export async function searchCars(params: any, sessionId: any) { try { // Validate required fields validateRequired(params.pickupLocation, 'pickupLocation'); validateRequired(params.dropoffLocation, 'dropoffLocation'); validateRequired(params.pickupDate, 'pickupDate'); validateRequired(params.dropoffDate, 'dropoffDate'); validateString(params.pickupLocation, 'pickupLocation', { minLength: 3, maxLength: 3 }); validateString(params.dropoffLocation, 'dropoffLocation', { minLength: 3, maxLength: 3 }); validateFutureDate(params.pickupDate, 'pickupDate'); validateFutureDate(params.dropoffDate, 'dropoffDate'); const pickupLocation = params.pickupLocation.toUpperCase(); const dropoffLocation = params.dropoffLocation.toUpperCase(); const pickupDate = new Date(params.pickupDate); const dropoffDate = new Date(params.dropoffDate); // Validate date range if (dropoffDate <= pickupDate) { throw new ValidationError('dropoffDate must be after pickupDate'); } const dropoffDateObj = new Date(dropoffDate); const days = Math.ceil((dropoffDateObj.getTime() - pickupDate.getTime()) / (1000 * 60 * 60 * 24)); if (days < 1) { throw new ValidationError('Minimum rental period is 1 day'); } // Generate car options const options = generateCarOptions( pickupLocation, dropoffLocation, params.pickupDate, params.dropoffDate ); logger.info({ sessionId, pickupLocation, dropoffLocation, days, resultCount: options.length }, 'Car search completed'); return { pickupLocation, dropoffLocation, pickupDate: params.pickupDate, dropoffDate: params.dropoffDate, days, results: options }; } catch (error) { logger.error({ error, sessionId }, 'Car search failed'); throw error; } } /** * Book a car rental * @param {Object} params - Booking parameters * @param {string} sessionId - Session identifier * @returns {Promise} Booking confirmation */ export async function bookCar(params: any, sessionId: any) { try { // Validate required fields validateRequired(params.carId, 'carId'); validateRequired(params.pickupLocation, 'pickupLocation'); validateRequired(params.dropoffLocation, 'dropoffLocation'); validateRequired(params.pickupDate, 'pickupDate'); validateRequired(params.dropoffDate, 'dropoffDate'); validateRequired(params.driverName, 'driverName'); validateRequired(params.driverEmail, 'driverEmail'); const { carId, pickupLocation, dropoffLocation, pickupDate, dropoffDate, driverName, driverEmail, driverPhone, driverLicense, pnr: existingPnr } = params; // Validate car exists const car = getCarById(carId); if (!car) { throw new ValidationError(`Car not found: ${carId}`); } // Validate dates validateFutureDate(pickupDate, 'pickupDate'); validateFutureDate(dropoffDate, 'dropoffDate'); const pickup = new Date(pickupDate); const dropoff = new Date(dropoffDate); if (dropoff <= pickup) { throw new ValidationError('dropoffDate must be after pickupDate'); } const pickupDateObj = new Date(params.pickupDate); const dropoffDateObj = new Date(params.dropoffDate); const days = Math.ceil((dropoffDateObj.getTime() - pickupDateObj.getTime()) / (1000 * 60 * 60 * 24)); // Calculate pricing (recalculate with same logic as search) const oneWayFee = pickupLocation !== dropoffLocation ? 75 : 0; const dailyRate = car.basePrice; // Simplified - real calculation in generateCarOptions const totalPrice = (dailyRate * days) + oneWayFee; // Generate or use existing PNR const pnr = existingPnr || generatePNR(); const client = storage.client; // Check if this is adding to existing booking let booking; if (existingPnr) { const existingKey = `gds:session:${sessionId}:booking:${existingPnr}`; const existingData = await client.get(existingKey); if (!existingData) { throw new BookingError(`Booking not found: ${existingPnr}`); } booking = JSON.parse(existingData); // Add car segment if (!booking.cars) { booking.cars = []; } booking.cars.push({ carId: car.carId, company: car.company, category: car.category, example: car.example, passengers: car.passengers, bags: car.bags, features: car.features, pickupLocation, dropoffLocation, pickupDate, dropoffDate, days, driverName, driverEmail, driverPhone, driverLicense, pricing: { dailyRate, days, oneWayFee, totalPrice, currency: 'USD' }, status: 'confirmed', bookedAt: new Date().toISOString() }); // Recalculate total price let totalBookingPrice = 0; if (booking.flights) { totalBookingPrice += booking.flights.reduce((sum, f) => sum + (f.pricing?.totalPrice || 0), 0); } if (booking.hotels) { totalBookingPrice += booking.hotels.reduce((sum, h) => sum + (h.pricing?.totalPrice || 0), 0); } if (booking.cars) { totalBookingPrice += booking.cars.reduce((sum, c) => sum + (c.pricing?.totalPrice || 0), 0); } booking.totalPrice = totalBookingPrice; booking.updatedAt = new Date().toISOString(); } else { // Create new car-only booking booking = { pnr, type: 'car', status: 'confirmed', cars: [{ carId: car.carId, company: car.company, category: car.category, example: car.example, passengers: car.passengers, bags: car.bags, features: car.features, pickupLocation, dropoffLocation, pickupDate, dropoffDate, days, driverName, driverEmail, driverPhone, driverLicense, pricing: { dailyRate, days, oneWayFee, totalPrice, currency: 'USD' }, status: 'confirmed', bookedAt: new Date().toISOString() }], totalPrice, currency: 'USD', createdAt: new Date().toISOString(), sessionId }; } // Store booking const bookingKey = `gds:session:${sessionId}:booking:${pnr}`; await client.set(bookingKey, JSON.stringify(booking), 'EX', 3600); // Update session bookings set await client.sadd(`gds:session:${sessionId}:bookings`, pnr); logger.info({ sessionId, pnr, carId, days }, 'Car rental booking created'); return { pnr, status: 'confirmed', car: { carId: car.carId, company: car.company, category: car.category, example: car.example, pickupLocation, dropoffLocation, pickupDate, dropoffDate, days }, pricing: { dailyRate, days, oneWayFee, totalPrice, currency: 'USD' }, driverName, driverEmail, message: existingPnr ? `Car rental added to existing booking ${pnr}` : `Car rental confirmed with PNR ${pnr}` }; } catch (error) { logger.error({ error, sessionId }, 'Car booking failed'); throw error; } } export default { searchCars, bookCar };