# Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import xmlrpc.client import logging from odoo.addons.component.core import AbstractComponent from odoo.addons.queue_job.exception import RetryableJobError from odoo.tools import ( DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT) from odoo.addons.payment.models.payment_acquirer import _partner_split_name from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo import fields, _ _logger = logging.getLogger(__name__) # GLOBAL VARS DEFAULT_WUBOOK_DATE_FORMAT = "%d/%m/%Y" DEFAULT_WUBOOK_TIME_FORMAT = "%H:%M" DEFAULT_WUBOOK_DATETIME_FORMAT = "%s %s" % (DEFAULT_WUBOOK_DATE_FORMAT, DEFAULT_WUBOOK_TIME_FORMAT) WUBOOK_STATUS_CONFIRMED = 1 WUBOOK_STATUS_WAITING = 2 WUBOOK_STATUS_REFUSED = 3 WUBOOK_STATUS_ACCEPTED = 4 WUBOOK_STATUS_CANCELLED = 5 WUBOOK_STATUS_CANCELLED_PENALTY = 6 WUBOOK_STATUS_GOOD = ( WUBOOK_STATUS_CONFIRMED, WUBOOK_STATUS_WAITING, WUBOOK_STATUS_ACCEPTED, ) WUBOOK_STATUS_BAD = ( WUBOOK_STATUS_REFUSED, WUBOOK_STATUS_CANCELLED, WUBOOK_STATUS_CANCELLED_PENALTY, ) class WuBookLogin(object): def __init__(self, address, user, passwd, lcode, pkey): self.address = address self.user = user self.passwd = passwd self.lcode = lcode self.pkey = pkey def is_valid(self): return self.address and self.user and self.passwd and self.lcode and self.pkey class WuBookServer(object): def __init__(self, login_data): self._server = None self._token = None self._login_data = login_data def __enter__(self): # we do nothing, api is lazy return self def __exit__(self, type, value, traceback): if self._server is not None: self.close() @property def server(self): if not self._login_data.is_valid(): raise ChannelConnectorError("Invalid Channel Parameters!") if self._server is None: try: self._server = xmlrpc.client.ServerProxy(self._login_data.address) res, tok = self._server.acquire_token( self._login_data.user, self._login_data.passwd, self._login_data.pkey) if res == 0: self._token = tok else: self._server = None except Exception: self._server = None raise RetryableJobError("Can't connect with channel!") return self._server @property def session_token(self): return self._token @property def lcode(self): return self._login_data.lcode def close(self): self._server.release_token(self._token) self._token = None self._server = None class HotelChannelInterfaceAdapter(AbstractComponent): _name = 'hotel.channel.adapter' _inherit = ['base.backend.adapter', 'base.hotel.channel.connector'] _usage = 'backend.adapter' def create_room(self, shortcode, name, capacity, price, availability): raise NotImplementedError def modify_room(self, channel_room_id, name, capacity, price, availability, scode): raise NotImplementedError def delete_room(self, channel_room_id): raise NotImplementedError def fetch_rooms(self, channel_room_id=0): raise NotImplementedError def fetch_rooms_values(self, date_from, date_to, rooms=False): raise NotImplementedError def update_availability(self, rooms_avail): raise NotImplementedError def corporate_fetch(self): raise NotImplementedError def create_reservation(self, channel_room_id, customer_name, email, city, phone, address, country_code, checkin, checkout, adults, children, notes=''): raise NotImplementedError def cancel_reservation(self, channel_reservation_id, reason=""): raise NotImplementedError def fetch_new_bookings(self): raise NotImplementedError def fetch_booking(self, channel_reservation_id): raise NotImplementedError def mark_bookings(self, channel_reservation_ids): raise NotImplementedError def create_plan(self, name, daily=1): raise NotImplementedError def delete_plan(self, channel_plan_id): raise NotImplementedError def update_plan_name(self, channel_plan_id, new_name): raise NotImplementedError def update_plan_prices(self, channel_plan_id, date_from, prices): raise NotImplementedError def update_plan_periods(self, channel_plan_id, periods): raise NotImplementedError def get_pricing_plans(self): raise NotImplementedError def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms): raise NotImplementedError def rplan_rplans(self): raise NotImplementedError def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id): raise NotImplementedError def update_rplan_values(self, channel_restriction_plan_id, date_from, values): raise NotImplementedError def create_rplan(self, name, compact=False): raise NotImplementedError def rename_rplan(self, channel_restriction_plan_id, new_name): raise NotImplementedError def delete_rplan(self, channel_restriction_plan_id): raise NotImplementedError def get_channels_info(self): raise NotImplementedError @property def _server(self): try: channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( 'You must provide a channel_api attribute with a ' 'WuBookServer instance to be able to use the ' 'Backend Adapter.' ) return channel_server.server @property def _session_info(self): try: channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( 'You must provide a channel_api attribute with a ' 'WuBookServer instance to be able to use the ' 'Backend Adapter.' ) return (channel_server.session_token, channel_server._login_data.lcode) class WuBookAdapter(AbstractComponent): _name = 'wubook.adapter' _inherit = 'hotel.channel.adapter' # === ROOMS def create_room(self, shortcode, name, capacity, price, availability): rcode, results = self._server.new_room( self._session_info[0], self._session_info[1], 0, name, capacity, price, availability, shortcode[:4], 'nb' # TODO: Complete this part # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: raise ChannelConnectorError("Can't create room in WuBook", { 'message': results, }) return results def modify_room(self, channel_room_id, name, capacity, price, availability, scode): rcode, results = self._server.mod_room( self._session_info[0], self._session_info[1], channel_room_id, name, capacity, price, availability, scode, 'nb' # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: raise ChannelConnectorError("Can't modify room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) return results def delete_room(self, channel_room_id): rcode, results = self._server.del_room( self._session_info[0], self._session_info[1], channel_room_id) if rcode != 0: raise ChannelConnectorError("Can't delete room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) return results def fetch_rooms(self, channel_room_id=0): rcode, results = self._server.fetch_rooms( self._session_info[0], self._session_info[1], channel_room_id) if rcode != 0: raise ChannelConnectorError("Can't fetch room values from WuBook", { 'message': results, 'channel_id': channel_room_id, }) return results def fetch_rooms_values(self, date_from, date_to, rooms=False): rcode, results = self._server.fetch_rooms_values( self._session_info[0], self._session_info[1], fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms) if rcode != 0: raise ChannelConnectorError("Can't fetch rooms values from WuBook", { 'message': results, }) return results def update_availability(self, rooms_avail): rcode, results = self._server.update_sparse_avail( self._session_info[0], self._session_info[1], rooms_avail) if rcode != 0: raise ChannelConnectorError("Can't update rooms availability in WuBook", { 'message': results, }) return results def corporate_fetch(self): rcode, results = self._server.corporate_fetchable_properties(self.TOKEN) if rcode != 0: raise ChannelConnectorError("Can't call 'corporate_fetch' from WuBook", { 'message': results, }) return results # === RESERVATIONS def create_reservation(self, channel_room_id, customer_name, email, city, phone, address, country_code, checkin, checkout, adults, children, notes=''): customer_name = _partner_split_name(customer_name) customer = { 'lname': customer_name[0], 'fname': customer_name[1], 'email': email, 'city': city, 'phone': phone, 'street': address, 'country': country_code, 'arrival_hour': fields.Datetime.from_string(checkin).strftime("%H:%M"), 'notes': notes } rcode, results = self._server.new_reservation( self._session_info[0], self._session_info[1], fields.Date.from_string(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT), {channel_room_id: [adults+children, 'nb']}, customer, adults+children) if rcode != 0: raise ChannelConnectorError("Can't create reservations in wubook", { 'message': results, 'date_from': checkin, 'date_to': checkout, }) return results def cancel_reservation(self, channel_reservation_id, reason=""): rcode, results = self._server.cancel_reservation( self._session_info[0], self._session_info[1], channel_reservation_id, reason) if rcode != 0: raise ChannelConnectorError("Can't cancel reservation in WuBook", { 'message': results, 'channel_reservation_id': channel_reservation_id, }) return results def fetch_new_bookings(self): rcode, results = self._server.fetch_new_bookings( self._session_info[0], self._session_info[1], 1, 0) if rcode != 0: raise ChannelConnectorError("Can't process reservations from wubook", { 'message': results, }) return results def fetch_booking(self, channel_reservation_id): rcode, results = self.backend_adapter.fetch_booking( self._session_info[0], self._session_info[1], channel_reservation_id) if rcode != 0: raise ChannelConnectorError("Can't process reservation from wubook", { 'message': results, }) return results def mark_bookings(self, channel_reservation_ids): init_connection = self._context.get('init_connection', True) if init_connection: if not self.init_connection(): return False rcode, results = self._server.mark_bookings( self._session_info[0], self._session_info[1], channel_reservation_ids) if rcode != 0: raise ChannelConnectorError("Can't mark as readed a reservation in wubook", { 'message': results, 'channel_reservation_ids': str(channel_reservation_ids), }) return results # === PRICE PLANS def create_plan(self, name, daily=1): rcode, results = self._server.add_pricing_plan( self._session_info[0], self._session_info[1], name, daily) if rcode != 0: raise ChannelConnectorError("Can't add pricing plan to wubook", { 'message': results, }) return results def delete_plan(self, channel_plan_id): rcode, results = self._server.del_plan( self._session_info[0], self._session_info[1], channel_plan_id) if rcode != 0: raise ChannelConnectorError("Can't delete pricing plan from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) return results def update_plan_name(self, channel_plan_id, new_name): rcode, results = self._server.update_plan_name( self._session_info[0], self._session_info[1], channel_plan_id, new_name) if rcode != 0: raise ChannelConnectorError("Can't update pricing plan name in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) return results def update_plan_prices(self, channel_plan_id, date_from, prices): rcode, results = self._server.update_plan_prices( self._session_info[0], self._session_info[1], channel_plan_id, fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), prices) if rcode != 0: raise ChannelConnectorError("Can't update pricing plan in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, }) return results def update_plan_periods(self, channel_plan_id, periods): rcode, results = self._server.update_plan_periods( self._session_info[0], self._session_info[1], channel_plan_id, periods) if rcode != 0: raise ChannelConnectorError("Can't update pricing plan period in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) return results def get_pricing_plans(self): rcode, results = self._server.get_pricing_plans( self._session_info[0], self._session_info[1]) if rcode != 0: raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, }) return results def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms): rcode, results = self._server.fetch_plan_prices( self._session_info[0], self._session_info[1], channel_plan_id, fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms or []) if rcode != 0: raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, 'date_to': date_to }) return results # === RESTRICTIONS def rplan_rplans(self): rcode, results = self._server.rplan_rplans( self._session_info[0], self._session_info[1]) if rcode != 0: raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, }) return results def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id): rcode, results = self._server.wired_rplan_get_rplan_values( self._session_info[0], self._session_info[1], fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), channel_restriction_plan_id) if rcode != 0: raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, 'date_to': date_to, }) return results def update_rplan_values(self, channel_restriction_plan_id, date_from, values): rcode, results = self._server.rplan_update_rplan_values( self._session_info[0], self._session_info[1], channel_restriction_plan_id, fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), values) if rcode != 0: raise ChannelConnectorError("Can't update plan restrictions on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, }) return results def create_rplan(self, name, compact=False): rcode, results = self._server.rplan_add_rplan( self._session_info[0], self._session_info[1], name, compact and 1 or 0) if rcode != 0: raise ChannelConnectorError("Can't create plan restriction in wubook", { 'message': results, }) return results def rename_rplan(self, channel_restriction_plan_id, new_name): rcode, results = self._server.rplan_rename_rplan( self._session_info[0], self._session_info[1], channel_restriction_plan_id, new_name) if rcode != 0: raise ChannelConnectorError("Can't rename plan restriction in wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) return results def delete_rplan(self, channel_restriction_plan_id): rcode, results = self._server.rplan_del_rplan( self._session_info[0], self._session_info[1], channel_restriction_plan_id) if rcode != 0: raise ChannelConnectorError("Can't delete plan restriction on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) return results def get_channels_info(self): results = self._server.get_channels_info(self._session_info[0]) if not any(results): raise ChannelConnectorError("Can't import channels info from wubook", { 'message': results, }) return results