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(); }); });