@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
JavaScript
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();