Реалізація бронювання номерів готелю на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація бронювання номерів готелю на сайті
Складна
~2-4 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація бронювання номерів готелю на веб-сайту

Готельне бронювання — один з найскладніших підвидів резервування: мультиночівки, тарифи з різними умовами скасування, сезонне ціноутворення, синхронізація з зовнішніми системами (OTA, PMS). Навіть базова реалізація повинна коректно працювати з date ranges, а не з часовими слотами.

Модель даних

CREATE TABLE room_types (
    id              SERIAL PRIMARY KEY,
    name            VARCHAR(100) NOT NULL,   -- 'Стандарт', 'Делюкс', 'Люкс'
    description     TEXT,
    max_occupancy   SMALLINT NOT NULL,
    area_sqm        NUMERIC(5,1),
    amenities       TEXT[],
    images          JSONB DEFAULT '[]',
    base_price      NUMERIC(10,2)
);

CREATE TABLE rooms (
    id              SERIAL PRIMARY KEY,
    room_type_id    INTEGER REFERENCES room_types(id),
    room_number     VARCHAR(10) NOT NULL,
    floor           SMALLINT,
    is_active       BOOLEAN DEFAULT TRUE
);

-- Динамічні тарифи (сезонність, знижки, мінімальний стей)
CREATE TABLE rate_plans (
    id              SERIAL PRIMARY KEY,
    room_type_id    INTEGER REFERENCES room_types(id),
    name            VARCHAR(100),
    price           NUMERIC(10,2),
    valid_from      DATE NOT NULL,
    valid_until     DATE NOT NULL,
    min_stay_nights SMALLINT DEFAULT 1,
    cancellation_hours INTEGER DEFAULT 24,  -- безкоштовне скасування за N годин
    is_refundable   BOOLEAN DEFAULT TRUE,
    includes_breakfast BOOLEAN DEFAULT FALSE
);

CREATE TABLE reservations (
    id              BIGSERIAL PRIMARY KEY,
    room_id         INTEGER REFERENCES rooms(id),
    room_type_id    INTEGER,
    rate_plan_id    INTEGER REFERENCES rate_plans(id),
    check_in        DATE NOT NULL,
    check_out       DATE NOT NULL,
    adults          SMALLINT DEFAULT 1,
    children        SMALLINT DEFAULT 0,
    guest_name      VARCHAR(255) NOT NULL,
    guest_email     VARCHAR(255) NOT NULL,
    guest_phone     VARCHAR(50),
    total_amount    NUMERIC(12,2),
    status          VARCHAR(20) DEFAULT 'pending',
    -- pending | confirmed | checked_in | checked_out | cancelled | no_show
    payment_status  VARCHAR(20) DEFAULT 'unpaid',
    notes           TEXT,
    source          VARCHAR(30) DEFAULT 'website',
    external_id     VARCHAR(100),   -- ID в OTA/PMS при синхронізації
    created_at      TIMESTAMP DEFAULT NOW(),
    CONSTRAINT no_room_overlap EXCLUDE USING gist (
        room_id WITH =,
        daterange(check_in, check_out, '[)') WITH &&
    ) WHERE (status NOT IN ('cancelled', 'no_show'))
);

Пошук доступних номерів

def search_available_rooms(check_in: date, check_out: date, adults: int, children: int = 0):
    nights = (check_out - check_in).days
    guests = adults + children

    return db.fetchall("""
        SELECT
            rt.*,
            COUNT(r.id) AS available_count,
            rp.price AS nightly_price,
            rp.price * %(nights)s AS total_price,
            rp.is_refundable,
            rp.includes_breakfast,
            rp.min_stay_nights
        FROM room_types rt
        JOIN rooms r ON r.room_type_id = rt.id AND r.is_active = TRUE
        JOIN rate_plans rp ON rp.room_type_id = rt.id
            AND rp.valid_from <= %(check_in)s
            AND rp.valid_until >= %(check_out)s
            AND rp.min_stay_nights <= %(nights)s
        WHERE rt.max_occupancy >= %(guests)s
          AND r.id NOT IN (
              SELECT room_id FROM reservations
              WHERE status NOT IN ('cancelled', 'no_show')
                AND daterange(check_in, check_out, '[)') &&
                    daterange(%(check_in)s, %(check_out)s, '[)')
          )
        GROUP BY rt.id, rp.id
        HAVING COUNT(r.id) > 0
        ORDER BY rp.price ASC
    """, {'check_in': check_in, 'check_out': check_out,
          'nights': nights, 'guests': guests})

