Files
hotell777_260507/modules/bookings/index.js
kalugin66 edbe54b915
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 1m0s
статистика2
2026-07-26 22:29:38 +05:00

192 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const config = require('../../config');
const { getRoomBasePriceByType } = config;
const seasonalModule = require('../seasonalPrices');
let db;
function init(database) {
db = database;
}
function getEffectivePrice(roomType, date, callback) {
seasonalModule.getSeasonalPrice(roomType, date, (err, seasonalPrice) => {
if (err || !seasonalPrice) {
const basePrice = getRoomBasePriceByType(roomType);
return callback(null, basePrice);
}
callback(null, seasonalPrice);
});
}
function calculateNightPrices(roomType, checkin, checkout, callback) {
const nights = config.calculateNights(checkin, checkout);
if (nights <= 0) return callback(new Error('Invalid dates'));
const dates = [];
for (let i = 0; i < nights; i++) {
const d = new Date(checkin);
d.setDate(d.getDate() + i);
dates.push(d.toISOString().split('T')[0]);
}
let total = 0;
let processed = 0;
let finished = false;
dates.forEach(date => {
getEffectivePrice(roomType, date, (err, price) => {
if (finished) return;
if (err) {
finished = true;
return callback(err, null);
}
total += price;
processed++;
if (processed === dates.length) {
finished = true;
callback(null, total);
}
});
});
}
function validatePromocode(promocode, callback) {
if (!promocode) return callback(null, null);
const now = new Date();
db.get(`SELECT * FROM promocodes WHERE code = ? AND is_active = 1`, [promocode], (err, row) => {
if (err || !row) return callback(null, null);
if (row.valid_from && new Date(row.valid_from) > now) return callback(null, null);
if (row.valid_to && new Date(row.valid_to) < now) return callback(null, null);
if (row.valid_days) {
const createdDate = new Date(row.created_at);
const expireDate = new Date(createdDate.getTime() + row.valid_days * 24 * 60 * 60 * 1000);
if (expireDate < new Date()) return callback(null, null);
}
callback(null, row);
});
}
function checkAvailability(roomId, roomType, checkin, checkout, requestedCount, callback) {
let sql;
let params;
if (roomId) {
sql = `SELECT r.rooms_count, COALESCE(
(SELECT COUNT(b.id) FROM bookings b
WHERE b.room_id = r.id AND b.status IN ('новая','оплачена','зарезервирована','заселена')
AND b.checkin_date < ? AND b.checkout_date > ?), 0) as booked
FROM rooms r WHERE r.id = ? AND r.is_active = 1`;
params = [checkout, checkin, roomId];
} else {
sql = `SELECT COALESCE(SUM(r.rooms_count), 0) as rooms_count,
(SELECT COUNT(b.id) FROM bookings b
WHERE b.room_type = ? AND b.status IN ('новая','оплачена','зарезервирована','заселена')
AND b.checkin_date < ? AND b.checkout_date > ?) as booked
FROM rooms r WHERE r.type = ? AND r.is_active = 1`;
params = [roomType, checkout, checkin, roomType];
}
db.get(sql, params, (err, row) => {
if (err) return callback(err, null);
if (!row) return callback(null, { available: 0, total: 0 });
const available = Math.max(0, row.rooms_count - row.booked);
callback(null, { available, total: row.rooms_count, booked: row.booked });
});
}
function createBooking(req, res) {
const { name, phone, adults, children, checkin, checkout, wishes, room, room_id, promocode } = req.body;
if (!name || !phone || !room || !adults || !checkin || !checkout) {
return res.status(400).json({ error: 'Missing required fields' });
}
const roomType = room;
const roomIdValue = room_id ? parseInt(room_id) : null;
checkAvailability(roomIdValue, roomType, checkin, checkout, 1, (err, avail) => {
if (err) return res.status(500).json({ error: 'Database error' });
if (avail.available < 1) {
return res.status(409).json({
error: `На выбранные даты нет свободных номеров типа "${roomType}". Доступно: ${avail.available} из ${avail.total}`,
available: avail.available
});
}
calculateNightPrices(roomType, checkin, checkout, (err, baseSum) => {
if (err) return res.status(400).json({ error: err.message });
const basePrice = baseSum * (parseInt(adults) || 1);
validatePromocode(promocode, (err, promo) => {
if (err) return res.status(500).json({ error: 'Database error' });
let discountPercent = 0;
let promocodeId = null;
if (promo) {
discountPercent = promo.discount_percent;
promocodeId = promo.id;
}
const safeBasePrice = basePrice || 0;
const discountAmount = Math.round(safeBasePrice * discountPercent / 100);
const totalPrice = safeBasePrice - discountAmount;
const stmt = db.prepare(`INSERT INTO bookings (name, phone, adults, children, checkin_date, checkout_date, wishes, status, room_type, room_id, base_price, discount_percent, discount_amount, total_price, promocode_id)
VALUES (?, ?, ?, ?, ?, ?, ?, 'новая', ?, ?, ?, ?, ?, ?, ?)`);
stmt.run(name, phone, parseInt(adults), parseInt(children || 0), checkin, checkout, wishes || null,
room || null, roomIdValue, safeBasePrice || null, discountPercent || 0, discountAmount || 0, totalPrice || null,
promocodeId, function(err) {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Database error' });
}
const booking = {
id: this.lastID,
name, phone, adults: parseInt(adults), children: parseInt(children || 0),
checkin_date: checkin, checkout_date: checkout, wishes, room_type: room, room_id: roomIdValue,
base_price: safeBasePrice, discount_percent: discountPercent, discount_amount: discountAmount,
total_price: totalPrice, promocode_id: promocodeId
};
try { require('../email').sendBookingConfirmation(booking); } catch {}
try { require('../telegram').sendBookingNotification(booking); } catch {}
res.status(201).json({
id: this.lastID, message: 'Booking saved',
base_price: safeBasePrice, discount_percent: discountPercent,
discount_amount: discountAmount, total_price: totalPrice
});
});
stmt.finalize();
});
});
});
}
function getBookings(req, res) {
const providedKey = req.headers['x-api-key'];
if (!providedKey) return res.status(401).json({ error: 'Invalid or missing API key' });
const API_KEY = process.env.HOTEL777KEY;
if (providedKey !== API_KEY) return res.status(401).json({ error: 'Invalid or missing API key' });
db.all(`SELECT b.*, p.code as promocode_code FROM bookings b
LEFT JOIN promocodes p ON b.promocode_id = p.id
ORDER BY b.checkin_date ASC`, (err, rows) => {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Database error' });
}
res.json(rows);
});
}
function getBookingHistory(req, res) {
const bookingId = parseInt(req.params.id);
db.all(`SELECT id, booking_id, user_id, user_login, field, old_value, new_value, created_at
FROM booking_history WHERE booking_id = ? ORDER BY created_at DESC`, [bookingId], (err, rows) => {
if (err) return res.status(500).json({ error: 'Database error' });
res.json(rows);
});
}
function setupRoutes(app, authenticateToken, requireAdmin) {
app.post('/api/bookings', createBooking);
app.get('/api/bookings', getBookings);
app.get('/api/admin/bookings/:id/history', authenticateToken, getBookingHistory);
}
module.exports = { init, setupRoutes, validatePromocode, calculateNightPrices, checkAvailability };