From 68e49aab3415a98c42d3028849503de833db0342 Mon Sep 17 00:00:00 2001 From: Jonas Linter Date: Mon, 6 Oct 2025 10:58:05 +0200 Subject: [PATCH] Made helper methods more userfriendly. Guest requests still works as expected --- .../AlpineBits-HotelData-2024-10.pdf | Bin 1042895 -> 1061334 bytes logs/wix_test_data_20251006_104642.json | 262 ++++++++++ logs/wix_test_data_20251006_105732.json | 262 ++++++++++ src/alpine_bits_python/alpine_bits_helpers.py | 481 ++++++++++-------- src/alpine_bits_python/alpinebits_server.py | 4 +- 5 files changed, 802 insertions(+), 207 deletions(-) create mode 100644 logs/wix_test_data_20251006_104642.json create mode 100644 logs/wix_test_data_20251006_105732.json diff --git a/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf b/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf index ac562095f5937035c851e1bb02efa73ac5101eaa..d496ecb40c5117154af45ca5c8179780184cdd67 100644 GIT binary patch delta 18077 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 R3;rpFmVNh~GP9%Q{{bZGkG%i@ delta 39 vcmcci*x~$T`-T?A7N!>F7M2#)7Pc1l7LFFq7OocV7M>Q~7QQX~=U)N NotifGuestCounts: + , message_type: OtaMessageType = OtaMessageType.RETRIEVE) -> NotifGuestCounts: """ - Create a GuestCounts object for OtaHotelResNotifRq. + Create a GuestCounts object for OtaHotelResNotifRq or OtaResRetrieveRs. :param adults: Number of adults :param kids: List of ages for each kid (optional) :return: GuestCounts instance """ - return GuestCountsFactory._create_guest_counts(adults, kids, NotifGuestCounts) + if message_type == OtaMessageType.RETRIEVE: + return GuestCountsFactory._create_guest_counts(adults, kids, RetrieveGuestCounts) + elif message_type == OtaMessageType.NOTIF: + return GuestCountsFactory._create_guest_counts(adults, kids, NotifGuestCounts) + else: + raise ValueError(f"Unsupported message type: {message_type}") + - @staticmethod - def create_retrieve_guest_counts( - adults: int, kids: Optional[list[int]] = None - ) -> RetrieveGuestCounts: - """ - Create a GuestCounts object for OtaResRetrieveRs. - :param adults: Number of adults - :param kids: List of ages for each kid (optional) - :return: GuestCounts instance - """ - return GuestCountsFactory._create_guest_counts( - adults, kids, RetrieveGuestCounts - ) @staticmethod def _create_guest_counts( @@ -567,6 +573,9 @@ class ResGuestFactory: return CustomerFactory.from_notif_customer(customer) else: return CustomerFactory.from_retrieve_customer(customer) + + + class AlpineBitsFactory: @@ -669,9 +678,217 @@ class AlpineBitsFactory: else: raise ValueError(f"Unsupported object type: {type(obj)}") + +def create_res_retrieve_response(list: list[Tuple[Reservation, Customer]]): + + """Create RetrievedReservation XML from database entries.""" + + return _create_xml_from_db(list, OtaMessageType.RETRIEVE) + +def create_res_notif_push_message(list: Tuple[Reservation, Customer]): + """Create Reservation Notification XML from database entries.""" + + return _create_xml_from_db(list, OtaMessageType.NOTIF) -def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): +def _process_single_reservation(reservation: Reservation, customer: Customer, message_type: OtaMessageType): + + phone_numbers = ( + [(customer.phone, PhoneTechType.MOBILE)] + if customer.phone is not None + else [] + ) + + customer_data = CustomerData( + given_name=customer.given_name, + surname=customer.surname, + name_prefix=customer.name_prefix, + name_title=customer.name_title, + phone_numbers=phone_numbers, + email_address=customer.email_address, + email_newsletter=customer.email_newsletter, + address_line=customer.address_line, + city_name=customer.city_name, + postal_code=customer.postal_code, + country_code=customer.country_code, + address_catalog=customer.address_catalog, + gender=customer.gender, + birth_date=customer.birth_date, + language=customer.language, + ) + alpine_bits_factory = AlpineBitsFactory() + res_guests = alpine_bits_factory.create_res_guests( + customer_data, message_type + ) + + # Guest counts + children_ages = [int(a) for a in reservation.children_ages.split(",") if a] + guest_counts = GuestCountsFactory.create_guest_counts( + reservation.num_adults, children_ages, message_type + ) + + unique_id_string = reservation.unique_id + + + + if message_type == OtaMessageType.NOTIF: + UniqueId = NotifUniqueId + RoomStays = NotifRoomStays + HotelReservation = NotifHotelReservation + elif message_type == OtaMessageType.RETRIEVE: + UniqueId = RetrieveUniqueId + RoomStays = RetrieveRoomStays + HotelReservation = RetrieveHotelReservation + else: + raise ValueError(f"Unsupported message type: {message_type}") + + # UniqueID + unique_id = UniqueId( + type_value=UniqueIdType2.VALUE_14, id=unique_id_string + ) + + + + # TimeSpan + time_span = RoomStays.RoomStay.TimeSpan( + start=reservation.start_date.isoformat() + if reservation.start_date + else None, + end=reservation.end_date.isoformat() if reservation.end_date else None, + ) + room_stay = ( + RoomStays.RoomStay( + time_span=time_span, + guest_counts=guest_counts, + ) + ) + room_stays = RoomStays( + room_stay=[room_stay], + ) + + res_id_source = "website" + + if reservation.fbclid != "": + klick_id = reservation.fbclid + res_id_source = "meta" + elif reservation.gclid != "": + klick_id = reservation.gclid + res_id_source = "google" + + + # explicitly set klick_id to None otherwise an empty string will be sent + if klick_id in (None, "", "None"): + klick_id = None + else: # extract string from Column object + klick_id = str(klick_id) + + hotel_res_id_data = HotelReservationIdData( + res_id_type="13", + res_id_value=klick_id, + res_id_source=res_id_source, + res_id_source_context="99tales", + ) + + # explicitly set klick_id to None otherwise an empty string will be sent + if klick_id in (None, "", "None"): + klick_id = None + else: # extract string from Column object + klick_id = str(klick_id) + + hotel_res_id_data = HotelReservationIdData( + res_id_type="13", + res_id_value=klick_id, + res_id_source=None, + res_id_source_context="99tales", + ) + + hotel_res_id = alpine_bits_factory.create( + hotel_res_id_data, message_type + ) + hotel_res_ids = HotelReservation.ResGlobalInfo.HotelReservationIds( + hotel_reservation_id=[hotel_res_id] + ) + + if reservation.hotel_code is None: + raise ValueError("Reservation hotel_code is None") + else: + hotel_code = str(reservation.hotel_code) + if reservation.hotel_name is None: + hotel_name = None + else: + hotel_name = str(reservation.hotel_name) + + basic_property_info = HotelReservation.ResGlobalInfo.BasicPropertyInfo( + hotel_code=hotel_code, + hotel_name=hotel_name, + ) + # Comments + + offer_comment = None + if reservation.offer is not None: + offer_comment = CommentData( + name=CommentName2.ADDITIONAL_INFO, + text="Angebot/Offerta", + list_items=[ + CommentListItemData( + value=reservation.offer, + language=customer.language, + list_item="1", + ) + ], + ) + comment = None + if reservation.user_comment: + comment = CommentData( + name=CommentName2.CUSTOMER_COMMENT, + text=reservation.user_comment, + list_items=[ + CommentListItemData( + value="Landing page comment", + language=customer.language, + list_item="1", + ) + ], + ) + comments = [offer_comment, comment] + + # filter out None comments + comments = [c for c in comments if c is not None] + + comments_xml = None + if comments: + for c in comments: + _LOGGER.info( + f"Creating comment: name={c.name}, text={c.text}, list_items={len(c.list_items)}" + ) + + comments_data = CommentsData(comments=comments) + comments_xml = alpine_bits_factory.create( + comments_data, OtaMessageType.RETRIEVE + ) + + res_global_info = ( + HotelReservation.ResGlobalInfo( + hotel_reservation_ids=hotel_res_ids, + basic_property_info=basic_property_info, + comments=comments_xml, + ) + ) + + hotel_reservation = HotelReservation( + create_date_time=datetime.now(timezone.utc).isoformat(), + res_status=HotelReservationResStatus.REQUESTED, + room_stay_reservation="true", + unique_id=unique_id, + room_stays=room_stays, + res_guests=res_guests, + res_global_info=res_global_info, + ) + + return hotel_reservation + + +def _create_xml_from_db(entries: list[Tuple[Reservation, Customer]] | Tuple[Reservation, Customer], type: OtaMessageType): """Create RetrievedReservation XML from database entries. list of pairs (Reservation, Customer) @@ -679,187 +896,20 @@ def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): reservations_list = [] - for reservation, customer in list: + # if entries isn't a list wrap the element in a list + + if not isinstance(entries, list): + entries = [entries] + + + for reservation, customer in entries: _LOGGER.info( f"Creating XML for reservation {reservation.unique_id} and customer {customer.given_name}" ) try: - phone_numbers = ( - [(customer.phone, PhoneTechType.MOBILE)] - if customer.phone is not None - else [] - ) - customer_data = CustomerData( - given_name=customer.given_name, - surname=customer.surname, - name_prefix=customer.name_prefix, - name_title=customer.name_title, - phone_numbers=phone_numbers, - email_address=customer.email_address, - email_newsletter=customer.email_newsletter, - address_line=customer.address_line, - city_name=customer.city_name, - postal_code=customer.postal_code, - country_code=customer.country_code, - address_catalog=customer.address_catalog, - gender=customer.gender, - birth_date=customer.birth_date, - language=customer.language, - ) - alpine_bits_factory = AlpineBitsFactory() - res_guests = alpine_bits_factory.create_res_guests( - customer_data, OtaMessageType.RETRIEVE - ) - # Guest counts - children_ages = [int(a) for a in reservation.children_ages.split(",") if a] - guest_counts = GuestCountsFactory.create_retrieve_guest_counts( - reservation.num_adults, children_ages - ) - - unique_id_string = reservation.unique_id - - # UniqueID - unique_id = OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId( - type_value=UniqueIdType2.VALUE_14, id=unique_id_string - ) - - # TimeSpan - time_span = OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.TimeSpan( - start=reservation.start_date.isoformat() - if reservation.start_date - else None, - end=reservation.end_date.isoformat() if reservation.end_date else None, - ) - room_stay = ( - OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay( - time_span=time_span, - guest_counts=guest_counts, - ) - ) - room_stays = OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays( - room_stay=[room_stay], - ) - - res_id_source = "website" - - if reservation.fbclid != "": - klick_id = reservation.fbclid - res_id_source = "meta" - elif reservation.gclid != "": - klick_id = reservation.gclid - res_id_source = "google" - - - # explicitly set klick_id to None otherwise an empty string will be sent - if klick_id in (None, "", "None"): - klick_id = None - else: # extract string from Column object - klick_id = str(klick_id) - - hotel_res_id_data = HotelReservationIdData( - res_id_type="13", - res_id_value=klick_id, - res_id_source=res_id_source, - res_id_source_context="99tales", - ) - - # explicitly set klick_id to None otherwise an empty string will be sent - if klick_id in (None, "", "None"): - klick_id = None - else: # extract string from Column object - klick_id = str(klick_id) - - hotel_res_id_data = HotelReservationIdData( - res_id_type="13", - res_id_value=klick_id, - res_id_source=None, - res_id_source_context="99tales", - ) - - hotel_res_id = alpine_bits_factory.create( - hotel_res_id_data, OtaMessageType.RETRIEVE - ) - hotel_res_ids = OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.HotelReservationIds( - hotel_reservation_id=[hotel_res_id] - ) - - if reservation.hotel_code is None: - raise ValueError("Reservation hotel_code is None") - else: - hotel_code = str(reservation.hotel_code) - if reservation.hotel_name is None: - hotel_name = None - else: - hotel_name = str(reservation.hotel_name) - - basic_property_info = OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.BasicPropertyInfo( - hotel_code=hotel_code, - hotel_name=hotel_name, - ) - # Comments - - offer_comment = None - if reservation.offer is not None: - offer_comment = CommentData( - name=CommentName2.ADDITIONAL_INFO, - text="Angebot/Offerta", - list_items=[ - CommentListItemData( - value=reservation.offer, - language=customer.language, - list_item="1", - ) - ], - ) - comment = None - if reservation.user_comment: - comment = CommentData( - name=CommentName2.CUSTOMER_COMMENT, - text=reservation.user_comment, - list_items=[ - CommentListItemData( - value="Landing page comment", - language=customer.language, - list_item="1", - ) - ], - ) - comments = [offer_comment, comment] - - # filter out None comments - comments = [c for c in comments if c is not None] - - comments_xml = None - if comments: - for c in comments: - _LOGGER.info( - f"Creating comment: name={c.name}, text={c.text}, list_items={len(c.list_items)}" - ) - - comments_data = CommentsData(comments=comments) - comments_xml = alpine_bits_factory.create( - comments_data, OtaMessageType.RETRIEVE - ) - - res_global_info = ( - OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo( - hotel_reservation_ids=hotel_res_ids, - basic_property_info=basic_property_info, - comments=comments_xml, - ) - ) - - hotel_reservation = OtaResRetrieveRs.ReservationsList.HotelReservation( - create_date_time=datetime.now(timezone.utc).isoformat(), - res_status=HotelReservationResStatus.REQUESTED, - room_stay_reservation="true", - unique_id=unique_id, - room_stays=room_stays, - res_guests=res_guests, - res_global_info=res_global_info, - ) + hotel_reservation = _process_single_reservation(reservation, customer, type) reservations_list.append(hotel_reservation) @@ -868,21 +918,42 @@ def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): f"Error creating XML for reservation {reservation.unique_id} and customer {customer.given_name}: {e}" ) - retrieved_reservations = OtaResRetrieveRs.ReservationsList( - hotel_reservation=reservations_list - ) + if type == OtaMessageType.NOTIF: + retrieved_reservations = OtaHotelResNotifRq.HotelReservations( + hotel_reservation=reservations_list + ) - ota_res_retrieve_rs = OtaResRetrieveRs( - version="7.000", success="", reservations_list=retrieved_reservations - ) + ota_hotel_res_notif_rq = OtaHotelResNotifRq( + version="7.000", hotel_reservations=retrieved_reservations + ) - try: - ota_res_retrieve_rs.model_validate(ota_res_retrieve_rs.model_dump()) - except Exception as e: - _LOGGER.error(f"Validation error: {e}") - raise + try: + ota_hotel_res_notif_rq.model_validate(ota_hotel_res_notif_rq.model_dump()) + except Exception as e: + _LOGGER.error(f"Validation error: {e}") + raise - return ota_res_retrieve_rs + return ota_hotel_res_notif_rq + elif type == OtaMessageType.RETRIEVE: + + retrieved_reservations = OtaResRetrieveRs.ReservationsList( + hotel_reservation=reservations_list + ) + + ota_res_retrieve_rs = OtaResRetrieveRs( + version="7.000", success="", reservations_list=retrieved_reservations + ) + + try: + ota_res_retrieve_rs.model_validate(ota_res_retrieve_rs.model_dump()) + except Exception as e: + _LOGGER.error(f"Validation error: {e}") + raise + + return ota_res_retrieve_rs + + else: + raise ValueError(f"Unsupported message type: {type}") # Usage examples diff --git a/src/alpine_bits_python/alpinebits_server.py b/src/alpine_bits_python/alpinebits_server.py index e4fcf0d..000cc10 100644 --- a/src/alpine_bits_python/alpinebits_server.py +++ b/src/alpine_bits_python/alpinebits_server.py @@ -18,7 +18,7 @@ from xml.etree import ElementTree as ET from dataclasses import dataclass from enum import Enum, IntEnum -from alpine_bits_python.alpine_bits_helpers import PhoneTechType, create_xml_from_db +from alpine_bits_python.alpine_bits_helpers import PhoneTechType, create_res_retrieve_response from .generated.alpinebits import OtaNotifReportRq, OtaNotifReportRs, OtaPingRq, OtaPingRs, WarningStatus, OtaReadRq @@ -559,7 +559,7 @@ class ReadAction(AlpineBitsAction): f"Reservation: {reservation.id}, Customer: {customer.given_name}" ) - res_retrive_rs = create_xml_from_db(reservation_customer_pairs) + res_retrive_rs = create_res_retrieve_response(reservation_customer_pairs) config = SerializerConfig( pretty_print=True, xml_declaration=True, encoding="UTF-8"