Динамічне ціноутворення

Ціна за ніч може змінюватися в залежності від дня тижня, завантаженості, сезону:

def calculate_total_price(room_type_id: int, check_in: date, check_out: date) -> Decimal:
    total = Decimal(0)
    current = check_in
    while current < check_out:
        rate = get_rate_for_date(room_type_id, current)
        if rate is None:
            raise NoRateAvailable(f"No rate for {current}")
        total += rate.price
        current += timedelta(days=1)
    return total

def get_rate_for_date(room_type_id: int, d: date) -> Optional[RatePlan]:
    return db.fetchone("""
        SELECT * FROM rate_plans
        WHERE room_type_id = %s
          AND valid_from <= %s AND valid_until >= %s
        ORDER BY price DESC   -- пріоритет — спеціальний (більш високий) тариф
        LIMIT 1
    """, [room_type_id, d, d])

Призначення конкретного номера

При створенні бронювання в reservations записується room_type_id, але конкретний номер (room_id) призначається при заїзді (або адміністратором заздалегідь):

def assign_room(reservation_id: int) -> Room:
    res = get_reservation(reservation_id)
    room = db.fetchone("""
        SELECT r.* FROM rooms r
        WHERE r.room_type_id = %(type_id)s
          AND r.is_active = TRUE
          AND r.id NOT IN (
              SELECT room_id FROM reservations
              WHERE status NOT IN ('cancelled', 'no_show')
                AND daterange(check_in, check_out, '[)') &&
                    daterange(%(check_in)s, %(check_out)s, '[)')
                AND room_id IS NOT NULL
          )
        LIMIT 1
    """, {'type_id': res.room_type_id, 'check_in': res.check_in, 'check_out': res.check_out})

    if not room:
        raise NoRoomAvailable("No physical room available for this reservation")

    db.execute("UPDATE reservations SET room_id=%s WHERE id=%s", [room.id, reservation_id])
    return room

Інтеграція з Channel Manager / OTA

Для синхронізації з Booking.com, Expedia, Airbnb використовується Channel Manager (TravelLine, Bnovo, Wubook). Стандартний протокол — OTA XML (OpenTravel Alliance) або iCal для простих випадків.

iCal-синхронізація для Airbnb:

def generate_ical_feed(room_id: int) -> str:
    bookings = get_confirmed_bookings(room_id)
    cal = Calendar()
    cal.add('prodid', '-//Hotel Booking//EN')
    cal.add('version', '2.0')

    for b in bookings:
        event = Event()
        event.add('uid', f"booking-{b.id}@hotel.example.com")
        event.add('dtstart', b.check_in)
        event.add('dtend', b.check_out)
        event.add('summary', 'BLOCKED')
        cal.add_component(event)

    return cal.to_ical().decode('utf-8')

Скасування та повернення

def cancel_reservation(reservation_id: int, initiator: str) -> dict:
    res = get_reservation(reservation_id)
    hours_to_arrival = (
        datetime.combine(res.check_in, time(14, 0)) - datetime.utcnow()
    ).total_seconds() / 3600

    rate = get_rate_plan(res.rate_plan_id)
    if rate.is_refundable and hours_to_arrival >= rate.cancellation_hours:
        refund_amount = res.total_amount
        refund_type = 'full'
    elif not rate.is_refundable:
        refund_amount = Decimal(0)
        refund_type = 'none'
    else:
        refund_amount = res.total_amount * Decimal('0.5')
        refund_type = 'partial'

    process_refund(res.payment_id, refund_amount)
    update_reservation_status(reservation_id, 'cancelled', initiator)
    send_cancellation_email(res, refund_amount, refund_type)

    return {'refund': refund_amount, 'type': refund_type}

Строки реалізації

Базовий модуль без динамічних тарифів та без PMS-інтеграції — 10–13 робочих днів. Динамічне ціноутворення, призначення номерів, iCal-синхронізація, управління тарифними планами, особистий кабінет гостя — 16–22 робочі дні.