From b1e76e44176f9b46bf0a529beb674fc601f309a8 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Thu, 20 Sep 2018 01:59:45 +0200 Subject: [PATCH 1/6] [WIP][IMP] hotel channel connector --- .../components/__init__.py | 2 +- .../components/backend_adapter.py | 3 ++- hotel_channel_connector/components/core.py | 2 +- hotel_channel_connector/models/__init__.py | 3 ++- .../models/hotel_reservation/common.py | 14 +++++++--- .../models/hotel_reservation/importer.py | 27 +++++++++++++++++++ 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/hotel_channel_connector/components/__init__.py b/hotel_channel_connector/components/__init__.py index 0d9ff44a4..45964d6bc 100644 --- a/hotel_channel_connector/components/__init__.py +++ b/hotel_channel_connector/components/__init__.py @@ -4,5 +4,5 @@ from . import core from . import backend_adapter from . import binder -from . import exporter from . import importer +from . import exporter diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index ea5fd6836..c06a33570 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -1,6 +1,7 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import xmlrpclib from odoo import _ from odoo.exceptions import ValidationError from odoo.addons.component.core import AbstractComponent @@ -179,7 +180,7 @@ class HotelChannelInterfaceAdapter(AbstractComponent): @property def _server(self): try: - channel_server = getattr(self.work, 'hotel_channel_server') + channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( 'You must provide a hotel_channel_server attribute with a ' diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py index d47d6b2fc..4590f6a2b 100644 --- a/hotel_channel_connector/components/core.py +++ b/hotel_channel_connector/components/core.py @@ -7,7 +7,7 @@ from odoo import api class BaseHotelChannelConnectorComponent(AbstractComponent): _name = 'base.hotel.channel.connector' _inherit = 'base.connector' - _collection = 'hotel.channel.backend' + _collection = 'channel.backend' @api.model def create_issue(self, section, message, channel_message, channel_object_id=False, diff --git a/hotel_channel_connector/models/__init__.py b/hotel_channel_connector/models/__init__.py index 96726f45d..70cca8a96 100644 --- a/hotel_channel_connector/models/__init__.py +++ b/hotel_channel_connector/models/__init__.py @@ -1,8 +1,9 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import channel_backend from . import channel_binding + +from . import channel_backend from . import res_config from . import hotel_room_type from . import product_pricelist diff --git a/hotel_channel_connector/models/hotel_reservation/common.py b/hotel_channel_connector/models/hotel_reservation/common.py index bf92a9bca..dd8254d48 100644 --- a/hotel_channel_connector/models/hotel_reservation/common.py +++ b/hotel_channel_connector/models/hotel_reservation/common.py @@ -26,7 +26,7 @@ class ChannelHotelReservation(models.Model): required=True, ondelete='cascade') channel_reservation_id = fields.Char("Channel Reservation ID", readonly=True, old_name='wrid') - ota_id = fields.Many2one('channel.ota.info', + ota_id = fields.Many2one('hotel.channel.connector.ota.info', string='Channel OTA ID', readonly=True, old_name='wchannel_id') @@ -53,7 +53,7 @@ class ChannelHotelReservation(models.Model): @api.model def import_reservations(self, backend): with backend.work_on(self._name) as work: - importer = work.component(usage='channel.hotel.reservation.importer') + importer = work.component(usage='hotel.reservation.importer') return importer.fetch_new_bookings() @api.depends('channel_reservation_id', 'ota_id') @@ -271,7 +271,15 @@ class HotelReservation(models.Model): if not self.is_from_ota: return super().on_change_checkin_checkout_product_id() -class ChannelBindingProductListener(Component): +class HotelReservationAdapter(Component): + _name = 'channel.hotel.reservation.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.reservation' + + def fetch_new_bookings(self): + return super(HotelReservationAdapter, self).fetch_new_bookings() + +class ChannelBindingHotelReservationListener(Component): _name = 'channel.binding.hotel.reservation.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.reservation'] diff --git a/hotel_channel_connector/models/hotel_reservation/importer.py b/hotel_channel_connector/models/hotel_reservation/importer.py index e4e673b1d..203fc4d05 100644 --- a/hotel_channel_connector/models/hotel_reservation/importer.py +++ b/hotel_channel_connector/models/hotel_reservation/importer.py @@ -14,3 +14,30 @@ class HotelReservationImporter(Component): _inherit = 'hotel.channel.importer' _apply_on = ['channel.hotel.reservation'] _usage = 'hotel.reservation.importer' + + def fetch_new_bookings(self): + try: + results = self.backend_adapter.fetch_new_bookings() + processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \ + self._generate_reservations(results) + if any(processed_rids): + uniq_rids = list(set(processed_rids)) + rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids) + if rcodeb != 0: + self.create_issue( + 'wubook', + _("Problem trying mark bookings (%s)") % str(processed_rids), + '') + # Update Odoo availability (don't wait for wubook) + # This cause abuse service in first import!! + if checkin_utc_dt and checkout_utc_dt: + self.backend_adapter.fetch_rooms_values( + checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + except ValidationError: + self.create_issue( + 'reservation', + _("Can't process reservations from wubook"), + results) + return False + return True From 1c6afa7f3a7291436602d7195f4ac2bb45e08ca8 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Fri, 21 Sep 2018 02:29:57 +0200 Subject: [PATCH 2/6] [WIP][11.0] hotel channel connector --- .../components/backend_adapter.py | 75 ++++---- hotel_channel_connector/components/core.py | 5 + .../components/importer.py | 162 ++++++++++-------- .../models/hotel_reservation/importer.py | 5 +- .../views/channel_connector_backend_views.xml | 3 +- 5 files changed, 135 insertions(+), 115 deletions(-) diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index c06a33570..e5b64afd7 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -1,9 +1,8 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import xmlrpclib -from odoo import _ -from odoo.exceptions import ValidationError +import xmlrpc.client +import logging from odoo.addons.component.core import AbstractComponent from odoo.addons.queue_job.exception import RetryableJobError from odoo.tools import ( @@ -11,7 +10,9 @@ from odoo.tools import ( DEFAULT_SERVER_DATETIME_FORMAT) from odoo.addons.payment.models.payment_acquirer import _partner_split_name from odoo.addons.hotel import date_utils -from odoo import fields +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" @@ -63,9 +64,11 @@ class WuBookServer(object): @property def server(self): - if self._server is None and self._login_data.is_valid(): + if not self._login_data.is_valid(): + raise ChannelConnectorError("Invalid Channel Parameters!") + if self._server is None: try: - self._server = xmlrpclib.Server(self._login_data.address) + self._server = xmlrpc.client.ServerProxy(self._login_data.address) res, tok = self._server.acquire_token( self._login_data.user, self._login_data.passwd, @@ -183,7 +186,7 @@ class HotelChannelInterfaceAdapter(AbstractComponent): channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( - 'You must provide a hotel_channel_server attribute with a ' + 'You must provide a channel_api attribute with a ' 'WuBookServer instance to be able to use the ' 'Backend Adapter.' ) @@ -192,14 +195,14 @@ class HotelChannelInterfaceAdapter(AbstractComponent): @property def _session_info(self): try: - channel_server = getattr(self.work, 'hotel_channel_server') + channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( - 'You must provide a hotel_channel_server attribute with a ' + '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.lcode) + return (channel_server.session_token, channel_server._login_data.lcode) class WuBookAdapter(AbstractComponent): _name = 'wubook.adapter' @@ -220,7 +223,7 @@ class WuBookAdapter(AbstractComponent): # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: - raise ValidationError(_("Can't create room in WuBook"), { + raise ChannelConnectorError("Can't create room in WuBook", { 'message': results, }) return results @@ -239,7 +242,7 @@ class WuBookAdapter(AbstractComponent): # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: - raise ValidationError(_("Can't modify room in WuBook"), { + raise ChannelConnectorError("Can't modify room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -251,7 +254,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_room_id) if rcode != 0: - raise ValidationError(_("Can't delete room in WuBook"), { + raise ChannelConnectorError("Can't delete room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -263,7 +266,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_room_id) if rcode != 0: - raise ValidationError(_("Can't fetch room values from WuBook"), { + raise ChannelConnectorError("Can't fetch room values from WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -277,7 +280,7 @@ class WuBookAdapter(AbstractComponent): date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms) if rcode != 0: - raise ValidationError(_("Can't fetch rooms values from WuBook"), { + raise ChannelConnectorError("Can't fetch rooms values from WuBook", { 'message': results, }) return results @@ -288,7 +291,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], rooms_avail) if rcode != 0: - raise ValidationError(_("Can't update rooms availability in WuBook"), { + raise ChannelConnectorError("Can't update rooms availability in WuBook", { 'message': results, }) return results @@ -296,7 +299,7 @@ class WuBookAdapter(AbstractComponent): def corporate_fetch(self): rcode, results = self._server.corporate_fetchable_properties(self.TOKEN) if rcode != 0: - raise ValidationError(_("Can't call 'corporate_fetch' from WuBook"), { + raise ChannelConnectorError("Can't call 'corporate_fetch' from WuBook", { 'message': results, }) return results @@ -326,7 +329,7 @@ class WuBookAdapter(AbstractComponent): customer, adults+children) if rcode != 0: - raise ValidationError(_("Can't create reservations in wubook"), { + raise ChannelConnectorError("Can't create reservations in wubook", { 'message': results, 'date_from': checkin, 'date_to': checkout, @@ -340,7 +343,7 @@ class WuBookAdapter(AbstractComponent): channel_reservation_id, reason) if rcode != 0: - raise ValidationError(_("Can't cancel reservation in WuBook"), { + raise ChannelConnectorError("Can't cancel reservation in WuBook", { 'message': results, 'channel_reservation_id': channel_reservation_id, }) @@ -353,7 +356,7 @@ class WuBookAdapter(AbstractComponent): 1, 0) if rcode != 0: - raise ValidationError(_("Can't process reservations from wubook"), { + raise ChannelConnectorError("Can't process reservations from wubook", { 'message': results, }) return results @@ -364,7 +367,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_reservation_id) if rcode != 0: - raise ValidationError(_("Can't process reservation from wubook"), { + raise ChannelConnectorError("Can't process reservation from wubook", { 'message': results, }) return results @@ -379,7 +382,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_reservation_ids) if rcode != 0: - raise ValidationError(_("Can't mark as readed a reservation in wubook"), { + raise ChannelConnectorError("Can't mark as readed a reservation in wubook", { 'message': results, 'channel_reservation_ids': str(channel_reservation_ids), }) @@ -393,7 +396,7 @@ class WuBookAdapter(AbstractComponent): name, daily) if rcode != 0: - raise ValidationError(_("Can't add pricing plan to wubook"), { + raise ChannelConnectorError("Can't add pricing plan to wubook", { 'message': results, }) return results @@ -404,7 +407,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_plan_id) if rcode != 0: - raise ValidationError(_("Can't delete pricing plan from wubook"), { + raise ChannelConnectorError("Can't delete pricing plan from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -417,7 +420,7 @@ class WuBookAdapter(AbstractComponent): channel_plan_id, new_name) if rcode != 0: - raise ValidationError(_("Can't update pricing plan name in wubook"), { + raise ChannelConnectorError("Can't update pricing plan name in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -431,7 +434,7 @@ class WuBookAdapter(AbstractComponent): date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), prices) if rcode != 0: - raise ValidationError(_("Can't update pricing plan in wubook"), { + raise ChannelConnectorError("Can't update pricing plan in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, @@ -445,7 +448,7 @@ class WuBookAdapter(AbstractComponent): channel_plan_id, periods) if rcode != 0: - raise ValidationError(_("Can't update pricing plan period in wubook"), { + raise ChannelConnectorError("Can't update pricing plan period in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -456,7 +459,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1]) if rcode != 0: - raise ValidationError(_("Can't get pricing plans from wubook"), { + raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, }) return results @@ -470,7 +473,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms or []) if rcode != 0: - raise ValidationError(_("Can't get pricing plans from wubook"), { + raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, @@ -484,7 +487,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1]) if rcode != 0: - raise ValidationError(_("Can't fetch restriction plans from wubook"), { + raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, }) return results @@ -497,7 +500,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), channel_restriction_plan_id) if rcode != 0: - raise ValidationError(_("Can't fetch restriction plans from wubook"), { + raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, @@ -513,7 +516,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), values) if rcode != 0: - raise ValidationError(_("Can't update plan restrictions on wubook"), { + raise ChannelConnectorError("Can't update plan restrictions on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, @@ -527,7 +530,7 @@ class WuBookAdapter(AbstractComponent): name, compact and 1 or 0) if rcode != 0: - raise ValidationError(_("Can't create plan restriction in wubook"), { + raise ChannelConnectorError("Can't create plan restriction in wubook", { 'message': results, }) return results @@ -539,7 +542,7 @@ class WuBookAdapter(AbstractComponent): channel_restriction_plan_id, new_name) if rcode != 0: - raise ValidationError(_("Can't rename plan restriction in wubook"), { + raise ChannelConnectorError("Can't rename plan restriction in wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) @@ -551,7 +554,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_restriction_plan_id) if rcode != 0: - raise ValidationError(_("Can't delete plan restriction on wubook"), { + raise ChannelConnectorError("Can't delete plan restriction on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) @@ -560,7 +563,7 @@ class WuBookAdapter(AbstractComponent): def get_channels_info(self): results = self._server.get_channels_info(self._session_info[0]) if not any(results): - raise ValidationError(_("Can't import channels info from wubook"), { + raise ChannelConnectorError("Can't import channels info from wubook", { 'message': results, }) return results diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py index 4590f6a2b..9a1bed05b 100644 --- a/hotel_channel_connector/components/core.py +++ b/hotel_channel_connector/components/core.py @@ -20,3 +20,8 @@ class BaseHotelChannelConnectorComponent(AbstractComponent): 'date_start': dfrom, 'date_end': dto, }) + +class ChannelConnectorError(Exception): + def __init__(self, message, data): + super().__init__(message) + self.data = data diff --git a/hotel_channel_connector/components/importer.py b/hotel_channel_connector/components/importer.py index 13bf9402f..2a7a782bb 100644 --- a/hotel_channel_connector/components/importer.py +++ b/hotel_channel_connector/components/importer.py @@ -7,6 +7,7 @@ import json from datetime import timedelta from odoo.exceptions import ValidationError from odoo.addons.component.core import AbstractComponent, Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel import date_utils from odoo import _ from odoo.tools import ( @@ -130,7 +131,7 @@ class HotelChannelConnectorImporter(AbstractComponent): @api.model def _generate_booking_vals(self, broom, checkin_str, checkout_str, - is_cancellation, wchannel_info, wstatus, crcode, + is_cancellation, channel_info, wstatus, crcode, rcode, room_type, split_booking, dates_checkin, dates_checkout, book): # Generate Reservation Day Lines @@ -154,24 +155,27 @@ class HotelChannelConnectorImporter(AbstractComponent): persons = room_type.wcapacity if 'ancillary' in broom and 'guests' in broom['ancillary']: persons = broom['ancillary']['guests'] + vals = { - 'checkin': checkin_str, - 'checkout': checkout_str, - 'adults': persons, - 'children': book['children'], - 'reservation_line_ids': reservation_line_ids, - 'price_unit': tprice, - 'to_assign': True, - 'wrid': rcode, - 'wchannel_id': wchannel_info and wchannel_info.id, - 'wchannel_reservation_code': crcode, + 'channel_reservation_id': rcode, + 'ota_id': channel_info and channel_info.id, + 'ota_reservation_id': crcode, + 'channel_raw_data': json.dumps(book), 'wstatus': wstatus, - 'to_read': True, - 'state': is_cancellation and 'cancelled' or 'draft', - 'room_type_id': room_type.id, - 'splitted': split_booking, - 'wbook_json': json.dumps(book), - 'wmodified': book['was_modified'] + 'wmodified': book['was_modified'], + 'odoo_id': [0, False, { + 'checkin': checkin_str, + 'checkout': checkout_str, + 'adults': persons, + 'children': book['children'], + 'reservation_line_ids': reservation_line_ids, + 'price_unit': tprice, + 'to_assign': True, + 'to_read': True, + 'state': is_cancellation and 'cancelled' or 'draft', + 'room_type_id': room_type.id, + 'splitted': split_booking, + }], } _logger.info("===== CONTRUCT RESERV") _logger.info(vals) @@ -210,8 +214,10 @@ class HotelChannelConnectorImporter(AbstractComponent): tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') res_partner_obj = self.env['res.partner'] + channel_reserv_obj = self.env['channel.hotel.reservation'] hotel_reserv_obj = self.env['hotel.reservation'] hotel_folio_obj = self.env['hotel.folio'] + channel_room_type_obj = self.env['channel.hotel.room.type'] hotel_room_type_obj = self.env['hotel.room.type'] # Space for store some data for construct folios processed_rids = [] @@ -231,10 +237,10 @@ class HotelChannelConnectorImporter(AbstractComponent): # (for example set a invalid new reservation and receive in # the same transaction an cancellation) if crcode in failed_reservations: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Can't process a reservation that previusly failed!", - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) continue # Get dates for the reservation (GMT->UTC) @@ -260,34 +266,37 @@ class HotelChannelConnectorImporter(AbstractComponent): # Search Folio. If exists. folio_id = False if crcode != 'undefined': - reserv_folio = hotel_reserv_obj.search([ - ('wchannel_reservation_code', '=', crcode) + reserv_folio = channel_reserv_obj.search([ + ('ota_reservation_id', '=', crcode) ], limit=1) if reserv_folio: - folio_id = reserv_folio.folio_id + folio_id = reserv_folio.odoo_id.folio_id else: - reserv_folio = hotel_reserv_obj.search([ - ('wrid', '=', rcode) + reserv_folio = channel_reserv_obj.search([ + ('channel_reservation_id', '=', rcode) ], limit=1) if reserv_folio: - folio_id = reserv_folio.folio_id + folio_id = reserv_folio.odoo_id.folio_id # Need update reservations? - sreservs = hotel_reserv_obj.search([('wrid', '=', rcode)]) - reservs = folio_id.room_lines if folio_id else sreservs + sreservs = channel_reserv_obj.search([('channel_reservation_id', '=', rcode)]) + reservs = folio_id.room_lines if folio_id else sreservs.mapped(lambda x: x.odoo_id) reservs_processed = False if any(reservs): folio_id = reservs[0].folio_id for reserv in reservs: - if reserv.wrid == rcode: - reserv.with_context({'wubook_action': False}).write({ + if reserv.channel_reservation_id == rcode: + binding_id = reserv.channel_bind_ids[0] + binding_id.write({ + 'channel_raw_data': json.dumps(book), 'wstatus': str(book['status']), 'wstatus_reason': book.get('status_reason', ''), + }) + reserv.with_context({'wubook_action': False}).write({ 'to_read': True, 'to_assign': True, 'price_unit': book['amount'], - 'wcustomer_notes': book['customer_notes'], - 'wbook_json': json.dumps(book), + 'customer_notes': book['customer_notes'], }) if reserv.partner_id.unconfirmed: reserv.partner_id.write( @@ -321,22 +330,22 @@ class HotelChannelConnectorImporter(AbstractComponent): partner_id = res_partner_obj.create(self._generate_partner_vals(book)) # Search Wubook Channel Info - wchannel_info = self.env['wubook.channel.info'].search( - [('wid', '=', str(book['id_channel']))], limit=1) + channel_info = self.env['hotel.channel.connector.ota.info'].search( + [('ota_id', '=', str(book['id_channel']))], limit=1) reservations = [] used_rooms = [] # Iterate booked rooms for broom in book['booked_rooms']: - room_type = hotel_room_type_obj.search([ - ('wrid', '=', broom['room_id']) + room_type = channel_room_type_obj.search([ + ('channel_room_id', '=', broom['room_id']) ], limit=1) if not room_type: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Can't found any room type associated to '%s' \ in this hotel" % book['rooms'], - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) failed_reservations.append(crcode) continue @@ -355,26 +364,26 @@ class HotelChannelConnectorImporter(AbstractComponent): checkin_str, checkout_str, is_cancellation, - wchannel_info, + channel_info, bstatus, crcode, rcode, - room_type, + room_type.odoo_id, split_booking, dates_checkin, dates_checkout, book, ) if vals['price_unit'] != book['amount']: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) - free_rooms = hotel_room_type_obj.check_availability_room( + free_rooms = room_type.odoo_id.check_availability_room( checkin_str, checkout_str, - room_type_id=room_type.id, + room_type_id=room_type.odoo_id.id, notthis=used_rooms) if any(free_rooms): vals.update({ @@ -412,11 +421,11 @@ class HotelChannelConnectorImporter(AbstractComponent): checkin_utc_dt, checkout_utc_dt, is_cancellation, - wchannel_info, + channel_info, bstatus, crcode, rcode, - room_type, + room_type.odoo_id, False, (checkin_utc_dt, False), (checkout_utc_dt, False), @@ -424,15 +433,15 @@ class HotelChannelConnectorImporter(AbstractComponent): ) vals.update({ 'product_id': - room_type.room_ids[0].product_id.id, - 'name': room_type.name, + room_type.odoo_id.room_ids[0].product_id.id, + 'name': room_type.odoo_id.name, 'overbooking': True, }) reservations.append((0, False, vals)) - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Reservation imported with overbooking state", - '', wid=rcode) + '', channel_object_id=rcode) dates_checkin = [False, False] dates_checkout = [False, False] split_booking = False @@ -448,10 +457,10 @@ class HotelChannelConnectorImporter(AbstractComponent): ] if split_booking: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Reservation Splitted", - '', wid=rcode) + '', channel_object_id=rcode) # Create Folio if not any(failed_reservations) and any(reservations): @@ -487,11 +496,11 @@ class HotelChannelConnectorImporter(AbstractComponent): creserv.parent_reservation = preserv.id processed_rids.append(rcode) - except Exception as e_msg: - self.create_channel_connector_issue( + except ChannelConnectorError as err: + self.create_issue( 'reservation', - e_msg[0], - '', wid=rcode) + err.data['message'], + '', channel_object_id=rcode) failed_reservations.append(crcode) return (processed_rids, any(failed_reservations), checkin_utc_dt, checkout_utc_dt) @@ -698,8 +707,8 @@ class HotelChannelConnectorImporter(AbstractComponent): room_type.with_context({'wubook_action': False}).write(vals) else: room_type_obj.with_context({'wubook_action': False}).create(vals) - except ValidationError: - self.create_issue('room', _("Can't import rooms from WuBook"), results) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) return count @@ -725,10 +734,10 @@ class HotelChannelConnectorImporter(AbstractComponent): dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms) self._generate_room_values(dfrom, dto, results, - set_max_avail=set_max_avail) - except ValidationError: + set_max_avail=set_max_avail) + except ChannelConnectorError as err: self.create_issue('room', _("Can't fetch rooms values from WuBook"), - results, dfrom=dfrom, dto=dto) + err.data['message'], dfrom=dfrom, dto=dto) return False return True @@ -746,11 +755,11 @@ class HotelChannelConnectorImporter(AbstractComponent): self.backend_adapter.fetch_rooms_values( checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) - except ValidationError: - self.create_channel_connector_issue( + except ChannelConnectorError as err: + self.create_issue( 'reservation', _("Can't process reservations from wubook"), - results, channel_object_id=channel_reservation_id) + err.data['message'], channel_object_id=channel_reservation_id) return False return True @@ -759,11 +768,11 @@ class HotelChannelConnectorImporter(AbstractComponent): try: results = self.backend_adapter.get_pricing_plans() count = self._generate_pricelists(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', _("Can't get pricing plans from wubook"), - results) + err.data['message']) return 0 return count @@ -776,11 +785,11 @@ class HotelChannelConnectorImporter(AbstractComponent): date_to, rooms) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', _("Can't fetch plan prices from wubook"), - results) + err.data['message']) return False return True @@ -799,11 +808,12 @@ class HotelChannelConnectorImporter(AbstractComponent): date_to, rooms) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', "Can't fetch all plan prices from wubook!", - results, wid=channel_plan_id, dfrom=date_from, dto=date_to) + err.data['message'], + channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to) return False return no_errors @@ -812,11 +822,11 @@ class HotelChannelConnectorImporter(AbstractComponent): try: results = self.backend_adapter.rplan_rplans() count = self._generate_restrictions(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'rplan', _("Can't fetch restriction plans from wubook"), - results) + err.data['message']) return 0 return count @@ -829,12 +839,12 @@ class HotelChannelConnectorImporter(AbstractComponent): int(channel_restriction_plan_id)) if any(results): self._generate_restriction_items(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'rplan', _("Can't fetch plan restrictions from wubook"), - results, - wid=channel_restriction_plan_id, + err.data['message'], + channel_object_id=channel_restriction_plan_id, dfrom=date_from, dto=date_to) return False return True @@ -844,10 +854,10 @@ class HotelChannelConnectorImporter(AbstractComponent): try: results = self.backend_adapter.get_channels_info() count = self._generate_wubook_channel_info(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'channel', _("Can't import channels info from wubook"), - results) + err.data['message']) return 0 return count diff --git a/hotel_channel_connector/models/hotel_reservation/importer.py b/hotel_channel_connector/models/hotel_reservation/importer.py index 203fc4d05..565b24ac9 100644 --- a/hotel_channel_connector/models/hotel_reservation/importer.py +++ b/hotel_channel_connector/models/hotel_reservation/importer.py @@ -3,6 +3,7 @@ from odoo.exceptions import ValidationError from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo import fields, api, _ from odoo.tools import ( DEFAULT_SERVER_DATE_FORMAT, @@ -34,10 +35,10 @@ class HotelReservationImporter(Component): self.backend_adapter.fetch_rooms_values( checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'reservation', _("Can't process reservations from wubook"), - results) + err.data['message']) return False return True diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 3724afa0a..607e341a6 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -21,9 +21,10 @@ - + + From 5b1abbfa0fb6a26079711653187c5979467fdde8 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Sat, 22 Sep 2018 01:45:50 +0200 Subject: [PATCH 3/6] [WIP][MIG][11.0] channel connector --- hotel_channel_connector/__manifest__.py | 1 - .../components/__init__.py | 1 + .../components/importer.py | 83 ++++++----------- hotel_channel_connector/components/mapper.py | 16 ++++ hotel_channel_connector/data/menus.xml | 37 ++++---- .../models/channel_backend/common.py | 8 +- .../models/hotel_room_type/__init__.py | 2 + .../models/hotel_room_type/common.py | 63 ++++++++----- .../models/hotel_room_type/exporter.py | 25 +++++ .../models/hotel_room_type/importer.py | 93 +++++++++++++++++++ .../views/channel_connector_backend_views.xml | 13 ++- .../views/channel_connector_menu.xml | 15 --- .../hotel_channel_connector_issue_views.xml | 9 ++ ...hotel_channel_connector_ota_info_views.xml | 61 ++++++------ 14 files changed, 281 insertions(+), 146 deletions(-) create mode 100644 hotel_channel_connector/components/mapper.py create mode 100644 hotel_channel_connector/models/hotel_room_type/exporter.py create mode 100644 hotel_channel_connector/models/hotel_room_type/importer.py delete mode 100644 hotel_channel_connector/views/channel_connector_menu.xml diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 7d82409d5..11579bee9 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -40,7 +40,6 @@ 'views/channel_hotel_room_type_restriction_views.xml', 'views/channel_product_pricelist_views.xml', 'views/channel_connector_backend_views.xml', - 'views/channel_connector_menu.xml', 'data/menus.xml', 'data/sequences.xml', #'security/ir.model.access.csv', diff --git a/hotel_channel_connector/components/__init__.py b/hotel_channel_connector/components/__init__.py index 45964d6bc..fbeb3cccb 100644 --- a/hotel_channel_connector/components/__init__.py +++ b/hotel_channel_connector/components/__init__.py @@ -6,3 +6,4 @@ from . import backend_adapter from . import binder from . import importer from . import exporter +from . import mapper diff --git a/hotel_channel_connector/components/importer.py b/hotel_channel_connector/components/importer.py index 2a7a782bb..f3e407fa4 100644 --- a/hotel_channel_connector/components/importer.py +++ b/hotel_channel_connector/components/importer.py @@ -685,62 +685,6 @@ class HotelChannelConnectorImporter(AbstractComponent): count = count + 1 return count - @api.model - def get_rooms(self): - count = 0 - try: - results = self.backend_adapter.fetch_rooms() - - room_type_obj = self.env['hotel.room.type'] - count = len(results) - for room in results: - vals = { - 'name': room['name'], - 'wrid': room['id'], - 'wscode': room['shortname'], - 'list_price': room['price'], - 'wcapacity': room['occupancy'], - # 'max_real_rooms': room['availability'], - } - room_type = room_type_obj.search([('wrid', '=', room['id'])], limit=1) - if room_type: - room_type.with_context({'wubook_action': False}).write(vals) - else: - room_type_obj.with_context({'wubook_action': False}).create(vals) - except ChannelConnectorError as err: - self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) - - return count - - @api.model - def fetch_rooms_values(self, dfrom, dto, rooms=False, - set_max_avail=False): - # Sanitize Dates - now_dt = date_utils.now() - dfrom_dt = date_utils.get_datetime(dfrom) - dto_dt = date_utils.get_datetime(dto) - if dto_dt < now_dt: - return True - if dfrom_dt < now_dt: - dfrom_dt = now_dt - if dfrom_dt > dto_dt: - dtemp_dt = dfrom_dt - dfrom_dt = dto_dt - dto_dt = dtemp_dt - - try: - results = self.backend_adapter.fetch_rooms_values( - dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - rooms) - self._generate_room_values(dfrom, dto, results, - set_max_avail=set_max_avail) - except ChannelConnectorError as err: - self.create_issue('room', _("Can't fetch rooms values from WuBook"), - err.data['message'], dfrom=dfrom, dto=dto) - return False - return True - @api.model def fetch_booking(self, channel_reservation_id): try: @@ -861,3 +805,30 @@ class HotelChannelConnectorImporter(AbstractComponent): err.data['message']) return 0 return count + +class BatchImporter(AbstractComponent): + """ The role of a BatchImporter is to search for a list of + items to import, then it can either import them directly or delay + the import of each item separately. + """ + + _name = 'channel.batch.importer' + _inherit = ['base.importer', 'base.hotel.channel.connector'] + _usage = 'batch.importer' + + def _import_record(self, external_id): + """ Import a record directly or delay the import of the record. + + Method to implement in sub-classes. + """ + raise NotImplementedError + +class DirectBatchImporter(AbstractComponent): + """ Import the records directly, without delaying the jobs. """ + + _name = 'channel.direct.batch.importer' + _inherit = 'channel.batch.importer' + + def _import_record(self, external_id): + """ Import the record directly """ + self.model.import_record(self.backend_record, external_id) diff --git a/hotel_channel_connector/components/mapper.py b/hotel_channel_connector/components/mapper.py new file mode 100644 index 000000000..ad2ed03b9 --- /dev/null +++ b/hotel_channel_connector/components/mapper.py @@ -0,0 +1,16 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import AbstractComponent + + +class ChannelImportMapper(AbstractComponent): + _name = 'channel.import.mapper' + _inherit = ['base.hotel.channel.connector', 'base.import.mapper'] + _usage = 'import.mapper' + + +class ChannelExportMapper(AbstractComponent): + _name = 'channel.export.mapper' + _inherit = ['base.hotel.channel.connector', 'base.export.mapper'] + _usage = 'export.mapper' diff --git a/hotel_channel_connector/data/menus.xml b/hotel_channel_connector/data/menus.xml index 4f0fdc237..724ad6661 100644 --- a/hotel_channel_connector/data/menus.xml +++ b/hotel_channel_connector/data/menus.xml @@ -1,28 +1,25 @@ - - Hotel Channel Connector OTA's Info - hotel.channel.connector.ota.info - form - tree,form - + - - Hotel Channel Connector Issues - hotel.channel.connector.issue - form - tree,form - - {"search_default_to_read":True} - + - + - - + diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 776bf5d04..a01fef7ff 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -35,6 +35,13 @@ class ChannelBackend(models.Model): channel_hotel_reservation.import_reservations(backend) return True + @api.multi + def import_rooms(self): + channel_hotel_room_type = self.env['channel.hotel.room.type'] + for backend in self: + channel_hotel_room_type.import_rooms(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): @@ -47,6 +54,5 @@ class ChannelBackend(models.Model): self.pkey) with WuBookServer(wubook_login) as channel_api: _super = super(ChannelBackend, self) - # from the components we'll be able to do: self.work.channel_api with _super.work_on(model_name, channel_api=channel_api, **kwargs) as work: yield work diff --git a/hotel_channel_connector/models/hotel_room_type/__init__.py b/hotel_channel_connector/models/hotel_room_type/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type/__init__.py @@ -2,3 +2,5 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index b967543cb..98a6410e4 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -1,11 +1,13 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging from odoo import api, models, fields, _ from odoo.exceptions import ValidationError from odoo.addons.queue_job.job import job, related_action from odoo.addons.component.core import Component from odoo.addons.component_event import skip_if +_logger = logging.getLogger(__name__) class ChannelHotelRoomType(models.Model): _name = 'channel.hotel.room.type' @@ -21,6 +23,13 @@ class ChannelHotelRoomType(models.Model): channel_short_code = fields.Char("Channel Short Code", readonly=True, old_name='wscode') ota_capacity = fields.Integer("OTA's Capacity", default=1, old_name='wcapacity') + @job(default_channel='root.channel') + @api.model + def import_rooms(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='hotel.room.type.importer') + return importer.get_rooms() + @api.constrains('ota_capacity') def _check_ota_capacity(self): for record in self: @@ -63,20 +72,16 @@ class ChannelHotelRoomType(models.Model): @related_action(action='related_action_unwrap_binding') @api.multi def modify_room(self): + _logger.info("PASA A =======") self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + _logger.info("PASA b =======") + if self._context.get('wubook_action', True) and self.channel_room_id: + _logger.info("PASA C =======") with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.modify_room( - self.channel_room_id, - self.name, - self.ota_capacity, - self.list_price, - self.total_rooms_count, - self.channel_short_code) - except ValidationError as e: - self.create_issue('room', "Can't modify room on channel", "sss") + _logger.info("PASA D =======") + exporter = work.component(usage='hotel.room.type.exporter') + exporter.modify_room(self) + _logger.info("PASA E =======") @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -91,14 +96,6 @@ class ChannelHotelRoomType(models.Model): except ValidationError as e: self.create_issue('room', "Can't delete room on channel", "sss") - @job(default_channel='root.channel') - @api.multi - def import_rooms(self): - if self._context.get('channel_action', True): - with self.backend_id.work_on(self._name) as work: - importer = work.component(usage='channel.importer') - return importer.import_rooms() - class HotelRoomType(models.Model): _inherit = 'hotel.room.type' @@ -125,13 +122,31 @@ class HotelRoomType(models.Model): ], limit=1) return restriction -class ChannelBindingRoomTypeListener(Component): - _name = 'channel.binding.room.type.listener' +class HotelRoomTypeAdapter(Component): + _name = 'channel.hotel.room.type.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.room.type' + + def fetch_rooms(self): + return super(HotelRoomTypeAdapter, self).fetch_rooms() + +class BindingHotelRoomTypeListener(Component): + _name = 'binding.hotel.room.type.listener' _inherit = 'base.connector.listener' - _apply_on = ['channel.room.type'] + _apply_on = ['hotel.room.type'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): + if 'name' in fields or 'list_price' in fields: + record.channel_bind_ids[0].modify_room() + +class ChannelBindingRoomTypeListener(Component): + _name = 'channel.binding.room.type.listener' + _inherit = 'base.connector.listener' + _apply_on = ['channel.hotel.room.type'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): record.with_delay(priority=20).create_room() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @@ -140,4 +155,4 @@ class ChannelBindingRoomTypeListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - record.with_delay(priority=20).modidy_room() + record.modify_room() diff --git a/hotel_channel_connector/models/hotel_room_type/exporter.py b/hotel_channel_connector/models/hotel_room_type/exporter.py new file mode 100644 index 000000000..07fd8381e --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type/exporter.py @@ -0,0 +1,25 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo import api, _ + +class HotelRoomTypeExporter(Component): + _name = 'channel.hotel.room.type.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type'] + _usage = 'hotel.room.type.exporter' + + @api.model + def modify_room(self, binding): + try: + return self.backend_adapter.modify_room( + binding.channel_room_id, + binding.name, + binding.ota_capacity, + binding.list_price, + binding.total_rooms_count, + binding.channel_short_code) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't modify rooms in WuBook"), err.data['message']) diff --git a/hotel_channel_connector/models/hotel_room_type/importer.py b/hotel_channel_connector/models/hotel_room_type/importer.py new file mode 100644 index 000000000..161e658e5 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -0,0 +1,93 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.connector.components.mapper import mapping +from odoo import fields, api, _ +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) + + +class HotelRoomTypeImporter(Component): + _name = 'channel.hotel.room.type.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.hotel.room.type'] + _usage = 'hotel.room.type.importer' + + def _import_record(self, external_id, job_options=None, **kwargs): + return super(HotelRoomTypeImporter, self)._import_record(external_id) + + @api.model + def get_rooms(self): + count = 0 + try: + results = self.backend_adapter.fetch_rooms() + + channel_room_type_obj = self.env['channel.hotel.room.type'] + room_mapper = self.component(usage='import.mapper', + model_name='channel.hotel.room.type') + count = len(results) + for room in results: + map_record = room_mapper.map_record(room) + room_bind = channel_room_type_obj.search([ + ('channel_room_id', '=', room['id']) + ], limit=1) + if room_bind: + room_bind.with_context({'wubook_action': False}).write(map_record.values()) + else: + room_bind = channel_room_type_obj.with_context({'wubook_action': False}).create( + map_record.values(for_create=True)) + room_bind.odoo_id.write({ + 'list_price': room['price'], + 'name': room['name'], + }) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) + + return count + + @api.model + def fetch_rooms_values(self, dfrom, dto, rooms=False, + set_max_avail=False): + # Sanitize Dates + now_dt = date_utils.now() + dfrom_dt = date_utils.get_datetime(dfrom) + dto_dt = date_utils.get_datetime(dto) + if dto_dt < now_dt: + return True + if dfrom_dt < now_dt: + dfrom_dt = now_dt + if dfrom_dt > dto_dt: + dfrom_dt, dto_dt = dto_dt, dfrom_dt + + try: + results = self.backend_adapter.fetch_rooms_values( + dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + rooms) + self._generate_room_values(dfrom, dto, results, + set_max_avail=set_max_avail) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't fetch rooms values from WuBook"), + err.data['message'], dfrom=dfrom, dto=dto) + return False + return True + + +class HotelRoomTypeImportMapper(Component): + _name = 'channel.hotel.room.type.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.hotel.room.type' + + direct = [ + ('id', 'channel_room_id'), + ('shortname', 'channel_short_code'), + ('occupancy', 'ota_capacity'), + ] + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 607e341a6..259860e0c 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -5,7 +5,7 @@ channel.backend.form channel.backend -
+
@@ -69,6 +70,15 @@ string="Import in background"/> + + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_views.xml index 7ccbbddb1..846964b59 100644 --- a/hotel_channel_connector/views/channel_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/channel_hotel_room_type_views.xml @@ -6,6 +6,9 @@ channel.hotel.room.type + + + diff --git a/hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml b/hotel_channel_connector/views/channel_ota_info_views.xml similarity index 70% rename from hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml rename to hotel_channel_connector/views/channel_ota_info_views.xml index 26439d1cc..8dcc4dff8 100644 --- a/hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml +++ b/hotel_channel_connector/views/channel_ota_info_views.xml @@ -3,8 +3,8 @@ - hotel.channel.connector.ota.info.form - hotel.channel.connector.ota.info + channel.ota.info.form + channel.ota.info @@ -20,8 +20,8 @@ - hotel.channel.connector.ota.info.tree - hotel.channel.connector.ota.info + channel.ota.info.tree + channel.ota.info @@ -30,9 +30,9 @@ - + Hotel Channel Connector OTA's Info - hotel.channel.connector.ota.info + channel.ota.info form tree,form diff --git a/hotel_channel_connector/views/inherited_reservation_restriction_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml similarity index 75% rename from hotel_channel_connector/views/inherited_reservation_restriction_views.xml rename to hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml index ca93004bf..86ef97d11 100644 --- a/hotel_channel_connector/views/inherited_reservation_restriction_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml @@ -1,9 +1,9 @@ - + hotel.room.type.restriction - +
diff --git a/hotel_channel_connector/views/inherited_hotel_room_type_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_views.xml index 12a51319c..e75a7a7c0 100644 --- a/hotel_channel_connector/views/inherited_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_views.xml @@ -9,9 +9,10 @@ - + + - + diff --git a/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml b/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml deleted file mode 100644 index 4cb391c94..000000000 --- a/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - hotel.room.type.restriction.item - - - - True - - - - - diff --git a/hotel_channel_connector/wizard/__init__.py b/hotel_channel_connector/wizard/__init__.py index 689ca52b8..937912893 100644 --- a/hotel_channel_connector/wizard/__init__.py +++ b/hotel_channel_connector/wizard/__init__.py @@ -19,7 +19,6 @@ # along with this program. If not, see . # ############################################################################## -from . import wubook_installer from . import wubook_import_plan_prices from . import wubook_import_plan_restrictions from . import wubook_import_availability diff --git a/hotel_channel_connector/wizard/wubook_installer.py b/hotel_channel_connector/wizard/wubook_installer.py deleted file mode 100644 index ceba052b4..000000000 --- a/hotel_channel_connector/wizard/wubook_installer.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# Alexandre Díaz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -############################################################################## -import os -import binascii -from openerp import models, fields, api, _ -from openerp.exceptions import ValidationError -from ..components.backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT -from odoo.addons.hotel import date_utils - - -class WuBookInstaller(models.TransientModel): - _name = 'wubook.installer' - _inherit = 'res.config.installer' - - wubook_user = fields.Char('User', required=True) - wubook_passwd = fields.Char('Password', required=True) - wubook_lcode = fields.Char('LCode', required=True) - wubook_server = fields.Char(string='Server', - default='https://wired.wubook.net/xrws/', - required=True) - wubook_pkey = fields.Char('PKey', required=True) - activate_push = fields.Boolean('Active Push Notifications', default=True) - - @api.multi - def execute(self): - super(WuBookInstaller, self).execute() - return self.execute_simple() - - @api.multi - def execute_simple(self): - activate_push = True - for rec in self: - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_user', - rec.wubook_user) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_passwd', - rec.wubook_passwd) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_lcode', - rec.wubook_lcode) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_server', - rec.wubook_server) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_pkey', - rec.wubook_pkey) - activate_push = rec.activate_push - self.env['ir.default'].sudo().set( - 'wubook.config.settings', - 'wubook_push_security_token', - binascii.hexlify(os.urandom(16)).decode()) - self.env.cr.commit() # FIXME: Need do this - - # Create Wubook Base Restrictions - restr_obj = self.env['hotel.room.type.restriction'].with_context({ - 'wubook_action': False - }) - base_rest = restr_obj.search([('wpid', '=', '0')], limit=1) - if not base_rest: - nrest = restr_obj.create({ - 'name': 'Base WuBook Restrictions', - 'wpid': '0', - }) - if not nrest: - raise ValidationError(_("Can't create base wubook restrictions")) - - # Initialize WuBook - wres = self.env['wubook'].initialize(activate_push) - if not wres: - raise ValidationError("Can't finish installation!") - - # Open Next Step - v_id = 'hotel_wubook_proto.view_wubook_configuration_installer_parity' - return { - 'name': _("Configure Hotel Parity"), - 'type': 'ir.actions.act_window', - 'res_model': 'wubook.installer.parity', - 'view_id': self.env.ref(v_id).id, - 'view_type': 'form', - 'view_mode': 'form', - 'target': 'new' - } - - -class WuBookInstallerParity(models.TransientModel): - _name = 'wubook.installer.parity' - _inherit = 'res.config.installer' - - parity_pricelist_id = fields.Many2one('product.pricelist', - 'Product Pricelist') - parity_restrictions_id = fields.Many2one('hotel.room.type.restriction', - 'Restrictions') - import_data = fields.Boolean('Import Data From WuBook', default=False) - date_start = fields.Date('Date Start') - date_end = fields.Date('Date End') - - @api.multi - def execute(self): - self.execute_simple() - return super(WuBookInstallerParity, self).execute() - - @api.multi - def execute_simple(self): - wubookObj = self.env['wubook'] - irValuesObj = self.env['ir.values'] - for rec in self: - irValuesObj.sudo().set_default('res.config.settings', - 'parity_pricelist_id', - rec.parity_pricelist_id.id) - irValuesObj.sudo().set_default('res.config.settings', - 'parity_restrictions_id', - rec.parity_restrictions_id.id) - import_data = rec.import_data - if rec.import_data: - date_start_dt = date_utils.get_datetime(rec.date_start) - date_end_dt = date_utils.get_datetime(rec.date_end) - # Availability - wresAvail = wubookObj.fetch_rooms_values( - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT)) - # Pricelist - wresPrices = wubookObj.fetch_plan_prices( - rec.parity_pricelist_id.wpid, - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT)) - # Restrictions - wresRestr = wubookObj.fetch_rplan_restrictions( - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - rec.parity_restrictions_id.wpid) - - if not wresAvail or not wresPrices or not wresRestr: - raise ValidationError(_("Errors importing data from WuBook")) - - # Reservations - wubookObj.fetch_new_bookings() diff --git a/hotel_channel_connector/wizard/wubook_installer.xml b/hotel_channel_connector/wizard/wubook_installer.xml deleted file mode 100644 index 57dbac21f..000000000 --- a/hotel_channel_connector/wizard/wubook_installer.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - wubook.installer.form - wubook.installer - - - - WuBook Configuration - - -
-
-
- -

