/** * 预订控制器 * * 功能:处理预订管理的业务逻辑 */ const Reservation = require('../models/Reservation'); const { validateRequired, validatePhone, validateBatch } = require('../utils/validator'); const { success } = require('../utils/response'); const { BusinessError, ValidationError, AuthorizationError } = require('../middleware/errorHandler'); /** * 获取预订列表(分页、筛选) * GET /api/reservations */ const getReservations = async (req, res, next) => { try { const { page = 1, pageSize = 10, type, status, date, table_number } = req.query; // 权限控制:顾客只能查看自己的预订 const customer_id = req.user.role === 'customer' ? req.user.id : undefined; const result = await Reservation.findAll({ page: parseInt(page), pageSize: parseInt(pageSize), customer_id, type, status, date, table_number }); res.json(success({ list: result.list, pagination: { total: result.total, page: result.page, pageSize: result.pageSize, totalPages: Math.ceil(result.total / result.pageSize) } }, '获取预订列表成功')); } catch (err) { next(err); } }; /** * 获取单个预订详情 * GET /api/reservations/:id */ const getReservationById = async (req, res, next) => { try { const { id } = req.params; const reservation = await Reservation.findById(id); if (!reservation) { throw new BusinessError('预订不存在', 404); } // 权限控制:顾客只能查看自己的预订 if (req.user.role === 'customer' && reservation.customer_id !== req.user.id) { throw new AuthorizationError('您没有权限查看此预订'); } res.json(success(reservation, '获取预订详情成功')); } catch (err) { next(err); } }; /** * 创建预订 * POST /api/reservations */ const createReservation = async (req, res, next) => { try { const { type, reservation_time, guest_count, table_number, contact_name, contact_phone, special_request } = req.body; // 数据验证 const validation = validateBatch([ validateRequired(type, '预订类型'), validateRequired(reservation_time, '预订时间'), validateRequired(guest_count, '客人数量'), validateRequired(contact_name, '联系人姓名'), validateRequired(contact_phone, '联系电话'), validatePhone(contact_phone, '联系电话') ]); if (!validation.valid) { throw new ValidationError('数据验证失败', validation.errors); } // 验证预订类型 const validTypes = ['dine_in', 'takeaway']; if (!validTypes.includes(type)) { throw new ValidationError('预订类型必须是 dine_in 或 takeaway'); } // 验证客人数量 if (parseInt(guest_count) <= 0) { throw new ValidationError('客人数量必须大于0'); } // 验证预订时间不能是过去时间 const reservationDate = new Date(reservation_time); if (reservationDate < new Date()) { throw new ValidationError('预订时间不能早于当前时间'); } // 堂食预订必须有桌号 if (type === 'dine_in') { if (!table_number) { throw new ValidationError('堂食预订必须指定桌号'); } // 检查桌位是否可用 const isAvailable = await Reservation.isTableAvailable(table_number, reservation_time); if (!isAvailable) { throw new BusinessError('该桌位在所选时间段内已被预订,请选择其他桌位或时间'); } } const reservation = await Reservation.create({ customer_id: req.user.id, type, reservation_time, guest_count: parseInt(guest_count), table_number: type === 'dine_in' ? parseInt(table_number) : null, contact_name, contact_phone, special_request: special_request || null }); res.status(201).json(success(reservation, '创建预订成功')); } catch (err) { next(err); } }; /** * 更新预订 * PUT /api/reservations/:id */ const updateReservation = async (req, res, next) => { try { const { id } = req.params; const { reservation_time, guest_count, table_number, contact_name, contact_phone, special_request } = req.body; // 检查预订是否存在 const reservation = await Reservation.findById(id); if (!reservation) { throw new BusinessError('预订不存在', 404); } // 权限控制:顾客只能修改自己的预订 if (req.user.role === 'customer' && reservation.customer_id !== req.user.id) { throw new AuthorizationError('您没有权限修改此预订'); } // 已完成或已取消的预订不能修改 if (['completed', 'cancelled', 'no_show'].includes(reservation.status)) { throw new BusinessError(`${reservation.status === 'completed' ? '已完成' : reservation.status === 'cancelled' ? '已取消' : '未到场'}的预订不能修改`); } // 验证数据 if (guest_count !== undefined && parseInt(guest_count) <= 0) { throw new ValidationError('客人数量必须大于0'); } if (contact_phone && !validatePhone(contact_phone).valid) { throw new ValidationError('联系电话格式不正确'); } // 如果修改了预订时间,检查是否为过去时间 const newReservationTime = reservation_time || reservation.reservation_time; if (new Date(newReservationTime) < new Date()) { throw new ValidationError('预订时间不能早于当前时间'); } // 如果是堂食且修改了桌号或时间,检查桌位是否可用 if (reservation.type === 'dine_in' && (table_number || reservation_time)) { const newTableNumber = table_number || reservation.table_number; const isAvailable = await Reservation.isTableAvailable( newTableNumber, newReservationTime, id ); if (!isAvailable) { throw new BusinessError('该桌位在所选时间段内已被预订,请选择其他桌位或时间'); } } const updatedReservation = await Reservation.update(id, { reservation_time, guest_count: guest_count !== undefined ? parseInt(guest_count) : undefined, table_number: table_number !== undefined ? (table_number ? parseInt(table_number) : null) : undefined, contact_name, contact_phone, special_request: special_request !== undefined ? special_request : undefined }); res.json(success(updatedReservation, '更新预订成功')); } catch (err) { next(err); } }; /** * 更新预订状态 * PUT /api/reservations/:id/status * 需要管理员或服务员权限 */ const updateReservationStatus = async (req, res, next) => { try { const { id } = req.params; const { status } = req.body; // 数据验证 const validation = validateRequired(status, '状态'); if (!validation.valid) { throw new ValidationError('数据验证失败', validation.errors); } // 验证状态值 const validStatuses = ['pending', 'confirmed', 'completed', 'cancelled', 'no_show']; if (!validStatuses.includes(status)) { throw new ValidationError(`状态必须是以下之一: ${validStatuses.join(', ')}`); } // 检查预订是否存在 const reservation = await Reservation.findById(id); if (!reservation) { throw new BusinessError('预订不存在', 404); } // 已完成或未到场的预订不能修改状态 if (['completed', 'no_show'].includes(reservation.status)) { throw new BusinessError(`${reservation.status === 'completed' ? '已完成' : '未到场'}的预订不能修改状态`); } const updatedReservation = await Reservation.updateStatus(id, status); const messages = { pending: '预订已标记为待确认', confirmed: '预订已确认', completed: '预订已完成', cancelled: '预订已取消', no_show: '预订已标记为未到场' }; res.json(success(updatedReservation, messages[status])); } catch (err) { next(err); } }; /** * 取消预订 * PUT /api/reservations/:id/cancel */ const cancelReservation = async (req, res, next) => { try { const { id } = req.params; const { cancel_reason } = req.body; // 检查预订是否存在 const reservation = await Reservation.findById(id); if (!reservation) { throw new BusinessError('预订不存在', 404); } // 权限控制:顾客只能取消自己的预订,管理员和服务员可以取消任何预订 if (req.user.role === 'customer' && reservation.customer_id !== req.user.id) { throw new AuthorizationError('您没有权限取消此预订'); } // 已完成、已取消或未到场的预订不能再次取消 if (['completed', 'cancelled', 'no_show'].includes(reservation.status)) { throw new BusinessError('该预订已完成或已取消'); } const updatedReservation = await Reservation.cancel(id, cancel_reason || null); res.json(success(updatedReservation, '取消预订成功')); } catch (err) { next(err); } }; /** * 删除预订 * DELETE /api/reservations/:id * 需要管理员权限 */ const deleteReservation = async (req, res, next) => { try { const { id } = req.params; // 检查预订是否存在 const reservation = await Reservation.findById(id); if (!reservation) { throw new BusinessError('预订不存在', 404); } await Reservation.delete(id); res.json(success(null, '删除预订成功')); } catch (err) { next(err); } }; /** * 获取今日预订列表 * GET /api/reservations/today */ const getTodayReservations = async (req, res, next) => { try { const { type } = req.query; const reservations = await Reservation.getTodayReservations(type || null); res.json(success({ count: reservations.length, reservations }, '获取今日预订成功')); } catch (err) { next(err); } }; /** * 获取即将到来的预订 * GET /api/reservations/upcoming */ const getUpcomingReservations = async (req, res, next) => { try { // 权限控制:顾客只能查看自己的预订 const customer_id = req.user.role === 'customer' ? req.user.id : null; const reservations = await Reservation.getUpcomingReservations(customer_id); res.json(success({ count: reservations.length, reservations }, '获取即将到来的预订成功')); } catch (err) { next(err); } }; /** * 获取我的预订列表 * GET /api/reservations/my */ const getMyReservations = async (req, res, next) => { try { const { page = 1, pageSize = 10, status } = req.query; const result = await Reservation.findAll({ page: parseInt(page), pageSize: parseInt(pageSize), customer_id: req.user.id, status }); res.json(success({ list: result.list, pagination: { total: result.total, page: result.page, pageSize: result.pageSize, totalPages: Math.ceil(result.total / result.pageSize) } }, '获取我的预订成功')); } catch (err) { next(err); } }; /** * 获取可用桌位 * GET /api/reservations/available-tables */ const getAvailableTables = async (req, res, next) => { try { const { reservation_time, guest_count } = req.query; // 数据验证 const validation = validateRequired(reservation_time, '预订时间'); if (!validation.valid) { throw new ValidationError('数据验证失败', validation.errors); } const tables = await Reservation.getAvailableTables( reservation_time, guest_count ? parseInt(guest_count) : null ); res.json(success({ count: tables.length, tables }, '获取可用桌位成功')); } catch (err) { next(err); } }; /** * 获取预订统计 * GET /api/reservations/statistics */ const getStatistics = async (req, res, next) => { try { const { start_date, end_date } = req.query; const stats = await Reservation.getStatistics( start_date || null, end_date || null ); res.json(success(stats, '获取预订统计成功')); } catch (err) { next(err); } }; // 导出所有控制器函数 module.exports = { getReservations, getReservationById, createReservation, updateReservation, updateReservationStatus, cancelReservation, deleteReservation, getTodayReservations, getUpcomingReservations, getMyReservations, getAvailableTables, getStatistics };