diff --git a/modules/adminBookings/index.js b/modules/adminBookings/index.js
index f136b67..6fbc8b6 100644
--- a/modules/adminBookings/index.js
+++ b/modules/adminBookings/index.js
@@ -147,7 +147,11 @@ function updateBookingRoom(req, res) {
const totalGuests = (booking.adults || 0) + (booking.children || 0);
calculateNightPrices(room.type, booking.checkin_date, booking.checkout_date, (err, baseSum) => {
if (err) return res.status(400).json({ error: err.message });
- const basePrice = baseSum * Math.max(1, totalGuests);
+ let basePrice = baseSum;
+ if (totalGuests > (room.max_guests || 99) && room.extra_beds > 0) {
+ const nights = config.calculateNights(booking.checkin_date, booking.checkout_date);
+ basePrice += (totalGuests - (room.max_guests || 99)) * room.extra_bed_price * nights;
+ }
const discountAmount = Math.round(basePrice * (booking.discount_percent || 0) / 100);
const totalPrice = basePrice - discountAmount;
@@ -307,17 +311,29 @@ function updateBookingDetails(req, res) {
if (!roomType) {
return finishUpdate();
}
- calculateNightPrices(roomType, finalCheckin, finalCheckout, (err, baseSum) => {
- if (err) return res.status(500).json({ error: err.message });
- const basePrice = baseSum * totalGuests;
- const discountPercent = row.discount_percent || 0;
- const discountAmount = Math.round(basePrice * discountPercent / 100);
- const totalPrice = basePrice - discountAmount;
- db.run(`UPDATE bookings SET base_price = ?, discount_amount = ?, total_price = ? WHERE id = ?`,
- [basePrice, discountAmount, totalPrice, bookingId], (err) => {
- if (err) return res.status(500).json({ error: 'Database error' });
- finishUpdate();
- });
+ db.get(`SELECT max_guests, extra_beds, extra_bed_price FROM rooms WHERE type = ? AND is_active = 1 LIMIT 1`,
+ [roomType], (err, rm) => {
+ if (err) return res.status(500).json({ error: 'Database error' });
+ const roomMax = rm ? rm.max_guests : 99;
+ const roomExtraBeds = rm ? rm.extra_beds : 0;
+ const roomExtraBedPrice = rm ? rm.extra_bed_price : 0;
+
+ calculateNightPrices(roomType, finalCheckin, finalCheckout, (err, baseSum) => {
+ if (err) return res.status(500).json({ error: err.message });
+ const nights = config.calculateNights(finalCheckin, finalCheckout);
+ let basePrice = baseSum;
+ if (totalGuests > roomMax && roomExtraBeds > 0) {
+ basePrice += (totalGuests - roomMax) * roomExtraBedPrice * nights;
+ }
+ const discountPercent = row.discount_percent || 0;
+ const discountAmount = Math.round(basePrice * discountPercent / 100);
+ const totalPrice = basePrice - discountAmount;
+ db.run(`UPDATE bookings SET base_price = ?, discount_amount = ?, total_price = ? WHERE id = ?`,
+ [basePrice, discountAmount, totalPrice, bookingId], (err) => {
+ if (err) return res.status(500).json({ error: 'Database error' });
+ finishUpdate();
+ });
+ });
});
function finishUpdate() {
db.get(`SELECT b.*, p.code as promocode_code, r.name as room_name
@@ -353,18 +369,29 @@ function validatePromocodeAPI(req, res) {
if (err) return res.status(500).json({ error: 'Database error' });
if (!promo) return res.status(404).json({ error: 'Invalid or expired promocode' });
const guestsCount = parseInt(guests) || 1;
- calculateNightPrices(room_type, checkin, checkout, (err, baseSum) => {
- if (err) return res.status(400).json({ error: err.message });
- const basePrice = baseSum * guestsCount;
- const discountAmount = Math.round(basePrice * promo.discount_percent / 100);
- const totalPrice = basePrice - discountAmount;
- res.json({
- valid: true,
- discount_percent: promo.discount_percent,
- base_price: basePrice,
- discount_amount: discountAmount,
- total_price: totalPrice,
- code: promo.code
+ db.get(`SELECT max_guests, extra_beds, extra_bed_price FROM rooms WHERE type = ? AND is_active = 1 LIMIT 1`,
+ [room_type], (err, rm) => {
+ if (err) return res.status(500).json({ error: 'Database error' });
+ const roomMax = rm ? rm.max_guests : 99;
+ const roomExtraBeds = rm ? rm.extra_beds : 0;
+ const roomExtraBedPrice = rm ? rm.extra_bed_price : 0;
+ calculateNightPrices(room_type, checkin, checkout, (err, baseSum) => {
+ if (err) return res.status(400).json({ error: err.message });
+ let basePrice = baseSum;
+ if (guestsCount > roomMax && roomExtraBeds > 0) {
+ const nights = config.calculateNights(checkin, checkout);
+ basePrice += (guestsCount - roomMax) * roomExtraBedPrice * nights;
+ }
+ const discountAmount = Math.round(basePrice * promo.discount_percent / 100);
+ const totalPrice = basePrice - discountAmount;
+ res.json({
+ valid: true,
+ discount_percent: promo.discount_percent,
+ base_price: basePrice,
+ discount_amount: discountAmount,
+ total_price: totalPrice,
+ code: promo.code
+ });
});
});
});
diff --git a/modules/bookings/index.js b/modules/bookings/index.js
index 5c31469..77fa521 100644
--- a/modules/bookings/index.js
+++ b/modules/bookings/index.js
@@ -112,9 +112,28 @@ function createBooking(req, res) {
});
}
- calculateNightPrices(roomType, checkin, checkout, (err, baseSum) => {
- if (err) return res.status(400).json({ error: err.message });
- const basePrice = baseSum * (parseInt(adults) || 1);
+ 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' });
@@ -156,6 +175,7 @@ function createBooking(req, res) {
});
});
});
+});
}
function getBookings(req, res) {
diff --git a/public/css/style.css b/public/css/style.css
index dfce029..5b972d7 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -1395,6 +1395,13 @@ h1, h2, h3, h4 {
box-shadow: 0 10px 30px rgba(10,77,104,0.3);
}
+/* Extra bed option in guest select */
+select option[data-extra="1"] {
+ background: #fef3e2;
+ color: #92400e;
+ font-weight: 600;
+}
+
/* Floating phone button */
.float-phone {
position: fixed;
diff --git a/public/js/main.js b/public/js/main.js
index 980dba1..b264224 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -116,7 +116,7 @@ document.querySelectorAll('.btn-book').forEach(btn => {
});
});
-function updateGuestOptionsDynamic(roomType, maxGuests) {
+function updateGuestOptionsDynamic(roomType, maxGuests, extraBeds = 0, extraBedPrice = 0) {
const guestsSelect = document.querySelector('[name="guests"]');
if (!guestsSelect) return;
@@ -126,16 +126,21 @@ function updateGuestOptionsDynamic(roomType, maxGuests) {
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
options.push(``);
}
+ if (extraBeds > 0 && extraBedPrice > 0) {
+ options.push(``);
+ }
guestsSelect.innerHTML = options.join('');
}
-window.updateRoomPricesData = function(prices, maxGuests) {
+window.updateRoomPricesData = function(prices, maxGuests, extraBeds, extraBedPrices) {
Object.assign(ROOM_PRICES, prices);
Object.assign(ROOM_MAX_GUESTS, maxGuests);
+ if (extraBeds) Object.assign(ROOM_EXTRA_BEDS, extraBeds);
+ if (extraBedPrices) Object.assign(ROOM_EXTRA_BED_PRICES, extraBedPrices);
};
-function updateGuestOptions(room) {
+function updateGuestOptions(room, extraBeds = 0, extraBedPrice = 0) {
const guestsSelect = document.querySelector('[name="guests"]');
if (!guestsSelect) return;
@@ -146,6 +151,9 @@ function updateGuestOptions(room) {
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
options.push(``);
}
+ if (extraBeds > 0 && extraBedPrice > 0) {
+ options.push(``);
+ }
guestsSelect.innerHTML = options.join('');
}
@@ -184,6 +192,8 @@ function hideGuestOptionsUpdate() {}
// Price calculation
const ROOM_PRICES = { '2x-местный': 1500, '3х-местный': 2000, 'Семейный': 3000, 'Люкс': 4500 };
const ROOM_MAX_GUESTS = { '2x-местный': 2, '3х-местный': 3, 'Семейный': 4, 'Люкс': 4 };
+const ROOM_EXTRA_BEDS = {};
+const ROOM_EXTRA_BED_PRICES = {};
let currentPromocodeData = null;
function calculateNights(checkin, checkout) {
@@ -254,7 +264,14 @@ async function checkPromocode() {
}
const guests = parseInt(document.querySelector('[name="guests"]').value) || 1;
- const basePrice = ROOM_PRICES[room] ? ROOM_PRICES[room] * guests * calculateNights(checkin, checkout) : 0;
+ const nightCount = calculateNights(checkin, checkout);
+ const maxGuests = ROOM_MAX_GUESTS[room] || 4;
+ const extraBeds = ROOM_EXTRA_BEDS[room] || 0;
+ const extraBedPrice = ROOM_EXTRA_BED_PRICES[room] || 0;
+ let basePrice = ROOM_PRICES[room] ? ROOM_PRICES[room] * nightCount : 0;
+ if (guests > maxGuests && extraBeds > 0) {
+ basePrice += (guests - maxGuests) * extraBedPrice * nightCount;
+ }
if (!promocode) {
updatePriceDisplay(basePrice, 0, 0, basePrice);
diff --git a/public/js/rooms-public.js b/public/js/rooms-public.js
index 44ac86a..1bf84d1 100644
--- a/public/js/rooms-public.js
+++ b/public/js/rooms-public.js
@@ -113,7 +113,7 @@ function renderRoomsPublic(rooms) {
${rt('rooms.from', 'от')} ${room.price_per_night || 0} ₽
${rt('rooms.per_night', '/ ночь')}
-
+
@@ -180,12 +180,16 @@ function applyCarouselFrame(index, images) {
function updateRoomPrices(rooms) {
const prices = {};
const maxGuests = {};
+ const extraBeds = {};
+ const extraBedPrices = {};
rooms.forEach(room => {
prices[room.type] = room.price_per_night;
maxGuests[room.type] = room.max_guests;
+ extraBeds[room.type] = room.extra_beds || 0;
+ extraBedPrices[room.type] = room.extra_bed_price || 0;
});
if (window.updateRoomPricesData) {
- window.updateRoomPricesData(prices, maxGuests);
+ window.updateRoomPricesData(prices, maxGuests, extraBeds, extraBedPrices);
}
}
@@ -196,9 +200,11 @@ function initRoomBookingHandlers() {
const roomType = this.getAttribute('data-room-type');
const roomName = this.getAttribute('data-room-name');
const maxGuests = parseInt(this.getAttribute('data-max-guests')) || 4;
+ const extraBeds = parseInt(this.getAttribute('data-extra-beds')) || 0;
+ const extraBedPrice = parseInt(this.getAttribute('data-extra-bed-price')) || 0;
document.getElementById('selectedRoom').value = roomType;
document.getElementById('selectedRoomId').value = roomId;
- updateGuestOptionsDynamic(roomType, maxGuests);
+ updateGuestOptionsDynamic(roomType, maxGuests, extraBeds, extraBedPrice);
hidePriceInfo();
});
});