- WuBook API Configuration. This wizard will activate push request and synchronize rooms & reservations with Odoo. -

- - - - - - - - - - - - - - -
-
-
- - - wubook.installer.parity.form - wubook.installer.parity - - - - WuBook Configuration Parity - - -
-
-
- -

- These models are used as masters -

- - - - - - - - - - - - - - - -
-
-
- - - Configure WuBook Data - ir.actions.act_window - wubook.installer - - form - form - new - - - - -
From d217f60ac1fe0fc43c7ae6cbc9fa725ef8091d7b Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Thu, 27 Sep 2018 17:20:16 +0200 Subject: [PATCH 5/6] [WIP][MIG][11.0] connector --- hotel_channel_connector/models/channel_backend/common.py | 7 +++++++ hotel_channel_connector/models/hotel_room_type/exporter.py | 2 ++ .../views/channel_connector_backend_views.xml | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 296948e0e..88b898522 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -1,6 +1,8 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os +import binascii from contextlib import contextmanager from odoo import models, api, fields from ...components.backend_adapter import WuBookLogin, WuBookServer @@ -29,6 +31,11 @@ class ChannelBackend(models.Model): pkey = fields.Char('Channel Service PKey') security_token = fields.Char('Channel Service Security Token') + @api.multi + def generate_key(self): + for record in self: + record.security_token = binascii.hexlify(os.urandom(32)).decode() + @api.multi def import_reservations(self): channel_hotel_reservation_obj = self.env['channel.hotel.reservation'] diff --git a/hotel_channel_connector/models/hotel_room_type/exporter.py b/hotel_channel_connector/models/hotel_room_type/exporter.py index 2d44b0e08..5ee014734 100644 --- a/hotel_channel_connector/models/hotel_room_type/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type/exporter.py @@ -1,9 +1,11 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging from odoo.addons.component.core import Component from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo import api, _ +_logger = logging.getLogger(__name__) class HotelRoomTypeExporter(Component): _name = 'channel.hotel.room.type.exporter' diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index da35fece1..0728b2bf1 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -27,7 +27,12 @@ + + +