/** * 预订数据模型 * * 功能:封装预订表的数据库操作 */ const { pool } = require('../config/database'); /** * 预订模型类 */ class Reservation { /** * 根据ID查找预订 * @param {number} id - 预订ID * @returns {Promise} 预订对象或null */ static async findById(id) { try { const [rows] = await pool.query( `SELECT r.*, u.username as customer_name, u.phone as customer_phone FROM reservations r LEFT JOIN users u ON r.customer_id = u.id WHERE r.id = ?`, [id] ); return rows[0] || null; } catch (error) { throw new Error('查询预订失败: ' + error.message); } } /** * 获取预订列表(分页、筛选) * @param {object} options - 查询选项 * @returns {Promise} 预订列表和总数 */ static async findAll(options = {}) { try { const { page = 1, pageSize = 10, customer_id, type, status, date, table_number } = options; const offset = (page - 1) * pageSize; // 构建WHERE条件 const conditions = []; const params = []; if (customer_id) { conditions.push('r.customer_id = ?'); params.push(customer_id); } if (type) { conditions.push('r.type = ?'); params.push(type); } if (status) { conditions.push('r.status = ?'); params.push(status); } if (date) { conditions.push('DATE(r.reservation_time) = ?'); params.push(date); } if (table_number) { conditions.push('r.table_number = ?'); params.push(table_number); } const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : ''; // 查询总数 const [countResult] = await pool.query( `SELECT COUNT(*) as total FROM reservations r ${whereClause}`, params ); const total = countResult[0].total; // 查询列表 const [rows] = await pool.query( `SELECT r.*, u.username as customer_name, u.phone as customer_phone FROM reservations r LEFT JOIN users u ON r.customer_id = u.id ${whereClause} ORDER BY r.reservation_time DESC LIMIT ? OFFSET ?`, [...params, pageSize, offset] ); return { list: rows, total, page: parseInt(page), pageSize: parseInt(pageSize) }; } catch (error) { throw new Error('查询预订列表失败: ' + error.message); } } /** * 创建新预订 * @param {object} reservationData - 预订数据 * @returns {Promise} 创建的预订 */ static async create(reservationData) { try { const { customer_id, type, reservation_time, guest_count, table_number, contact_name, contact_phone, special_request } = reservationData; const [result] = await pool.query( `INSERT INTO reservations (customer_id, type, reservation_time, guest_count, table_number, contact_name, contact_phone, special_request, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`, [ customer_id, type, reservation_time, guest_count, table_number || null, contact_name, contact_phone, special_request || null ] ); return await this.findById(result.insertId); } catch (error) { throw new Error('创建预订失败: ' + error.message); } } /** * 更新预订 * @param {number} id - 预订ID * @param {object} reservationData - 要更新的数据 * @returns {Promise} 更新后的预订 */ static async update(id, reservationData) { try { const { reservation_time, guest_count, table_number, contact_name, contact_phone, special_request } = reservationData; const updates = []; const values = []; if (reservation_time !== undefined) { updates.push('reservation_time = ?'); values.push(reservation_time); } if (guest_count !== undefined) { updates.push('guest_count = ?'); values.push(guest_count); } if (table_number !== undefined) { updates.push('table_number = ?'); values.push(table_number); } if (contact_name !== undefined) { updates.push('contact_name = ?'); values.push(contact_name); } if (contact_phone !== undefined) { updates.push('contact_phone = ?'); values.push(contact_phone); } if (special_request !== undefined) { updates.push('special_request = ?'); values.push(special_request); } if (updates.length === 0) { throw new Error('没有要更新的数据'); } values.push(id); await pool.query( `UPDATE reservations SET ${updates.join(', ')} WHERE id = ?`, values ); return await this.findById(id); } catch (error) { throw new Error('更新预订失败: ' + error.message); } } /** * 更新预订状态 * @param {number} id - 预订ID * @param {string} status - 新状态 * @returns {Promise} 更新后的预订 */ static async updateStatus(id, status) { try { await pool.query( 'UPDATE reservations SET status = ?, updated_at = NOW() WHERE id = ?', [status, id] ); return await this.findById(id); } catch (error) { throw new Error('更新预订状态失败: ' + error.message); } } /** * 取消预订 * @param {number} id - 预订ID * @param {string} cancelReason - 取消原因 * @returns {Promise} 更新后的预订 */ static async cancel(id, cancelReason = null) { try { await pool.query( `UPDATE reservations SET status = 'cancelled', cancel_reason = ?, updated_at = NOW() WHERE id = ?`, [cancelReason, id] ); return await this.findById(id); } catch (error) { throw new Error('取消预订失败: ' + error.message); } } /** * 删除预订 * @param {number} id - 预订ID * @returns {Promise} 是否成功 */ static async delete(id) { try { await pool.query('DELETE FROM reservations WHERE id = ?', [id]); return true; } catch (error) { throw new Error('删除预订失败: ' + error.message); } } /** * 检查桌位是否可用 * @param {number} tableNumber - 桌号 * @param {string} reservationTime - 预订时间 * @param {number} excludeId - 排除的预订ID(用于更新时) * @returns {Promise} 是否可用 */ static async isTableAvailable(tableNumber, reservationTime, excludeId = null) { try { // 检查前后2小时内是否有冲突的预订 const query = excludeId ? `SELECT id FROM reservations WHERE table_number = ? AND type = 'dine_in' AND status IN ('pending', 'confirmed') AND id != ? AND ABS(TIMESTAMPDIFF(MINUTE, reservation_time, ?)) < 120` : `SELECT id FROM reservations WHERE table_number = ? AND type = 'dine_in' AND status IN ('pending', 'confirmed') AND ABS(TIMESTAMPDIFF(MINUTE, reservation_time, ?)) < 120`; const params = excludeId ? [tableNumber, excludeId, reservationTime] : [tableNumber, reservationTime]; const [rows] = await pool.query(query, params); return rows.length === 0; } catch (error) { throw new Error('检查桌位可用性失败: ' + error.message); } } /** * 获取指定时间段内的预订列表 * @param {string} startTime - 开始时间 * @param {string} endTime - 结束时间 * @param {string} type - 预订类型(可选) * @returns {Promise} 预订列表 */ static async findByTimeRange(startTime, endTime, type = null) { try { let query = ` SELECT r.*, u.username as customer_name, u.phone as customer_phone FROM reservations r LEFT JOIN users u ON r.customer_id = u.id WHERE r.reservation_time >= ? AND r.reservation_time <= ? AND r.status IN ('pending', 'confirmed') `; const params = [startTime, endTime]; if (type) { query += ' AND r.type = ?'; params.push(type); } query += ' ORDER BY r.reservation_time ASC'; const [rows] = await pool.query(query, params); return rows; } catch (error) { throw new Error('查询时间段预订失败: ' + error.message); } } /** * 获取今日预订列表 * @param {string} type - 预订类型(可选) * @returns {Promise} 今日预订列表 */ static async getTodayReservations(type = null) { try { const today = new Date().toISOString().split('T')[0]; let query = ` SELECT r.*, u.username as customer_name, u.phone as customer_phone FROM reservations r LEFT JOIN users u ON r.customer_id = u.id WHERE DATE(r.reservation_time) = ? AND r.status IN ('pending', 'confirmed') `; const params = [today]; if (type) { query += ' AND r.type = ?'; params.push(type); } query += ' ORDER BY r.reservation_time ASC'; const [rows] = await pool.query(query, params); return rows; } catch (error) { throw new Error('查询今日预订失败: ' + error.message); } } /** * 获取即将到来的预订(未来24小时内) * @param {number} customerId - 顾客ID(可选) * @returns {Promise} 预订列表 */ static async getUpcomingReservations(customerId = null) { try { let query = ` SELECT r.*, u.username as customer_name, u.phone as customer_phone FROM reservations r LEFT JOIN users u ON r.customer_id = u.id WHERE r.reservation_time >= NOW() AND r.reservation_time <= DATE_ADD(NOW(), INTERVAL 24 HOUR) AND r.status IN ('pending', 'confirmed') `; const params = []; if (customerId) { query += ' AND r.customer_id = ?'; params.push(customerId); } query += ' ORDER BY r.reservation_time ASC'; const [rows] = await pool.query(query, params); return rows; } catch (error) { throw new Error('查询即将到来的预订失败: ' + error.message); } } /** * 获取预订统计信息 * @param {string} startDate - 开始日期(可选) * @param {string} endDate - 结束日期(可选) * @returns {Promise} 统计信息 */ static async getStatistics(startDate = null, endDate = null) { try { let whereClause = ''; const params = []; if (startDate && endDate) { whereClause = 'WHERE DATE(reservation_time) BETWEEN ? AND ?'; params.push(startDate, endDate); } // 总预订数和各状态数量 const [statusStats] = await pool.query( `SELECT COUNT(*) as total_reservations, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count, SUM(CASE WHEN status = 'no_show' THEN 1 ELSE 0 END) as no_show_count FROM reservations ${whereClause}`, params ); // 按类型统计 const [typeStats] = await pool.query( `SELECT type, COUNT(*) as count, SUM(guest_count) as total_guests FROM reservations ${whereClause} GROUP BY type`, params ); return { ...statusStats[0], by_type: typeStats }; } catch (error) { throw new Error('查询预订统计失败: ' + error.message); } } /** * 获取可用桌位列表 * @param {string} reservationTime - 预订时间 * @param {number} guestCount - 客人数量(可选,用于推荐合适的桌位) * @returns {Promise} 可用桌位列表 */ static async getAvailableTables(reservationTime, guestCount = null) { try { // 获取所有已被预订的桌位(前后2小时内) const [bookedTables] = await pool.query( `SELECT DISTINCT table_number FROM reservations WHERE table_number IS NOT NULL AND type = 'dine_in' AND status IN ('pending', 'confirmed') AND ABS(TIMESTAMPDIFF(MINUTE, reservation_time, ?)) < 120`, [reservationTime] ); const bookedTableNumbers = bookedTables.map(t => t.table_number); // 简化版:返回1-20号桌中未被预订的桌位 const allTables = Array.from({ length: 20 }, (_, i) => ({ table_number: i + 1, capacity: i < 10 ? 4 : 8, // 前10桌4人桌,后10桌8人桌 is_available: !bookedTableNumbers.includes(i + 1) })); // 如果提供了客人数量,优先推荐合适容量的桌位 if (guestCount) { return allTables .filter(t => t.is_available) .sort((a, b) => { const aDiff = Math.abs(a.capacity - guestCount); const bDiff = Math.abs(b.capacity - guestCount); return aDiff - bDiff; }); } return allTables.filter(t => t.is_available); } catch (error) { throw new Error('查询可用桌位失败: ' + error.message); } } } module.exports = Reservation;