פיתוח מערכת הזמנת מלונות לאתר שלך
תארו לעצמכם: אורח מזמין חדר דרך Booking.com, בעוד שהמנהל מוכר אותו במקביל ישירות. התוצאה — הזמנת יתר, לקוח לא מרוצה, ופגיעה במוניטין. אנחנו פותרים בעיה זו עם מערכת שמסנכרנת את כל הערוצים בזמן אמת. הניסיון שלנו: 10+ שנים, יותר מ-40 פרויקטים שבוצעו. אנו מבטיחים אפס הזמנות כפולות וסנכרון נכון עם ערוצים חיצוניים.
הזמנת מלונות היא אחת ממשימות ההזמנה המאתגרות ביותר. אורחים נשארים למספר לילות, המחירים משתנים לפי עונה, וכללי הביטול שונים. המערכת חייבת לטפל בטווחי תאריכים, לא בימים בודדים. בעיות נפוצות כוללות דליפת חדרים בין ערוצי מכירה, חישוב מחיר שגוי כשהתעריפים משתנים, וניהול מורכב של סוגי חדרים שונים. הגישה שלנו פותרת这些问题 עם סכמת מסד נתונים המשתמשת באילוצי הדרה ומערכת תעריפים גמישה.
למה הזמנת מלונות היא מאתגרת
| גורם | מורכבות | הפתרון שלנו |
|---|---|---|
| תעריפים דינמיים | המחיר משתנה מדי יום | טבלת rate_plans עם תקופות ועדיפויות |
| מניעת הזמנות יתר | שני אורחים לא יכולים לתפוס את אותו חדר | אילוץ הדרה daterange עם && |
| אינטגרציית PMS/OTA | פרוטוקולים ופורמטים שונים | מתאמים ל-iCal, OTA XML, REST API |
| הגורם העיקרי להזמנות כפולות | איך אנחנו מבטלים |
|---|---|
| ניהול חדרים ידני | סנכרון אוטומטי דרך Channel Manager |
| עדכוני תפוסה מאוחרים | עדכונים בזמן אמת דרך API |
| מערכות הנהלת חשבונות שונות | מסד נתונים יחיד עם אילוצי הדרה |
למה בדיקת חריגה ברמת מסד הנתונים מהירה יותר מבדיקת קוד?
שימוש באילוץ הדרה מאפשר למסד הנתונים לבדוק חפיפות טווחים בצורה אטומית. זה מבטל מצבי מרוץ ומפחית את העומס על האפליקציה. ב-PostgreSQL, אינדקס GiST על daterange עובד ב-O(log n).איך למנוע הזמנות כפולות
בפועל, אנו משתמשים בשתי רמות הגנה. ראשית, מסד הנתונים — אילוץ הדרה על טבלת ההזמנות מונע רשומות חופפות. שנית, האפליקציה בודקת זמינות באמצעות שאילתה נפרדת לפני יצירת הזמנה. זה מבטל מצבי מרוץ גם תחת עומס גבוה. לדוגמה, אם שני אורחים מזמינים בו-זמנית את החדר האחרון מסוג מסוים, המערכת מעבדת את הבקשות ברצף ורק אישור אחד מונפק.
בפרויקט אחד לרשת מלונות, יישמנו מערכת כזו. בעבר, הם איבדו עד 5% מההזמנות בגלל מכירות כפולות. לאחר יישום הדרה ברמת מסד הנתונים וסנכרון אוטומטי עם Booking.com ו-Airbnb, לא היו הזמנות יתר במשך חודש. זמן העיבוד של כל הזמנה ירד מ-3 דקות ל-30 שניות.
איך אנחנו מיישמים את הפרויקט: תוכנית שלב-אחר-שלב
- ביקורת דרישות ואינטגרציות. רישום רשימת ה-OTA, PMS, סוגי חדרים ותעריפים.
- עיצוב הסכמה וה-API. יצירת מודל נתונים עם אילוצי הדרה ונקודות קצה לפרונטאנד.
- פיתוח מודולי הזמנה ותעריפים. יישום חיפוש, חישוב מחיר, ביטול.
- אינטגרציה עם OTA דרך Channel Manager. הגדרת iCal או OTA XML בהתאם לערוץ.
- בדיקות וניפוי שגיאות. ביצוע בדיקות עומס (עד 1000 בקשות/שנייה) ותרחישי הזמנות כפולות.
- פריסה והדרכת צוות. פריסה על השרת, הדרכת מנהלים.
מודל נתונים
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, 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', payment_status VARCHAR(20) DEFAULT 'unpaid', notes TEXT, source VARCHAR(30) DEFAULT 'website', external_id VARCHAR(100), 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]) אינטגרציה עם Channel Manager / OTA
לסנכרון עם Booking.com, Expedia, Airbnb, אנו משתמשים ב-Channel Manager (TravelLine, Bnovo, Wubook). הפרוטוקול הסטנדרטי הוא OTA XML (OpenTravel Alliance) או iCal למקרים פשוטים. Channel Manager הוא מערכת לניהול ערוצי מכירה. הפתרון שלנו מתמודד עם עד 1000 בקשות בשנייה, מהיר פי שניים מפתרונות WordPress טיפוסיים.
סנכרון 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} מה כלול בעבודה
- עיצוב ארכיטקטורה וסכמת מסד נתונים (PostgreSQL עם אילוצי הדרה).
- יישום מודולים: חיפוש, הזמנה, תמחור, ביטול.
- אינטגרציה עם מערכות חיצוניות (PMS, OTA) דרך iCal או OTA XML.
- כתיבת בדיקות (יחידה, אינטגרציה) ותיעוד.
- הדרכת צוות על המערכת.
- 3 חודשי תמיכה באחריות.
לוח זמנים ליישום
מודול בסיסי ללא תעריפים דינמיים ואינטגרציית PMS — 10–13 ימי עסקים. תמחור דינמי, הקצאת חדרים, סנכרון iCal, ניהול תוכניות תעריפים, חשבון אישי לאורח — 16–22 ימי עסקים.
העריכו את היתרונות של אינטגרציה — צרו קשר לביקורת מקדימה. בקשו ייעוץ — נעריך את הפרויקט שלכם ונציע את הסט האופטימלי.







