diff --git a/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf b/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf index ac56209..d496ecb 100644 Binary files a/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf and b/AlpineBits-HotelData-2024-10/AlpineBits-HotelData-2024-10.pdf differ diff --git a/logs/wix_test_data_20251006_104642.json b/logs/wix_test_data_20251006_104642.json new file mode 100644 index 0000000..f60d983 --- /dev/null +++ b/logs/wix_test_data_20251006_104642.json @@ -0,0 +1,262 @@ +{ + "timestamp": "2025-10-06T10:46:42.527300", + "client_ip": "127.0.0.1", + "headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7499" + }, + "data": { + "data": { + "formName": "Contact us", + "submissions": [ + { + "label": "Angebot auswählen", + "value": "Zimmer: Doppelzimmer" + }, + { + "label": "Anreisedatum", + "value": "2025-12-21" + }, + { + "label": "Abreisedatum", + "value": "2025-10-28" + }, + { + "label": "Anzahl Erwachsene", + "value": "2" + }, + { + "label": "Anzahl Kinder", + "value": "0" + }, + { + "label": "Anrede", + "value": "Herr" + }, + { + "label": "Vorname", + "value": "Ernst-Dieter" + }, + { + "label": "Nachname", + "value": "Koepper" + }, + { + "label": "Email", + "value": "koepper-ed@t-online.de" + }, + { + "label": "Phone", + "value": "+49 175 8555456" + }, + { + "label": "Message", + "value": "Guten Morgen,\nwir sind nicht gebau an die Reisedaten gebunden: Anreise ist möglich ab 20. Dezember, Aufenthalt mindestens eine Woche, gern auch 8 oder 9 Tage. Natürlich mit Halbpension. Mit freundlichem Gruß D. Köpper" + }, + { + "label": "Einwilligung Marketing", + "value": "Angekreuzt" + }, + { + "label": "utm_Source", + "value": "" + }, + { + "label": "utm_Medium", + "value": "" + }, + { + "label": "utm_Campaign", + "value": "" + }, + { + "label": "utm_Term", + "value": "" + }, + { + "label": "utm_Content", + "value": "" + }, + { + "label": "utm_term_id", + "value": "" + }, + { + "label": "utm_content_id", + "value": "" + }, + { + "label": "gad_source", + "value": "5" + }, + { + "label": "gad_campaignid", + "value": "23065043477" + }, + { + "label": "gbraid", + "value": "" + }, + { + "label": "gclid", + "value": "EAIaIQobChMI-d7Bn_-OkAMVuZJQBh09uD0vEAAYASAAEgKR8_D_BwE" + }, + { + "label": "fbclid", + "value": "" + }, + { + "label": "hotelid", + "value": "12345" + }, + { + "label": "hotelname", + "value": "Bemelmans Post" + } + ], + "field:date_picker_7e65": "2025-10-28", + "field:number_7cf5": "2", + "field:utm_source": "", + "submissionTime": "2025-10-06T07:05:34.001Z", + "field:gad_source": "5", + "field:form_field_5a7b": "Angekreuzt", + "field:gad_campaignid": "23065043477", + "field:utm_medium": "", + "field:utm_term_id": "", + "context": { + "metaSiteId": "1dea821c-8168-4736-96e4-4b92e8b364cf", + "activationId": "fd8e9c90-0335-4fd2-976d-985f065f3f80" + }, + "field:email_5139": "koepper-ed@t-online.de", + "field:phone_4c77": "+49 175 8555456", + "_context": { + "activation": { + "id": "fd8e9c90-0335-4fd2-976d-985f065f3f80" + }, + "configuration": { + "id": "a976f18c-fa86-495d-be1e-676df188eeae" + }, + "app": { + "id": "225dd912-7dea-4738-8688-4b8c6955ffc2" + }, + "action": { + "id": "152db4d7-5263-40c4-be2b-1c81476318b7" + }, + "trigger": { + "key": "wix_form_app-form_submitted" + } + }, + "field:gclid": "EAIaIQobChMI-d7Bn_-OkAMVuZJQBh09uD0vEAAYASAAEgKR8_D_BwE", + "formFieldMask": [ + "field:", + "field:", + "field:angebot_auswaehlen", + "field:date_picker_a7c8", + "field:date_picker_7e65", + "field:", + "field:number_7cf5", + "field:anzahl_kinder", + "field:alter_kind_3", + "field:alter_kind_25", + "field:alter_kind_4", + "field:alter_kind_5", + "field:alter_kind_6", + "field:alter_kind_7", + "field:alter_kind_8", + "field:alter_kind_9", + "field:alter_kind_10", + "field:alter_kind_11", + "field:", + "field:anrede", + "field:first_name_abae", + "field:last_name_d97c", + "field:email_5139", + "field:phone_4c77", + "field:long_answer_3524", + "field:form_field_5a7b", + "field:", + "field:utm_source", + "field:utm_medium", + "field:utm_campaign", + "field:utm_term", + "field:utm_content", + "field:utm_term_id", + "field:utm_content_id", + "field:gad_source", + "field:gad_campaignid", + "field:gbraid", + "field:gclid", + "field:fbclid", + "field:hotelid", + "field:hotelname", + "field:", + "metaSiteId" + ], + "contact": { + "name": { + "first": "Ernst-Dieter", + "last": "Koepper" + }, + "email": "koepper-ed@t-online.de", + "locale": "de-de", + "phones": [ + { + "tag": "UNTAGGED", + "formattedPhone": "+49 175 8555456", + "id": "530a3bf4-6dbe-4611-8963-a50df805785d", + "countryCode": "DE", + "e164Phone": "+491758555456", + "primary": true, + "phone": "175 8555456" + } + ], + "contactId": "13659da8-4035-47fe-a66b-6ce461ad290f", + "emails": [ + { + "id": "e1d2168e-ca3c-4844-8f93-f2e1b0ae70e3", + "tag": "UNTAGGED", + "email": "koepper-ed@t-online.de", + "primary": true + } + ], + "updatedDate": "2025-10-06T07:05:35.675Z", + "phone": "+491758555456", + "createdDate": "2025-10-06T07:05:35.675Z" + }, + "submissionId": "86d247dc-9d5a-4eb7-87a7-677bf64645ad", + "field:anzahl_kinder": "0", + "field:first_name_abae": "Ernst-Dieter", + "field:utm_content_id": "", + "field:utm_campaign": "", + "field:utm_term": "", + "contactId": "13659da8-4035-47fe-a66b-6ce461ad290f", + "field:date_picker_a7c8": "2025-12-21", + "field:hotelname": "Bemelmans Post", + "field:angebot_auswaehlen": "Zimmer: Doppelzimmer", + "field:utm_content": "", + "field:last_name_d97c": "Koepper", + "field:hotelid": "12345", + "submissionsLink": "https://manage.wix.app/forms/submissions/1dea821c-8168-4736-96e4-4b92e8b364cf/e084006b-ae83-4e4d-b2f5-074118cdb3b1?d=https%3A%2F%2Fmanage.wix.com%2Fdashboard%2F1dea821c-8168-4736-96e4-4b92e8b364cf%2Fwix-forms%2Fform%2Fe084006b-ae83-4e4d-b2f5-074118cdb3b1%2Fsubmissions&s=true", + "field:gbraid": "", + "field:fbclid": "", + "submissionPdf": { + "fileName": "86d247dc-9d5a-4eb7-87a7-677bf64645ad.pdf", + "downloadUrl": "https://manage.wix.com/_api/form-submission-service/v4/submissions/86d247dc-9d5a-4eb7-87a7-677bf64645ad/download?accessToken=JWS.eyJraWQiOiJWLVNuLWhwZSIsImFsZyI6IkhTMjU2In0.eyJkYXRhIjoie1wibWV0YVNpdGVJZFwiOlwiMWRlYTgyMWMtODE2OC00NzM2LTk2ZTQtNGI5MmU4YjM2NGNmXCJ9IiwiaWF0IjoxNzU5NzM0MzM1LCJleHAiOjE3NTk3MzQ5MzV9.9koy-O_ptm0dRspjh01Yefkt2rCHiUlRCFtE_S3auYw" + }, + "field:anrede": "Herr", + "field:long_answer_3524": "Guten Morgen,\nwir sind nicht gebau an die Reisedaten gebunden: Anreise ist möglich ab 20. Dezember, Aufenthalt mindestens eine Woche, gern auch 8 oder 9 Tage. Natürlich mit Halbpension. Mit freundlichem Gruß D. Köpper", + "formId": "e084006b-ae83-4e4d-b2f5-074118cdb3b1" + } + }, + "origin_header": null, + "all_headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7499" + } +} \ No newline at end of file diff --git a/logs/wix_test_data_20251006_105732.json b/logs/wix_test_data_20251006_105732.json new file mode 100644 index 0000000..83adc96 --- /dev/null +++ b/logs/wix_test_data_20251006_105732.json @@ -0,0 +1,262 @@ +{ + "timestamp": "2025-10-06T10:57:32.973217", + "client_ip": "127.0.0.1", + "headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7499" + }, + "data": { + "data": { + "formName": "Contact us", + "submissions": [ + { + "label": "Angebot auswählen", + "value": "Zimmer: Doppelzimmer" + }, + { + "label": "Anreisedatum", + "value": "2025-12-21" + }, + { + "label": "Abreisedatum", + "value": "2025-10-28" + }, + { + "label": "Anzahl Erwachsene", + "value": "2" + }, + { + "label": "Anzahl Kinder", + "value": "0" + }, + { + "label": "Anrede", + "value": "Herr" + }, + { + "label": "Vorname", + "value": "Ernst-Dieter" + }, + { + "label": "Nachname", + "value": "Koepper" + }, + { + "label": "Email", + "value": "koepper-ed@t-online.de" + }, + { + "label": "Phone", + "value": "+49 175 8555456" + }, + { + "label": "Message", + "value": "Guten Morgen,\nwir sind nicht gebau an die Reisedaten gebunden: Anreise ist möglich ab 20. Dezember, Aufenthalt mindestens eine Woche, gern auch 8 oder 9 Tage. Natürlich mit Halbpension. Mit freundlichem Gruß D. Köpper" + }, + { + "label": "Einwilligung Marketing", + "value": "Angekreuzt" + }, + { + "label": "utm_Source", + "value": "" + }, + { + "label": "utm_Medium", + "value": "" + }, + { + "label": "utm_Campaign", + "value": "" + }, + { + "label": "utm_Term", + "value": "" + }, + { + "label": "utm_Content", + "value": "" + }, + { + "label": "utm_term_id", + "value": "" + }, + { + "label": "utm_content_id", + "value": "" + }, + { + "label": "gad_source", + "value": "5" + }, + { + "label": "gad_campaignid", + "value": "23065043477" + }, + { + "label": "gbraid", + "value": "" + }, + { + "label": "gclid", + "value": "EAIaIQobChMI-d7Bn_-OkAMVuZJQBh09uD0vEAAYASAAEgKR8_D_BwE" + }, + { + "label": "fbclid", + "value": "" + }, + { + "label": "hotelid", + "value": "12345" + }, + { + "label": "hotelname", + "value": "Bemelmans Post" + } + ], + "field:date_picker_7e65": "2025-10-28", + "field:number_7cf5": "2", + "field:utm_source": "", + "submissionTime": "2025-10-06T07:05:34.001Z", + "field:gad_source": "5", + "field:form_field_5a7b": "Angekreuzt", + "field:gad_campaignid": "23065043477", + "field:utm_medium": "", + "field:utm_term_id": "", + "context": { + "metaSiteId": "1dea821c-8168-4736-96e4-4b92e8b364cf", + "activationId": "fd8e9c90-0335-4fd2-976d-985f065f3f80" + }, + "field:email_5139": "koepper-ed@t-online.de", + "field:phone_4c77": "+49 175 8555456", + "_context": { + "activation": { + "id": "fd8e9c90-0335-4fd2-976d-985f065f3f80" + }, + "configuration": { + "id": "a976f18c-fa86-495d-be1e-676df188eeae" + }, + "app": { + "id": "225dd912-7dea-4738-8688-4b8c6955ffc2" + }, + "action": { + "id": "152db4d7-5263-40c4-be2b-1c81476318b7" + }, + "trigger": { + "key": "wix_form_app-form_submitted" + } + }, + "field:gclid": "EAIaIQobChMI-d7Bn_-OkAMVuZJQBh09uD0vEAAYASAAEgKR8_D_BwE", + "formFieldMask": [ + "field:", + "field:", + "field:angebot_auswaehlen", + "field:date_picker_a7c8", + "field:date_picker_7e65", + "field:", + "field:number_7cf5", + "field:anzahl_kinder", + "field:alter_kind_3", + "field:alter_kind_25", + "field:alter_kind_4", + "field:alter_kind_5", + "field:alter_kind_6", + "field:alter_kind_7", + "field:alter_kind_8", + "field:alter_kind_9", + "field:alter_kind_10", + "field:alter_kind_11", + "field:", + "field:anrede", + "field:first_name_abae", + "field:last_name_d97c", + "field:email_5139", + "field:phone_4c77", + "field:long_answer_3524", + "field:form_field_5a7b", + "field:", + "field:utm_source", + "field:utm_medium", + "field:utm_campaign", + "field:utm_term", + "field:utm_content", + "field:utm_term_id", + "field:utm_content_id", + "field:gad_source", + "field:gad_campaignid", + "field:gbraid", + "field:gclid", + "field:fbclid", + "field:hotelid", + "field:hotelname", + "field:", + "metaSiteId" + ], + "contact": { + "name": { + "first": "Ernst-Dieter", + "last": "Koepper" + }, + "email": "koepper-ed@t-online.de", + "locale": "de-de", + "phones": [ + { + "tag": "UNTAGGED", + "formattedPhone": "+49 175 8555456", + "id": "530a3bf4-6dbe-4611-8963-a50df805785d", + "countryCode": "DE", + "e164Phone": "+491758555456", + "primary": true, + "phone": "175 8555456" + } + ], + "contactId": "13659da8-4035-47fe-a66b-6ce461ad290f", + "emails": [ + { + "id": "e1d2168e-ca3c-4844-8f93-f2e1b0ae70e3", + "tag": "UNTAGGED", + "email": "koepper-ed@t-online.de", + "primary": true + } + ], + "updatedDate": "2025-10-06T07:05:35.675Z", + "phone": "+491758555456", + "createdDate": "2025-10-06T07:05:35.675Z" + }, + "submissionId": "86d247dc-9d5a-4eb7-87a7-677bf64645ad", + "field:anzahl_kinder": "0", + "field:first_name_abae": "Ernst-Dieter", + "field:utm_content_id": "", + "field:utm_campaign": "", + "field:utm_term": "", + "contactId": "13659da8-4035-47fe-a66b-6ce461ad290f", + "field:date_picker_a7c8": "2025-12-21", + "field:hotelname": "Bemelmans Post", + "field:angebot_auswaehlen": "Zimmer: Doppelzimmer", + "field:utm_content": "", + "field:last_name_d97c": "Koepper", + "field:hotelid": "12345", + "submissionsLink": "https://manage.wix.app/forms/submissions/1dea821c-8168-4736-96e4-4b92e8b364cf/e084006b-ae83-4e4d-b2f5-074118cdb3b1?d=https%3A%2F%2Fmanage.wix.com%2Fdashboard%2F1dea821c-8168-4736-96e4-4b92e8b364cf%2Fwix-forms%2Fform%2Fe084006b-ae83-4e4d-b2f5-074118cdb3b1%2Fsubmissions&s=true", + "field:gbraid": "", + "field:fbclid": "", + "submissionPdf": { + "fileName": "86d247dc-9d5a-4eb7-87a7-677bf64645ad.pdf", + "downloadUrl": "https://manage.wix.com/_api/form-submission-service/v4/submissions/86d247dc-9d5a-4eb7-87a7-677bf64645ad/download?accessToken=JWS.eyJraWQiOiJWLVNuLWhwZSIsImFsZyI6IkhTMjU2In0.eyJkYXRhIjoie1wibWV0YVNpdGVJZFwiOlwiMWRlYTgyMWMtODE2OC00NzM2LTk2ZTQtNGI5MmU4YjM2NGNmXCJ9IiwiaWF0IjoxNzU5NzM0MzM1LCJleHAiOjE3NTk3MzQ5MzV9.9koy-O_ptm0dRspjh01Yefkt2rCHiUlRCFtE_S3auYw" + }, + "field:anrede": "Herr", + "field:long_answer_3524": "Guten Morgen,\nwir sind nicht gebau an die Reisedaten gebunden: Anreise ist möglich ab 20. Dezember, Aufenthalt mindestens eine Woche, gern auch 8 oder 9 Tage. Natürlich mit Halbpension. Mit freundlichem Gruß D. Köpper", + "formId": "e084006b-ae83-4e4d-b2f5-074118cdb3b1" + } + }, + "origin_header": null, + "all_headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7499" + } +} \ No newline at end of file diff --git a/src/alpine_bits_python/alpine_bits_helpers.py b/src/alpine_bits_python/alpine_bits_helpers.py index 8308116..037dfdf 100644 --- a/src/alpine_bits_python/alpine_bits_helpers.py +++ b/src/alpine_bits_python/alpine_bits_helpers.py @@ -51,6 +51,18 @@ RetrieveGuestCounts = ( OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.GuestCounts ) +NotifUniqueId = (OtaHotelResNotifRq.HotelReservations.HotelReservation.UniqueId) +RetrieveUniqueId = (OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId) + +NotifTimeSpan = (OtaHotelResNotifRq.HotelReservations.HotelReservation.RoomStays.RoomStay.TimeSpan) +RetrieveTimeSpan = (OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.TimeSpan) + +NotifRoomStays = (OtaHotelResNotifRq.HotelReservations.HotelReservation.RoomStays) +RetrieveRoomStays = (OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays) + +NotifHotelReservation = (OtaHotelResNotifRq.HotelReservations.HotelReservation) +RetrieveHotelReservation = (OtaResRetrieveRs.ReservationsList.HotelReservation) + # phonetechtype enum 1,3,5 voice, fax, mobile class PhoneTechType(Enum): @@ -104,31 +116,25 @@ class CustomerData: class GuestCountsFactory: + """Factory class to create GuestCounts instances for both OtaHotelResNotifRq and OtaResRetrieveRs.""" @staticmethod - def create_notif_guest_counts( + def create_guest_counts( adults: int, kids: Optional[list[int]] = None - ) -> 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"