Files
hotell777_260507/modules/bookings/index.js
kalugin66 0277b46e33
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 58s
фикс3
2026-07-26 23:57:14 +05:00

212 lines
9.5 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
});
}
db.get(`SELECT max_guests, extra_beds, extra_bed_price FROM rooms WHERE type = ? AND is_active = 1 LIMIT 1`,
[roomType], (err, roomRow) => {
if (err) return res.status(500).json({ error: 'Database error' });
const roomMax = roomRow ? roomRow.max_guests : 99;
const roomExtraBeds = roomRow ? roomRow.extra_beds : 0;
const roomExtraBedPrice = roomRow ? roomRow.extra_bed_price : 0;
const adultsCount = parseInt(adults) || 1;
if (adultsCount > roomMax && roomExtraBeds === 0) {
return res.status(400).json({ error: `Максимум ${roomMax} гостей для этого типа номера` });
}
if (adultsCount > roomMax + 1) {
return res.status(400).json({ error: `Максимум ${roomMax + 1} гостей (включая доп. место)` });
}
calculateNightPrices(roomType, checkin, checkout, (err, baseSum) => {
if (err) return res.status(400).json({ error: err.message });
let basePrice = baseSum;
if (adultsCount > roomMax && roomExtraBeds > 0) {
const nights = config.calculateNights(checkin, checkout);
basePrice += (adultsCount - roomMax) * roomExtraBedPrice * nights;
}
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 };