UNPKG

@ideal-photography/shared

Version:

Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.

427 lines (382 loc) 15.1 kB
import { models } from '../mongoDB/index.js'; import OrderService from './OrderService.js'; class BookingService { constructor() { this.orderService = OrderService; } /** * Create booking from order * @param {string} orderId - Order ID * @param {Object} bookingData - Booking data * @returns {Promise<Object>} Created booking */ async createBookingFromOrder(orderId, bookingData) { try { // Get order details const orderResult = await this.orderService.getOrderById(orderId, bookingData.userId); if (!orderResult.success) { throw new Error('Order not found'); } const order = orderResult.order; // Find service items in the order const serviceItems = order.items.filter(item => ['makeover', 'studio', 'service'].includes(item.productSnapshot.category) ); if (serviceItems.length === 0) { throw new Error('No service items found in order'); } const bookings = []; // Create booking for each service item for (const item of serviceItems) { const bookingNumber = models.Booking.generateBookingNumber(); const booking = await models.Booking.create({ bookingNumber, userId: bookingData.userId, orderId, serviceType: item.productSnapshot.category, serviceName: item.productSnapshot.name, preferredDate: bookingData.preferredDate, preferredTime: bookingData.preferredTime, duration: bookingData.duration || 1, location: bookingData.location, address: bookingData.address || {}, specialInstructions: bookingData.specialInstructions, requirements: bookingData.requirements || [] }); bookings.push(booking); } return { success: true, bookings: bookings.map(booking => ({ id: booking._id, bookingNumber: booking.bookingNumber, serviceType: booking.serviceType, serviceName: booking.serviceName, preferredDate: booking.preferredDate, preferredTime: booking.preferredTime, status: booking.status, createdAt: booking.createdAt })) }; } catch (error) { console.error('Error creating booking from order:', error); throw error; } } /** * Get booking by ID * @param {string} bookingId - Booking ID * @param {string} userId - User ID (for authorization) * @returns {Promise<Object>} Booking data */ async getBookingById(bookingId, userId) { try { const booking = await models.Booking.findOne({ _id: bookingId, userId }); if (!booking) { throw new Error('Booking not found'); } return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, serviceType: booking.serviceType, serviceName: booking.serviceName, preferredDate: booking.preferredDate, preferredTime: booking.preferredTime, confirmedDate: booking.confirmedDate, confirmedTime: booking.confirmedTime, duration: booking.duration, location: booking.location, address: booking.address, specialInstructions: booking.specialInstructions, requirements: booking.requirements, status: booking.status, notes: booking.notes, customerFeedback: booking.customerFeedback, staffNotes: booking.staffNotes, createdAt: booking.createdAt, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error getting booking:', error); throw error; } } /** * Get user's bookings * @param {string} userId - User ID * @param {Object} filters - Filter options * @returns {Promise<Object>} Bookings and pagination info */ async getUserBookings(userId, filters = {}) { try { const query = { userId }; if (filters.status) { query.status = filters.status; } if (filters.serviceType) { query.serviceType = filters.serviceType; } const [bookings, total] = await Promise.all([ models.Booking.find(query) .sort({ preferredDate: -1 }) .skip(filters.skip || 0) .limit(filters.limit || 20) .lean(), models.Booking.countDocuments(query) ]); return { success: true, bookings, pagination: { total, page: Math.floor((filters.skip || 0) / (filters.limit || 20)) + 1, limit: filters.limit || 20, pages: Math.ceil(total / (filters.limit || 20)) } }; } catch (error) { console.error('Error getting user bookings:', error); throw error; } } /** * Confirm booking * @param {string} bookingId - Booking ID * @param {string} confirmedBy - User ID who confirmed * @param {Date} confirmedDate - Confirmed date (optional) * @param {string} confirmedTime - Confirmed time (optional) * @returns {Promise<Object>} Updated booking */ async confirmBooking(bookingId, confirmedBy, confirmedDate = null, confirmedTime = null) { try { const booking = await models.Booking.findById(bookingId); if (!booking) { throw new Error('Booking not found'); } if (booking.status !== 'pending') { throw new Error('Booking is not in pending status'); } booking.confirm(confirmedBy, confirmedDate, confirmedTime); await booking.save(); return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, status: booking.status, confirmedDate: booking.confirmedDate, confirmedTime: booking.confirmedTime, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error confirming booking:', error); throw error; } } /** * Start booking * @param {string} bookingId - Booking ID * @param {string} startedBy - User ID who started * @returns {Promise<Object>} Updated booking */ async startBooking(bookingId, startedBy) { try { const booking = await models.Booking.findById(bookingId); if (!booking) { throw new Error('Booking not found'); } if (booking.status !== 'confirmed') { throw new Error('Booking must be confirmed before starting'); } booking.start(startedBy); await booking.save(); return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, status: booking.status, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error starting booking:', error); throw error; } } /** * Complete booking * @param {string} bookingId - Booking ID * @param {string} completedBy - User ID who completed * @param {string} notes - Optional completion notes * @returns {Promise<Object>} Updated booking */ async completeBooking(bookingId, completedBy, notes = null) { try { const booking = await models.Booking.findById(bookingId); if (!booking) { throw new Error('Booking not found'); } if (booking.status !== 'in_progress') { throw new Error('Booking must be in progress to complete'); } booking.complete(completedBy, notes); await booking.save(); return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, status: booking.status, completedAt: booking.completedAt, staffNotes: booking.staffNotes, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error completing booking:', error); throw error; } } /** * Cancel booking * @param {string} bookingId - Booking ID * @param {string} cancelledBy - User ID who cancelled * @param {string} reason - Cancellation reason * @returns {Promise<Object>} Updated booking */ async cancelBooking(bookingId, cancelledBy, reason = null) { try { const booking = await models.Booking.findById(bookingId); if (!booking) { throw new Error('Booking not found'); } if (booking.status === 'completed' || booking.status === 'cancelled') { throw new Error('Cannot cancel booking in current status'); } booking.cancel(cancelledBy, reason); await booking.save(); return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, status: booking.status, cancelledAt: booking.cancelledAt, cancellationReason: booking.cancellationReason, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error cancelling booking:', error); throw error; } } /** * Reschedule booking * @param {string} bookingId - Booking ID * @param {Date} newDate - New preferred date * @param {string} newTime - New preferred time * @param {string} rescheduledBy - User ID who rescheduled * @param {string} reason - Reschedule reason * @returns {Promise<Object>} Updated booking */ async rescheduleBooking(bookingId, newDate, newTime, rescheduledBy, reason = null) { try { const booking = await models.Booking.findById(bookingId); if (!booking) { throw new Error('Booking not found'); } if (booking.status === 'completed' || booking.status === 'cancelled') { throw new Error('Cannot reschedule booking in current status'); } booking.reschedule(newDate, newTime, rescheduledBy, reason); await booking.save(); return { success: true, booking: { id: booking._id, bookingNumber: booking.bookingNumber, preferredDate: booking.preferredDate, preferredTime: booking.preferredTime, rescheduledFrom: booking.rescheduledFrom, rescheduleReason: booking.rescheduleReason, status: booking.status, updatedAt: booking.updatedAt } }; } catch (error) { console.error('Error rescheduling booking:', error); throw error; } } /** * Get bookings by date range * @param {Date} startDate - Start date * @param {Date} endDate - End date * @param {Object} filters - Additional filters * @returns {Promise<Object>} Bookings in date range */ async getBookingsByDateRange(startDate, endDate, filters = {}) { try { const query = { preferredDate: { $gte: startDate, $lte: endDate }, ...filters }; const bookings = await models.Booking.find(query) .sort({ preferredDate: 1, preferredTime: 1 }) .lean(); return { success: true, bookings }; } catch (error) { console.error('Error getting bookings by date range:', error); throw error; } } /** * Get booking statistics * @param {string} userId - User ID (optional, for user-specific stats) * @returns {Promise<Object>} Booking statistics */ async getBookingStatistics(userId = null) { try { const query = userId ? { userId } : {}; const [ totalBookings, pendingBookings, confirmedBookings, completedBookings, cancelledBookings ] = await Promise.all([ models.Booking.countDocuments(query), models.Booking.countDocuments({ ...query, status: 'pending' }), models.Booking.countDocuments({ ...query, status: 'confirmed' }), models.Booking.countDocuments({ ...query, status: 'completed' }), models.Booking.countDocuments({ ...query, status: 'cancelled' }) ]); return { success: true, statistics: { totalBookings, pendingBookings, confirmedBookings, completedBookings, cancelledBookings } }; } catch (error) { console.error('Error getting booking statistics:', error); throw error; } } } export default new BookingService();