From 5b1abbfa0fb6a26079711653187c5979467fdde8 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Sat, 22 Sep 2018 01:45:50 +0200 Subject: [PATCH] [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 -
+