From 44d3665a7b098352f199a5ce5519ac16cf14e291 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 9 Oct 2018 09:39:07 +0200 Subject: [PATCH 1/7] [IMP][MIG][11.0] hotel_calendar --- hotel_calendar/models/bus_hotel_calendar.py | 2 +- .../models/inherited_hotel_room_type_availability.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hotel_calendar/models/bus_hotel_calendar.py b/hotel_calendar/models/bus_hotel_calendar.py index 2cef55759..9314f95ed 100644 --- a/hotel_calendar/models/bus_hotel_calendar.py +++ b/hotel_calendar/models/bus_hotel_calendar.py @@ -110,7 +110,7 @@ class BusHotelCalendar(models.TransientModel): return { 'type': 'availability', 'availability': { - vals['room_id']: { + vals['room_type_id']: { date_dt.strftime("%d/%m/%Y"): [ vals['avail'], vals['no_ota'], diff --git a/hotel_calendar/models/inherited_hotel_room_type_availability.py b/hotel_calendar/models/inherited_hotel_room_type_availability.py index fa81bf137..4a001c4a9 100644 --- a/hotel_calendar/models/inherited_hotel_room_type_availability.py +++ b/hotel_calendar/models/inherited_hotel_room_type_availability.py @@ -8,7 +8,7 @@ class HotelRoomTypeAvailability(models.Model): @api.model def create(self, vals): - res = super(HotelVirtualRoomAvailability, self).create(vals) + res = super(HotelRoomTypeAvailability, self).create(vals) self.env['bus.hotel.calendar'].send_availability_notification({ 'date': res.date, 'avail': res.avail, @@ -20,7 +20,7 @@ class HotelRoomTypeAvailability(models.Model): @api.multi def write(self, vals): - ret_vals = super(HotelVirtualRoomAvailability, self).write(vals) + ret_vals = super(HotelRoomTypeAvailability, self).write(vals) bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for record in self: bus_hotel_calendar_obj.send_availability_notification({ @@ -44,7 +44,7 @@ class HotelRoomTypeAvailability(models.Model): 'no_ota': False, 'id': record.id, }) - res = super(HotelVirtualRoomAvailability, self).unlink() + res = super(HotelRoomTypeAvailability, self).unlink() bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for uval in unlink_vals: bus_hotel_calendar_obj.send_availability_notification(uval) From fee28b870627f049fab26ada8572f28a5a760212 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 9 Oct 2018 09:39:07 +0200 Subject: [PATCH 2/7] [IMP][MIG][11.0] hotel_calendar --- hotel_calendar/models/bus_hotel_calendar.py | 2 +- .../models/inherited_hotel_room_type_availability.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hotel_calendar/models/bus_hotel_calendar.py b/hotel_calendar/models/bus_hotel_calendar.py index 2cef55759..9314f95ed 100644 --- a/hotel_calendar/models/bus_hotel_calendar.py +++ b/hotel_calendar/models/bus_hotel_calendar.py @@ -110,7 +110,7 @@ class BusHotelCalendar(models.TransientModel): return { 'type': 'availability', 'availability': { - vals['room_id']: { + vals['room_type_id']: { date_dt.strftime("%d/%m/%Y"): [ vals['avail'], vals['no_ota'], diff --git a/hotel_calendar/models/inherited_hotel_room_type_availability.py b/hotel_calendar/models/inherited_hotel_room_type_availability.py index fa81bf137..4a001c4a9 100644 --- a/hotel_calendar/models/inherited_hotel_room_type_availability.py +++ b/hotel_calendar/models/inherited_hotel_room_type_availability.py @@ -8,7 +8,7 @@ class HotelRoomTypeAvailability(models.Model): @api.model def create(self, vals): - res = super(HotelVirtualRoomAvailability, self).create(vals) + res = super(HotelRoomTypeAvailability, self).create(vals) self.env['bus.hotel.calendar'].send_availability_notification({ 'date': res.date, 'avail': res.avail, @@ -20,7 +20,7 @@ class HotelRoomTypeAvailability(models.Model): @api.multi def write(self, vals): - ret_vals = super(HotelVirtualRoomAvailability, self).write(vals) + ret_vals = super(HotelRoomTypeAvailability, self).write(vals) bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for record in self: bus_hotel_calendar_obj.send_availability_notification({ @@ -44,7 +44,7 @@ class HotelRoomTypeAvailability(models.Model): 'no_ota': False, 'id': record.id, }) - res = super(HotelVirtualRoomAvailability, self).unlink() + res = super(HotelRoomTypeAvailability, self).unlink() bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for uval in unlink_vals: bus_hotel_calendar_obj.send_availability_notification(uval) From a2c357729b498534870ab749f95718f19316deda Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Sat, 3 Nov 2018 16:54:29 +0100 Subject: [PATCH 3/7] [WIP] Channel connector --- .../components/exporter.py | 38 +-------- .../models/channel_backend/common.py | 20 +++++ .../models/hotel_room_type/common.py | 2 +- .../hotel_room_type_availability/__init__.py | 1 + .../hotel_room_type_availability/common.py | 80 ++++++++++++------- .../hotel_room_type_availability/exporter.py | 74 +++++++++++++++++ .../hotel_room_type_availability/importer.py | 62 +++++++++++++- .../hotel_room_type_restriction/common.py | 9 ++- .../views/channel_connector_backend_views.xml | 22 +++++ 9 files changed, 237 insertions(+), 71 deletions(-) create mode 100644 hotel_channel_connector/models/hotel_room_type_availability/exporter.py diff --git a/hotel_channel_connector/components/exporter.py b/hotel_channel_connector/components/exporter.py index fb42e8e61..bc1545ede 100644 --- a/hotel_channel_connector/components/exporter.py +++ b/hotel_channel_connector/components/exporter.py @@ -12,7 +12,7 @@ from odoo.tools import ( DEFAULT_SERVER_DATETIME_FORMAT) from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT from odoo.addons.hotel import date_utils -from odoo import api +from odoo import api, fields _logger = logging.getLogger(__name__) class HotelChannelConnectorExporter(AbstractComponent): @@ -25,42 +25,6 @@ class HotelChannelConnectorExporter(AbstractComponent): return self.push_availability() and self.push_priceplans() and \ self.push_restrictions() - @api.model - def push_availability(self): - room_type_avail_ids = self.env['hotel.room.type.availability'].search([ - ('wpushed', '=', False), - ('date', '>=', date_utils.now(hours=False).strftime( - DEFAULT_SERVER_DATE_FORMAT)) - ]) - - room_types = room_type_avail_ids.mapped('room_type_id') - avails = [] - for room_type in room_types: - room_type_avails = room_type_avail_ids.filtered( - lambda x: x.room_type_id.id == room_type.id) - days = [] - for room_type_avail in room_type_avails: - room_type_avail.with_context({ - 'wubook_action': False}).write({'wpushed': True}) - wavail = room_type_avail.avail - if wavail > room_type_avail.wmax_avail: - wavail = room_type_avail.wmax_avail - date_dt = date_utils.get_datetime( - room_type_avail.date, - dtformat=DEFAULT_SERVER_DATE_FORMAT) - days.append({ - 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - 'avail': wavail, - 'no_ota': room_type_avail.no_ota and 1 or 0, - # 'booked': room_type_avail.booked and 1 or 0, - }) - avails.append({'id': room_type.wrid, 'days': days}) - _logger.info("UPDATING AVAILABILITY IN WUBOOK...") - _logger.info(avails) - if any(avails): - self.backend_adapter.update_availability(avails) - return True - @api.model def push_priceplans(self): unpushed = self.env['product.pricelist.item'].search([ diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 88b898522..34b30c0e5 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -31,6 +31,9 @@ class ChannelBackend(models.Model): pkey = fields.Char('Channel Service PKey') security_token = fields.Char('Channel Service Security Token') + avail_from = fields.Date('Availability From') + avail_to = fields.Date('Availability To') + @api.multi def generate_key(self): for record in self: @@ -57,6 +60,23 @@ class ChannelBackend(models.Model): channel_ota_info_obj.import_otas_info(backend) return True + @api.multi + def import_availability(self): + channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] + for backend in self: + channel_hotel_room_type_avail_obj.import_availability( + backend, + self.avail_from, + self.avail_to) + return True + + @api.multi + def push_availability(self): + channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] + for backend in self: + channel_hotel_room_type_avail_obj.push_availability(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index 5af7a14b3..4c6e2abb8 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -142,7 +142,7 @@ class BindingHotelRoomTypeListener(Component): @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: + if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields: record.channel_bind_ids[0].modify_room() # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/__init__.py b/hotel_channel_connector/models/hotel_room_type_availability/__init__.py index 06e54858b..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/__init__.py @@ -3,3 +3,4 @@ from . import common from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 1235db837..17c533951 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta -from odoo import api, models, fields +from odoo import api, models, fields, _ from odoo.tools import DEFAULT_SERVER_DATE_FORMAT from odoo.exceptions import ValidationError from odoo.addons.queue_job.job import job, related_action @@ -15,7 +15,7 @@ class ChannelHotelRoomTypeAvailability(models.Model): _name = 'channel.hotel.room.type.availability' _inherit = 'channel.binding' _inherits = {'hotel.room.type.availability': 'odoo_id'} - _description = 'Channel Product Pricelist' + _description = 'Channel Availability' @api.model def _default_channel_max_avail(self): @@ -44,19 +44,24 @@ class ChannelHotelRoomTypeAvailability(models.Model): @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi - def update_availability(self): - self.ensure_one() - if self._context.get('channel_action', True): - with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - date_dt = fields.Date.from_string(self.date) - adapter.update_availability([{ - 'id': self.odoo_id.room_type_id.channel_room_id, - 'days': [{ - 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - 'avail': self.odoo_id.avail, - }], - }]) + def update_availability(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='hotel.room.type.availability.exporter') + return exporter.update_availability(self) + + @job(default_channel='root.channel') + @api.model + def import_availability(self, backend, date_from, date_to): + with backend.work_on(self._name) as work: + importer = work.component(usage='hotel.room.type.availability.importer') + return importer.get_availability(date_from, date_to) + + @job(default_channel='root.channel') + @api.model + def push_availability(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='hotel.room.type.availability.exporter') + return exporter.push_availability() class HotelRoomTypeAvailability(models.Model): _inherit = 'hotel.room.type.availability' @@ -79,16 +84,15 @@ class HotelRoomTypeAvailability(models.Model): if record.avail > max_avail: issue_obj.sudo().create({ 'section': 'avail', - 'message': _(r"The new availability can't be greater than \ - the actual availability \ - \n[%s]\nInput: %d\Limit: %d") % (record.room_type_id.name, - record.avail, - record), - 'channel_id': record.room_type_id.channel_bind_ids[0].channel_plan_id, + 'internal_message': _(r"The new availability can't be greater than \ + the max. availability \ + (%s) [Input: %d\Max: %d]") % (record.room_type_id.name, + record.avail, + max_avail), 'date_start': record.date, 'date_end': record.date, }) - # Auto-Fix wubook availability + # Auto-Fix channel availability self._event('on_fix_channel_availability').notify(record) return super(HotelRoomTypeAvailability, self)._check_avail() @@ -97,12 +101,6 @@ class HotelRoomTypeAvailability(models.Model): if self.room_type_id: self.channel_max_avail = self.room_type_id.total_rooms_count - @api.multi - def write(self, vals): - if self._context.get('channel_action', True): - vals.update({'channel_pushed': False}) - return super(HotelRoomTypeAvailability, self).write(vals) - @api.model def refresh_availability(self, checkin, checkout, product_id): date_start = fields.Date.from_string(checkin) @@ -143,6 +141,32 @@ class HotelRoomTypeAvailability(models.Model): 'avail': avail, }) +class HotelRoomTypeAvailabilityAdapter(Component): + _name = 'channel.hotel.room.type.availability.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.room.type.availability' + + def fetch_rooms_values(self, date_from, date_to, rooms=False): + return super(HotelRoomTypeAvailabilityAdapter, self).fetch_rooms_values( + date_from, + date_to, + rooms) + + def update_availability(self, rooms_avail): + return super(HotelRoomTypeAvailabilityAdapter, self).update_availability( + rooms_avail) + +class BindingHotelRoomTypeAvailabilityListener(Component): + _name = 'binding.hotel.room.type.listener' + _inherit = 'base.connector.listener' + _apply_on = ['hotel.room.type.availability'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if 'avail' in fields: + for binding in record.channel_bind_ids: + binding.channel_pushed = False + class ChannelBindingHotelRoomTypeAvailabilityListener(Component): _name = 'channel.binding.hotel.room.type.availability.listener' _inherit = 'base.connector.listener' diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py new file mode 100644 index 000000000..c98747a03 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -0,0 +1,74 @@ +# 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, fields, _ +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +_logger = logging.getLogger(__name__) + +class HotelRoomTypeAvailabilityExporter(Component): + _name = 'channel.hotel.room.type.availability.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type.availability'] + _usage = 'hotel.room.type.availability.exporter' + + @api.model + def update_availability(self, binding): + if any(binding.room_type_id.channel_bind_ids): + try: + sday_dt = fields.Date.from_string(binding.date) + # Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_availability({ + 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, + 'days': [{ + 'date': sday_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 'avail': binding.avail, + 'no_ota': binding.no_ota, + }], + }) + except ChannelConnectorError as err: + self.create_issue( + 'room', + _("Can't update availability in WuBook"), + err.data['message']) + + def push_availability(self): + channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([ + ('channel_pushed', '=', False), + ('date', '>=', fields.Date.today()) + ]) + room_types = channel_room_type_avail_ids.mapped('room_type_id') + avails = [] + for room_type in room_types: + if any(room_type.channel_bind_ids): + channel_room_type_avails = channel_room_type_avail_ids.filtered( + lambda x: x.room_type_id.id == room_type.id) + days = [] + for channel_room_type_avail in channel_room_type_avails: + channel_room_type_avail.channel_pushed = True + cavail = channel_room_type_avail.avail + if channel_room_type_avail.channel_max_avail >= 0 and \ + cavail > channel_room_type_avail.channel_max_avail: + cavail = channel_room_type_avail.channel_max_avail + date_dt = fields.Date.from_string(channel_room_type_avail.date) + days.append({ + 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 'avail': cavail, + 'no_ota': channel_room_type_avail.no_ota and 1 or 0, + # 'booked': room_type_avail.booked and 1 or 0, + }) + avails.append({'id': room_type.channel_bind_ids[0].channel_room_id, 'days': days}) + _logger.info("UPDATING AVAILABILITY IN WUBOOK...") + _logger.info(avails) + if any(avails): + try: + self.backend_adapter.update_availability(avails) + except ChannelConnectorError as err: + self.create_issue( + 'room', + _("Can't update availability in WuBook"), + err.data['message']) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/importer.py b/hotel_channel_connector/models/hotel_room_type_availability/importer.py index 4970a308b..0aba55d20 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/importer.py @@ -2,11 +2,11 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from datetime import timedelta +from datetime import date, timedelta 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.addons.connector.components.mapper import mapping, external_to_m2o from odoo.addons.hotel import date_utils from odoo import fields, api, _ _logger = logging.getLogger(__name__) @@ -18,6 +18,59 @@ class HotelRoomTypeAvailabilityImporter(Component): _apply_on = ['channel.hotel.room.type.availability'] _usage = 'hotel.room.type.availability.importer' + @api.model + def get_availability(self, date_from, date_to): + now_dt = date.today() + dfrom_dt = fields.Date.from_string(date_from) + dto_dt = fields.Date.from_string(date_to) + if dfrom_dt < now_dt: + dfrom_dt = now_dt + if dfrom_dt > dto_dt: + dfrom_dt, dto_dt = dto_dt, dfrom_dt + if dto_dt < now_dt: + return True + count = 0 + try: + results = self.backend_adapter.fetch_rooms_values(date_from, date_to) + + channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] + channel_room_type_obj = self.env['channel.hotel.room.type'] + room_avail_mapper = self.component( + usage='import.mapper', + model_name='channel.hotel.room.type.availability') + count = len(results) + for room_k, room_v in results.items(): + iter_day = dfrom_dt + channel_room_type = channel_room_type_obj.search([ + ('channel_room_id', '=', room_k) + ], limit=1) + if channel_room_type: + for room in room_v: + room.update({ + 'room_type_id': channel_room_type.odoo_id.id, + 'date': fields.Date.to_string(iter_day), + }) + map_record = room_avail_mapper.map_record(room) + room_type_avail_bind = channel_room_type_avail_obj.search([ + ('room_type_id', '=', room['room_type_id']), + ('date', '=', room['date']) + ], limit=1) + if room_type_avail_bind: + room_type_avail_bind.with_context({ + 'wubook_action': False + }).write(map_record.values()) + else: + room_type_avail_bind = channel_room_type_avail_obj.with_context({ + 'wubook_action': False + }).create(map_record.values(for_create=True)) + iter_day += timedelta(days=1) + except ChannelConnectorError as err: + self.create_issue( + 'room', + _("Can't import availability from WuBook"), + err.data['message']) + return count + class HotelRoomTypeAvailabilityImportMapper(Component): _name = 'channel.hotel.room.type.availability.import.mapper' @@ -28,10 +81,13 @@ class HotelRoomTypeAvailabilityImportMapper(Component): ('no_ota', 'no_ota'), ('booked', 'booked'), ('avail', 'avail'), - ('room_type_id', 'room_type_id'), ('date', 'date'), ] @mapping def backend_id(self, record): return {'backend_id': self.backend_record.id} + + @mapping + def room_type_id(self, record): + return {'room_type_id': record['room_type_id']} diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/common.py b/hotel_channel_connector/models/hotel_room_type_restriction/common.py index 189523a1d..ed2c2befe 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/common.py @@ -85,8 +85,13 @@ class HotelRoomTypeRestriction(models.Model): names = [] for name in org_names: restriction_id = room_type_restriction_obj.browse(name[0]) - if restriction_id.channel_bind_ids.channel_plan_id: - names.append((name[0], '%s (WuBook)' % name[1])) + if any(restriction_id.channel_bind_ids) and \ + restriction_id.channel_bind_ids[0].channel_plan_id: + names.append(( + name[0], + '%s (%s Backend)' % (name[1], + restriction_id.channel_bind_ids[0].backend_id.name), + )) else: names.append((name[0], name[1])) return names diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 0728b2bf1..1f06b3c7b 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -84,6 +84,28 @@ string="Import in background"/> + + + + + + From 19127e3091c5d2ad5d04437cc479c9d417353117 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Mon, 5 Nov 2018 15:23:48 +0100 Subject: [PATCH 4/7] [WIP] Channel Connector --- hotel_channel_connector/__manifest__.py | 2 + .../components/backend_adapter.py | 23 +++--- hotel_channel_connector/components/binder.py | 2 + hotel_channel_connector/components/core.py | 16 ++-- .../components/importer.py | 68 ++++++++-------- hotel_channel_connector/models/__init__.py | 2 +- .../models/channel_backend/common.py | 29 ++++++- .../models/channel_binding/common.py | 2 + .../models/channel_ota_info/importer.py | 6 +- .../models/hotel_channel_connector_issue.py | 9 ++- .../models/hotel_reservation/common.py | 1 - .../models/hotel_reservation/importer.py | 13 +-- .../models/hotel_room_type/common.py | 7 +- .../models/hotel_room_type/exporter.py | 24 ++++-- .../models/hotel_room_type/importer.py | 17 ++-- .../hotel_room_type_availability/common.py | 4 +- .../hotel_room_type_availability/exporter.py | 14 ++-- .../hotel_room_type_availability/importer.py | 7 +- .../hotel_room_type_restriction/__init__.py | 1 + .../hotel_room_type_restriction/common.py | 79 +++++++++++-------- .../hotel_room_type_restriction/exporter.py | 52 ++++++++++++ .../hotel_room_type_restriction/importer.py | 37 +++++++-- .../common.py | 27 ++++++- .../importer.py | 78 +++++++++++++++++- .../inherited_product_pricelist_item.py | 56 ------------- .../models/product_pricelist/common.py | 15 +++- .../models/product_pricelist_item/__init__.py | 4 + .../models/product_pricelist_item/common.py | 30 +++++++ .../views/channel_connector_backend_views.xml | 32 ++++++++ .../views/channel_hotel_reservation_views.xml | 2 +- ...nel_hotel_room_type_availability_views.xml | 4 + ...nnel_hotel_room_type_restriction_views.xml | 7 +- .../views/channel_hotel_room_type_views.xml | 3 +- .../views/channel_ota_info_views.xml | 4 + .../channel_product_pricelist_item_views.xml | 27 +++++++ .../views/channel_product_pricelist_views.xml | 8 +- .../hotel_channel_connector_issue_views.xml | 5 ++ .../inherited_hotel_reservation_views.xml | 2 +- ...ted_hotel_room_type_availability_views.xml | 2 +- ...hotel_room_type_restriction_item_views.xml | 24 ++++++ ...ited_hotel_room_type_restriction_views.xml | 18 +++-- .../views/inherited_hotel_room_type_views.xml | 2 +- ...inherited_product_pricelist_item_views.xml | 18 +++-- .../inherited_product_pricelist_views.xml | 21 +++-- 44 files changed, 579 insertions(+), 225 deletions(-) create mode 100644 hotel_channel_connector/models/hotel_room_type_restriction/exporter.py delete mode 100644 hotel_channel_connector/models/inherited_product_pricelist_item.py create mode 100644 hotel_channel_connector/models/product_pricelist_item/__init__.py create mode 100644 hotel_channel_connector/models/product_pricelist_item/common.py create mode 100644 hotel_channel_connector/views/channel_product_pricelist_item_views.xml create mode 100644 hotel_channel_connector/views/inherited_hotel_room_type_restriction_item_views.xml diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 8bb150279..77b4d51f9 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -29,6 +29,7 @@ 'views/inherited_product_pricelist_views.xml', 'views/inherited_product_pricelist_item_views.xml', 'views/inherited_hotel_room_type_restriction_views.xml', + 'views/inherited_hotel_room_type_restriction_item_views.xml', 'views/inherited_res_partner_views.xml', 'views/channel_ota_info_views.xml', 'views/hotel_channel_connector_issue_views.xml', @@ -37,6 +38,7 @@ 'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_restriction_views.xml', 'views/channel_product_pricelist_views.xml', + 'views/channel_product_pricelist_item_views.xml', 'views/channel_connector_backend_views.xml', 'data/menus.xml', 'data/sequences.xml', diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index e5b64afd7..930b1dd92 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -9,7 +9,6 @@ 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 import date_utils from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo import fields, _ _logger = logging.getLogger(__name__) @@ -276,8 +275,8 @@ class WuBookAdapter(AbstractComponent): rcode, results = self._server.fetch_rooms_values( self._session_info[0], self._session_info[1], - date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 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", { @@ -317,14 +316,14 @@ class WuBookAdapter(AbstractComponent): 'phone': phone, 'street': address, 'country': country_code, - 'arrival_hour': date_utils.get_datetime(checkin).strftime("%H:%M"), + '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], - date_utils.get_datetime(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_utils.get_datetime(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 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) @@ -431,7 +430,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1], channel_plan_id, - date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), prices) if rcode != 0: raise ChannelConnectorError("Can't update pricing plan in wubook", { @@ -469,8 +468,8 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1], channel_plan_id, - date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 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", { @@ -496,8 +495,8 @@ class WuBookAdapter(AbstractComponent): rcode, results = self._server.wired_rplan_get_rplan_values( self._session_info[0], self._session_info[1], - date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 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", { @@ -513,7 +512,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1], channel_restriction_plan_id, - date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), values) if rcode != 0: raise ChannelConnectorError("Can't update plan restrictions on wubook", { diff --git a/hotel_channel_connector/components/binder.py b/hotel_channel_connector/components/binder.py index 3276998d5..3ed074208 100644 --- a/hotel_channel_connector/components/binder.py +++ b/hotel_channel_connector/components/binder.py @@ -11,6 +11,8 @@ class HotelConnectorModelBinder(Component): 'channel.hotel.room.type', 'channel.hotel.room.type.availability', 'channel.hotel.room.type.restriction', + 'channel.hotel.room.type.restriction.item', 'channel.product.pricelist', + 'channel.product.pricelist.item', 'channel.ota.info', ] diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py index 951c40996..629108ea3 100644 --- a/hotel_channel_connector/components/core.py +++ b/hotel_channel_connector/components/core.py @@ -10,15 +10,15 @@ class BaseHotelChannelConnectorComponent(AbstractComponent): _collection = 'channel.backend' @api.model - def create_issue(self, section, message, channel_message, channel_object_id=False, - dfrom=False, dto=False): + def create_issue(self, **kwargs): self.env['hotel.channel.connector.issue'].sudo().create({ - 'section': section, - 'internal_message': message, - 'channel_object_id': channel_object_id, - 'channel_message': channel_message, - 'date_start': dfrom, - 'date_end': dto, + 'backend_id': kwargs.get('backend', False), + 'section': kwargs.get('section', False), + 'internal_message': kwargs.get('internal_message', False), + 'channel_object_id': kwargs.get('channel_object_id', False), + 'channel_message': kwargs.get('channel_message', False), + 'date_start': kwargs.get('dfrom', False), + 'date_end': kwargs.get('dto', False), }) class ChannelConnectorError(Exception): diff --git a/hotel_channel_connector/components/importer.py b/hotel_channel_connector/components/importer.py index dc94680dd..093a7b7f2 100644 --- a/hotel_channel_connector/components/importer.py +++ b/hotel_channel_connector/components/importer.py @@ -238,9 +238,9 @@ class HotelChannelConnectorImporter(AbstractComponent): # the same transaction an cancellation) if crcode in failed_reservations: self.create_issue( - 'reservation', - "Can't process a reservation that previusly failed!", - '', channel_object_id=book['reservation_code']) + section='reservation', + internal_emssage="Can't process a reservation that previusly failed!", + channel_object_id=book['reservation_code']) continue # Get dates for the reservation (GMT->UTC) @@ -342,10 +342,10 @@ class HotelChannelConnectorImporter(AbstractComponent): ], limit=1) if not room_type: self.create_issue( - 'reservation', - "Can't found any room type associated to '%s' \ - in this hotel" % book['rooms'], - '', channel_object_id=book['reservation_code']) + section='reservation', + internal_message="Can't found any room type associated to '%s' \ + in this hotel" % book['rooms'], + channel_object_id=book['reservation_code']) failed_reservations.append(crcode) continue @@ -376,9 +376,9 @@ class HotelChannelConnectorImporter(AbstractComponent): ) if vals['price_unit'] != book['amount']: self.create_issue( - 'reservation', - "Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), - '', channel_object_id=book['reservation_code']) + section='reservation', + internal_message="Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), + channel_object_id=book['reservation_code']) free_rooms = room_type.odoo_id.check_availability_room( checkin_str, @@ -439,9 +439,9 @@ class HotelChannelConnectorImporter(AbstractComponent): }) reservations.append((0, False, vals)) self.create_issue( - 'reservation', - "Reservation imported with overbooking state", - '', channel_object_id=rcode) + section='reservation', + internal_message="Reservation imported with overbooking state", + channel_object_id=rcode) dates_checkin = [False, False] dates_checkout = [False, False] split_booking = False @@ -458,9 +458,9 @@ class HotelChannelConnectorImporter(AbstractComponent): if split_booking: self.create_issue( - 'reservation', - "Reservation Splitted", - '', channel_object_id=rcode) + section='reservation', + internal_message="Reservation Splitted", + channel_object_id=rcode) # Create Folio if not any(failed_reservations) and any(reservations): @@ -498,9 +498,9 @@ class HotelChannelConnectorImporter(AbstractComponent): processed_rids.append(rcode) except ChannelConnectorError as err: self.create_issue( - 'reservation', - err.data['message'], - '', channel_object_id=rcode) + section='reservation', + internal_message=err.data['message'], + channel_object_id=rcode) failed_reservations.append(crcode) return (processed_rids, any(failed_reservations), checkin_utc_dt, checkout_utc_dt) @@ -692,9 +692,9 @@ class HotelChannelConnectorImporter(AbstractComponent): count = self._generate_pricelists(results) except ChannelConnectorError as err: self.create_issue( - 'plan', - _("Can't get pricing plans from wubook"), - err.data['message']) + section='plan', + internal_message=_("Can't get pricing plans from wubook"), + channel_message=err.data['message']) return 0 return count @@ -709,9 +709,9 @@ class HotelChannelConnectorImporter(AbstractComponent): self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) except ChannelConnectorError as err: self.create_issue( - 'plan', - _("Can't fetch plan prices from wubook"), - err.data['message']) + section='plan', + internal_message=_("Can't fetch plan prices from wubook"), + channel_message=err.data['message']) return False return True @@ -732,9 +732,9 @@ class HotelChannelConnectorImporter(AbstractComponent): self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) except ChannelConnectorError as err: self.create_issue( - 'plan', - "Can't fetch all plan prices from wubook!", - err.data['message'], + section='plan', + internal_message="Can't fetch all plan prices from wubook!", + channel_message=err.data['message'], channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to) return False return no_errors @@ -746,9 +746,9 @@ class HotelChannelConnectorImporter(AbstractComponent): count = self._generate_restrictions(results) except ChannelConnectorError as err: self.create_issue( - 'rplan', - _("Can't fetch restriction plans from wubook"), - err.data['message']) + section='rplan', + internal_message=_("Can't fetch restriction plans from wubook"), + channel_message=err.data['message']) return 0 return count @@ -763,9 +763,9 @@ class HotelChannelConnectorImporter(AbstractComponent): self._generate_restriction_items(results) except ChannelConnectorError as err: self.create_issue( - 'rplan', - _("Can't fetch plan restrictions from wubook"), - err.data['message'], + section='rplan', + internal_message=_("Can't fetch plan restrictions from wubook"), + channel_message=err.data['message'], channel_object_id=channel_restriction_plan_id, dfrom=date_from, dto=date_to) return False diff --git a/hotel_channel_connector/models/__init__.py b/hotel_channel_connector/models/__init__.py index 622a159e4..b9ecc917a 100644 --- a/hotel_channel_connector/models/__init__.py +++ b/hotel_channel_connector/models/__init__.py @@ -5,7 +5,7 @@ from . import channel_binding from . import channel_backend from . import hotel_room_type from . import product_pricelist -from . import inherited_product_pricelist_item +from . import product_pricelist_item from . import hotel_room_type_restriction from . import hotel_room_type_restriction_item from . import hotel_room_type_availability diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 34b30c0e5..1901bfb36 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -34,6 +34,16 @@ class ChannelBackend(models.Model): avail_from = fields.Date('Availability From') avail_to = fields.Date('Availability To') + restriction_from = fields.Date('Restriction From') + restriction_to = fields.Date('Restriction To') + restriction_id = fields.Many2one('channel.hotel.room.type.restriction', + 'Channel Restriction') + + issue_ids = fields.One2many('hotel.channel.connector.issue', + 'backend_id', + string='Issues', + copy=True) + @api.multi def generate_key(self): for record in self: @@ -64,10 +74,7 @@ class ChannelBackend(models.Model): def import_availability(self): channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] for backend in self: - channel_hotel_room_type_avail_obj.import_availability( - backend, - self.avail_from, - self.avail_to) + channel_hotel_room_type_avail_obj.import_availability(backend) return True @api.multi @@ -77,6 +84,20 @@ class ChannelBackend(models.Model): channel_hotel_room_type_avail_obj.push_availability(backend) return True + @api.multi + def import_restriction_plans(self): + channel_hotel_room_type_restr_obj = self.env['channel.hotel.room.type.restriction'] + for backend in self: + channel_hotel_room_type_restr_obj.import_restriction_plans(backend) + return True + + @api.multi + def import_restriction_values(self): + channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item'] + for backend in self: + channel_hotel_restr_item_obj.import_restriction_values(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): diff --git a/hotel_channel_connector/models/channel_binding/common.py b/hotel_channel_connector/models/channel_binding/common.py index 0db43c593..558404509 100644 --- a/hotel_channel_connector/models/channel_binding/common.py +++ b/hotel_channel_connector/models/channel_binding/common.py @@ -16,6 +16,8 @@ class ChannelBinding(models.AbstractModel): required=True, ondelete='restrict') + external_id = fields.Char(string='ID on Channel') + _sql_constraints = [ ('channel_uniq', 'unique(backend_id, external_id)', 'A binding already exists with the same Channel ID.'), diff --git a/hotel_channel_connector/models/channel_ota_info/importer.py b/hotel_channel_connector/models/channel_ota_info/importer.py index 6fed02a7d..bef4ee915 100644 --- a/hotel_channel_connector/models/channel_ota_info/importer.py +++ b/hotel_channel_connector/models/channel_ota_info/importer.py @@ -42,7 +42,11 @@ class ChannelOtaInfoImporter(Component): ota_info_bind.create(map_record.values(for_create=True)) count = count + 1 except ChannelConnectorError as err: - self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't import rooms from WuBook"), + channel_message=err.data['message']) return count diff --git a/hotel_channel_connector/models/hotel_channel_connector_issue.py b/hotel_channel_connector/models/hotel_channel_connector_issue.py index 5e77127c7..fba387289 100644 --- a/hotel_channel_connector/models/hotel_channel_connector_issue.py +++ b/hotel_channel_connector/models/hotel_channel_connector_issue.py @@ -9,11 +9,16 @@ class HotelChannelConnectorIssue(models.Model): _name = 'hotel.channel.connector.issue' _old_name = 'wubook.issue' + backend_id = fields.Many2one('channel.backend', + 'Restriction Plan', + ondelete='cascade', + index=True) + section = fields.Selection([ ('channel', 'Channel'), ('reservation', 'Reservation'), - ('rplan', 'Restriction Plan'), - ('plan', 'Price Plan'), + ('restriction', 'Restriction Plan'), + ('pricelist', 'Price Plan'), ('room', 'Room'), ('avail', 'Availability')], required=True) to_read = fields.Boolean("To Read", default=True) diff --git a/hotel_channel_connector/models/hotel_reservation/common.py b/hotel_channel_connector/models/hotel_reservation/common.py index 87ed888d2..c0e059130 100644 --- a/hotel_channel_connector/models/hotel_reservation/common.py +++ b/hotel_channel_connector/models/hotel_reservation/common.py @@ -25,7 +25,6 @@ class ChannelHotelReservation(models.Model): string='Reservation', required=True, ondelete='cascade') - channel_reservation_id = fields.Char("Channel Reservation ID", readonly=True, old_name='wrid') ota_id = fields.Many2one('channel.ota.info', string='Channel OTA ID', readonly=True, diff --git a/hotel_channel_connector/models/hotel_reservation/importer.py b/hotel_channel_connector/models/hotel_reservation/importer.py index 565b24ac9..0b6f8b981 100644 --- a/hotel_channel_connector/models/hotel_reservation/importer.py +++ b/hotel_channel_connector/models/hotel_reservation/importer.py @@ -26,9 +26,9 @@ class HotelReservationImporter(Component): rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids) if rcodeb != 0: self.create_issue( - 'wubook', - _("Problem trying mark bookings (%s)") % str(processed_rids), - '') + backend=self.backend_adapter.id, + section='wubook', + internal_message=_("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: @@ -37,8 +37,9 @@ class HotelReservationImporter(Component): checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) except ChannelConnectorError as err: self.create_issue( - 'reservation', - _("Can't process reservations from wubook"), - err.data['message']) + backend=self.backend_adapter.id, + section='reservation', + internal_message=_("Can't process reservations from wubook"), + channel_message=err.data['message']) return False return True diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index 4c6e2abb8..ca490e8dd 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -19,7 +19,6 @@ class ChannelHotelRoomType(models.Model): string='Room Type', required=True, ondelete='cascade') - channel_room_id = fields.Char("Channel Room ID", readonly=True, old_name='wrid') 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') @@ -57,7 +56,7 @@ class ChannelHotelRoomType(models.Model): @api.multi def create_room(self): self.ensure_one() - if not self.channel_room_id: + if not self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.exporter') exporter.create_room(self) @@ -67,7 +66,7 @@ class ChannelHotelRoomType(models.Model): @api.multi def modify_room(self): self.ensure_one() - if self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.exporter') exporter.modify_room(self) @@ -77,7 +76,7 @@ class ChannelHotelRoomType(models.Model): @api.multi def delete_room(self): self.ensure_one() - if self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.exporter') exporter.delete_room(self) diff --git a/hotel_channel_connector/models/hotel_room_type/exporter.py b/hotel_channel_connector/models/hotel_room_type/exporter.py index 5ee014734..e35e15ecf 100644 --- a/hotel_channel_connector/models/hotel_room_type/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type/exporter.py @@ -17,21 +17,29 @@ class HotelRoomTypeExporter(Component): def modify_room(self, binding): try: return self.backend_adapter.modify_room( - binding.channel_room_id, + binding.external_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']) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't modify rooms in WuBook"), + channel_message=err.data['message']) @api.model def delete_room(self, binding): try: - return self.backend_adapter.delete_room(binding.channel_room_id) + return self.backend_adapter.delete_room(binding.external_id) except ChannelConnectorError as err: - self.create_issue('room', _("Can't delete room in WuBook"), err.data['message']) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't delete room in WuBook"), + channel_message=err.data['message']) @api.model def create_room(self, binding): @@ -46,10 +54,14 @@ class HotelRoomTypeExporter(Component): binding.total_rooms_count ) binding.write({ - 'channel_room_id': external_id, + 'external_id': external_id, 'channel_short_code': short_code, }) except ChannelConnectorError as err: - self.create_issue('room', _("Can't delete room in WuBook"), err.data['message']) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't create room in WuBook"), + channel_message=err.data['message']) else: self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/hotel_room_type/importer.py b/hotel_channel_connector/models/hotel_room_type/importer.py index d1728e3f6..9a471dcd3 100644 --- a/hotel_channel_connector/models/hotel_room_type/importer.py +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -35,7 +35,7 @@ class HotelRoomTypeImporter(Component): for room in results: map_record = room_mapper.map_record(room) room_bind = channel_room_type_obj.search([ - ('channel_room_id', '=', room['id']) + ('external_id', '=', room['id']) ], limit=1) if room_bind: room_bind.with_context({'wubook_action': False}).write(map_record.values()) @@ -43,7 +43,11 @@ class HotelRoomTypeImporter(Component): room_bind = channel_room_type_obj.with_context({'wubook_action': False}).create( map_record.values(for_create=True)) except ChannelConnectorError as err: - self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't import rooms from WuBook"), + channel_message=err.data['message']) return count @@ -69,8 +73,11 @@ class HotelRoomTypeImporter(Component): 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) + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message=_("Can't fetch rooms values from WuBook"), + channel_message=err.data['message'], dfrom=dfrom, dto=dto) return False return True @@ -158,7 +165,7 @@ class HotelRoomTypeImportMapper(Component): _apply_on = 'channel.hotel.room.type' direct = [ - ('id', 'channel_room_id'), + ('id', 'externa_id'), ('shortname', 'channel_short_code'), ('occupancy', 'ota_capacity'), ('price', 'list_price'), diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 17c533951..7210008e6 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -51,10 +51,10 @@ class ChannelHotelRoomTypeAvailability(models.Model): @job(default_channel='root.channel') @api.model - def import_availability(self, backend, date_from, date_to): + def import_availability(self, backend): with backend.work_on(self._name) as work: importer = work.component(usage='hotel.room.type.availability.importer') - return importer.get_availability(date_from, date_to) + return importer.get_availability(backend.avail_from, backend.avail_to) @job(default_channel='root.channel') @api.model diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py index c98747a03..8eb17c2fc 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -32,9 +32,10 @@ class HotelRoomTypeAvailabilityExporter(Component): }) except ChannelConnectorError as err: self.create_issue( - 'room', - _("Can't update availability in WuBook"), - err.data['message']) + backend=self.backend_adapter.id, + section='avail', + internal_message=_("Can't update availability in WuBook"), + channel_message=err.data['message']) def push_availability(self): channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([ @@ -69,6 +70,7 @@ class HotelRoomTypeAvailabilityExporter(Component): self.backend_adapter.update_availability(avails) except ChannelConnectorError as err: self.create_issue( - 'room', - _("Can't update availability in WuBook"), - err.data['message']) + backend=self.backend_adapter.id, + section='avail', + internal_message=_("Can't update availability in WuBook"), + channel_message=err.data['message']) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/importer.py b/hotel_channel_connector/models/hotel_room_type_availability/importer.py index 0aba55d20..843fdd678 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/importer.py @@ -66,9 +66,10 @@ class HotelRoomTypeAvailabilityImporter(Component): iter_day += timedelta(days=1) except ChannelConnectorError as err: self.create_issue( - 'room', - _("Can't import availability from WuBook"), - err.data['message']) + backend=self.backend_adapter.id, + section='avail', + internal_message=_("Can't import availability from WuBook"), + channel_message=err.data['message']) return count diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py b/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py index 06e54858b..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py @@ -3,3 +3,4 @@ from . import common from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/common.py b/hotel_channel_connector/models/hotel_room_type_restriction/common.py index ed2c2befe..d0b5908d6 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/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 ChannelHotelRoomTypeRestriction(models.Model): _name = 'channel.hotel.room.type.restriction' @@ -17,57 +19,43 @@ class ChannelHotelRoomTypeRestriction(models.Model): string='Hotel Virtual Room Restriction', required=True, ondelete='cascade') - channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid') - is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan') @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def create_plan(self): self.ensure_one() - if self._context.get('channel_action', True): + if not self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - channel_plan_id = adapter.create_rplan(self.name) - if channel_plan_id: - self.channel_plan_id = channel_plan_id - except ValidationError as e: - self.create_issue('room', "Can't create restriction plan on channel", "sss") + exporter = work.component(usage='hotel.room.type.restriction.exporter') + exporter.create_rplan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def update_plan_name(self): self.ensure_one() - if self._context.get('channel_action', True): + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.rename_rplan(self.channel_plan_id, self.name) - except ValidationError as e: - self.create_issue('room', "Can't update restriction plan name on channel", "sss") + exporter = work.component(usage='hotel.room.type.restriction.exporter') + exporter.rename_rplan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def delete_plan(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.delete_rplan(self.channel_plan_id) - except ValidationError as e: - self.create_issue('room', "Can't delete restriction plan on channel", "sss") + exporter = work.component(usage='hotel.room.type.restriction.exporter') + exporter.delete_rplan(self) @job(default_channel='root.channel') - @api.multi - def import_restriction_plans(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_restriction_plans() + @api.model + def import_restriction_plans(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='hotel.room.type.restriction.importer') + return importer.import_restriction_plans() class HotelRoomTypeRestriction(models.Model): _inherit = 'hotel.room.type.restriction' @@ -86,7 +74,7 @@ class HotelRoomTypeRestriction(models.Model): for name in org_names: restriction_id = room_type_restriction_obj.browse(name[0]) if any(restriction_id.channel_bind_ids) and \ - restriction_id.channel_bind_ids[0].channel_plan_id: + restriction_id.channel_bind_ids[0].external_id: names.append(( name[0], '%s (%s Backend)' % (name[1], @@ -96,6 +84,33 @@ class HotelRoomTypeRestriction(models.Model): names.append((name[0], name[1])) return names +class HotelRoomTypeRestrictionAdapter(Component): + _name = 'channel.hotel.room.type.restriction.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.room.type.restriction' + + def rplan_rplans(self): + return super(HotelRoomTypeRestrictionAdapter, self).rplan_rplans() + + def create_rplan(self, name): + return super(HotelRoomTypeRestrictionAdapter, self).create_rplan(name) + + def delete_rplan(self, external_id): + return super(HotelRoomTypeRestrictionAdapter, self).delete_rplan(external_id) + + def rename_rplan(self, external_id, new_name): + return super(HotelRoomTypeRestrictionAdapter, self).rename_rplan(external_id, new_name) + +class BindingHotelRoomTypeListener(Component): + _name = 'binding.hotel.room.type.restriction.listener' + _inherit = 'base.connector.listener' + _apply_on = ['hotel.room.type.restriction'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if any(record.channel_bind_ids) and 'name' in fields: + record.channel_bind_ids[0].update_plan_name() + class ChannelBindingHotelRoomTypeRestrictionListener(Component): _name = 'channel.binding.hotel.room.type.restriction.listener' _inherit = 'base.connector.listener' @@ -103,13 +118,13 @@ class ChannelBindingHotelRoomTypeRestrictionListener(Component): @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_plan() + record.create_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_unlink(self, record, fields=None): - record.with_delay(priority=20).delete_plan() + record.delete_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'name' in fields: - record.with_delay(priority=20).update_plan_name() + record.update_plan_name() diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py new file mode 100644 index 000000000..b60878830 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py @@ -0,0 +1,52 @@ +# 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 HotelRoomTypeRestrictionExporter(Component): + _name = 'channel.hotel.room.type.restriction.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type.restriction'] + _usage = 'hotel.room.type.restriction.exporter' + + @api.model + def rename_rplan(self, binding): + try: + return self.backend_adapter.rename_rplan( + binding.external_id, + binding.name) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't modify restriction plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def delete_rplan(self, binding): + try: + return self.backend_adapter.delete_rplan(binding.external_id) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't delete restriction plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def create_rplan(self, binding): + try: + external_id = self.backend_adapter.create_rplan(binding.name) + binding.external_id = external_id + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't create restriction plan in WuBook"), + channel_message=err.data['message']) + else: + self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py index 33b15b3a4..a328904bf 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -18,6 +18,36 @@ class HotelRoomTypeRestrictionImporter(Component): _apply_on = ['channel.hotel.room.type.restriction'] _usage = 'hotel.room.type.restriction.importer' + @api.model + def import_restriction_plans(self): + count = 0 + try: + results = self.backend_adapter.rplan_rplans() + channel_restriction_obj = self.env['channel.hotel.room.type.restriction'] + restriction_mapper = self.component(usage='import.mapper', + model_name='channel.hotel.room.type.restriction') + for plan in results: + plan_record = restriction_mapper.map_record(plan) + plan_bind = channel_restriction_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_restriction_obj.with_context({ + 'wubook_action': False, + 'rules': plan.get('rules'), + }).create(plan_record.values()) + else: + plan_bind.with_context({'wubook_action': False}).write( + plan_record.values(for_create=True)) + count = count + 1 + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='rplan', + internal_message=_("Can't fetch restriction plans from wubook"), + channel_message=err.data['message']) + return count + class HotelRoomTypeRestrictionImportMapper(Component): _name = 'channel.hotel.room.type.restriction.import.mapper' @@ -25,11 +55,8 @@ class HotelRoomTypeRestrictionImportMapper(Component): _apply_on = 'channel.hotel.room.type.restriction' direct = [ - ('no_ota', 'no_ota'), - ('booked', 'booked'), - ('avail', 'avail'), - ('room_type_id', 'room_type_id'), - ('date', 'date') + ('name', 'name'), + ('id', 'external_id'), ] @mapping diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py index 2f79bfc38..c01c39197 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -26,6 +26,16 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): self.ensure_one() self.channel_pushed = status + @job(default_channel='root.channel') + @api.model + def import_restriction_values(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='hotel.room.type.restriction.item.importer') + return importer.import_restriction_values( + backend.restriction_from, + backend.restriction_to, + channel_restr_id=backend.restriction_id) + class HotelRoomTypeRestrictionItem(models.Model): _inherit = 'hotel.room.type.restriction.item' @@ -34,15 +44,26 @@ class HotelRoomTypeRestrictionItem(models.Model): inverse_name='odoo_id', string='Hotel Channel Connector Bindings') +class HotelRoomTypeRestrictionItemAdapter(Component): + _name = 'channel.hotel.room.type.restriction.item.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.room.type.restriction.item' + + def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id): + return super(HotelRoomTypeRestrictionItemAdapter, self).wired_rplan_get_rplan_values( + date_from, + date_to, + channel_restriction_plan_id) + class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): - _name = 'channel.binding.hotel.room.type.restriction.listener' + _name = 'channel.binding.hotel.room.type.restriction.item.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.restriction'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_create(self, record, fields=None): - record.update_channel_pushed(False) + return True @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - record.update_channel_pushed(False) + return True diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py index a50a8868d..426e11028 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py @@ -2,11 +2,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from datetime import timedelta +from datetime import datetime, timedelta 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, only_create -from odoo.addons.hotel import date_utils +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT from odoo import fields, api, _ _logger = logging.getLogger(__name__) @@ -17,6 +19,69 @@ class HotelRoomTypeRestrictionImporter(Component): _apply_on = ['channel.hotel.room.type.restriction.item'] _usage = 'hotel.room.type.restriction.item.importer' + # FIXME: Reduce Nested Loops!! + @api.model + def _generate_restriction_items(self, plan_restrictions): + channel_hotel_room_type_obj = self.env['channel.hotel.room.type'] + channel_reserv_restriction_obj = self.env['channel.hotel.room.type.restriction'] + channel_restriction_item_obj = self.env['channel.hotel.room.type.restriction.item'] + restriction_item_mapper = self.component( + usage='import.mapper', + model_name='channel.hotel.room.type.restriction.item') + _logger.info("==[CHANNEL->ODOO]==== RESTRICTIONS ==") + _logger.info(plan_restrictions) + for k_rpid, v_rpid in plan_restrictions.items(): + channel_restriction_id = channel_reserv_restriction_obj.search([ + ('external_id', '=', k_rpid) + ], limit=1) + if channel_restriction_id: + for k_rid, v_rid in v_rpid.items(): + channel_room_type = channel_hotel_room_type_obj.search([ + ('external_id', '=', k_rid) + ], limit=1) + if channel_room_type: + for item in v_rid: + map_record = restriction_item_mapper.map_record(item) + date_dt = datetime.strptime(item['date'], DEFAULT_WUBOOK_DATE_FORMAT) + date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + channel_restriction_item = channel_restriction_item_obj.search([ + ('restriction_id', '=', channel_restriction_id.odoo_id.id), + ('date', '=', date_str), + ('applied_on', '=', '0_room_type'), + ('room_type_id', '=', channel_room_type.odoo_id.id) + ], limit=1) + item.update({ + 'date': date_str, + 'room_type_id': channel_room_type.odoo_id.id, + 'restriction_id': channel_restriction_id.odoo_id.id, + }) + if channel_restriction_item: + channel_restriction_item.with_context({ + 'wubook_action': False}).write(map_record.values()) + else: + channel_restriction_item_obj.with_context({ + 'wubook_action': False + }).create(map_record.values(for_create=True)) + + @api.model + def import_restriction_values(self, date_from, date_to, channel_restr_id=False): + channel_restr_plan_id = channel_restr_id.external_id if channel_restr_id else False + try: + results = self.backend_adapter.wired_rplan_get_rplan_values( + date_from, + date_to, + int(channel_restr_plan_id)) + if any(results): + self._generate_restriction_items(results) + except ChannelConnectorError as err: + self.create_issue( + section='restriction', + internal_message=_("Can't fetch plan restrictions from wubook"), + channel_message=err.data['message'], + channel_object_id=channel_restr_plan_id, + dfrom=date_from, dto=date_to) + return False + class HotelRoomTypeRestrictionItemImportMapper(Component): _name = 'channel.hotel.room.type.restriction.item.import.mapper' _inherit = 'channel.import.mapper' @@ -30,7 +95,6 @@ class HotelRoomTypeRestrictionItemImportMapper(Component): ('closed', 'closed'), ('closed_departure', 'closed_departure'), ('closed_arrival', 'closed_arrival'), - ('room_type_id', 'room_type_id'), ('date', 'date'), ] @@ -39,6 +103,14 @@ class HotelRoomTypeRestrictionItemImportMapper(Component): def applied_on(self, record): return {'applied_on': '0_room_type'} + @mapping + def room_type_id(self, record): + return {'room_type_id': record['room_type_id']} + + @mapping + def restriction_id(self, record): + return {'restriction_id': record['restriction_id']} + @mapping def backend_id(self, record): return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/models/inherited_product_pricelist_item.py b/hotel_channel_connector/models/inherited_product_pricelist_item.py deleted file mode 100644 index 49934f850..000000000 --- a/hotel_channel_connector/models/inherited_product_pricelist_item.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models, fields, api, _ -from openerp.exceptions import ValidationError - - -class ProductPricelistItem(models.Model): - _inherit = 'product.pricelist.item' - - is_channel_pushed = fields.Boolean("WuBook Pushed", default=True, readonly=True, - old_name='wpushed') - is_daily_plan = fields.Boolean(related='pricelist_id.channel_bind_ids.is_daily_plan', readonly=True, - old_name='wdaily') - - @api.constrains('fixed_price') - def _check_fixed_price(self): - room_type_obj = self.env['hotel.room.type'] - for record in self: - room_type = room_type_obj.search([ - ('product_id.product_tmpl_id', '=', record.product_tmpl_id.id) - ], limit=1) - if room_type and room_type.channel_room_id and record.compute_price == 'fixed' \ - and record.fixed_price <= 0.0: - raise ValidationError(_("Price need be greater than zero")) - - @api.model - def create(self, vals): - if self._context.get('channel_action', True): - pricelist_id = self.env['product.pricelist'].browse( - vals.get('pricelist_id')) - room_type = self.env['hotel.room.type'].search([ - ('product_id.product_tmpl_id', '=', - vals.get('product_tmpl_id')), - ('channel_room_id', '!=', False) - ]) - if room_type and pricelist_id.channel_plan_id: - vals.update({'is_channel_pushed': False}) - return super(ProductPricelistItem, self).create(vals) - - @api.multi - def write(self, vals): - if self._context.get('channel_action', True): - prices_obj = self.env['product.pricelist'] - for record in self: - pricelist_id = prices_obj.browse(vals.get('pricelist_id')) if \ - vals.get('pricelist_id') else record.pricelist_id - product_tmpl_id = vals.get('product_tmpl_id') or \ - record.product_tmpl_id.id - room_type = self.env['hotel.room.type'].search([ - ('product_id.product_tmpl_id', '=', product_tmpl_id), - ('channel_room_id', '!=', False), - ]) - if room_type and pricelist_id.channel_plan_id: - vals.update({'is_channel_pushed': False}) - return super(ProductPricelistItem, self).write(vals) diff --git a/hotel_channel_connector/models/product_pricelist/common.py b/hotel_channel_connector/models/product_pricelist/common.py index 976e0b338..3430fcc87 100644 --- a/hotel_channel_connector/models/product_pricelist/common.py +++ b/hotel_channel_connector/models/product_pricelist/common.py @@ -34,7 +34,10 @@ class ChannelProductPricelist(models.Model): if channel_plan_id: self.channel_plan_id = channel_plan_id except ValidationError as e: - self.create_issue('room', "Can't create plan on channel", "sss") + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message="Can't create plan on channel") @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -49,7 +52,10 @@ class ChannelProductPricelist(models.Model): self.channel_plan_id, self.name) except ValidationError as e: - self.create_issue('room', "Can't update plan name on channel", "sss") + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message="Can't update plan name on channel") @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -62,7 +68,10 @@ class ChannelProductPricelist(models.Model): try: adapter.delete_plan(self.channel_plan_id) except ValidationError as e: - self.create_issue('room', "Can't delete plan on channel", "sss") + self.create_issue( + backend=self.backend_adapter.id, + section='room', + internal_message="Can't delete plan on channel") @job(default_channel='root.channel') @api.multi diff --git a/hotel_channel_connector/models/product_pricelist_item/__init__.py b/hotel_channel_connector/models/product_pricelist_item/__init__.py new file mode 100644 index 000000000..257ab04fc --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist_item/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import common diff --git a/hotel_channel_connector/models/product_pricelist_item/common.py b/hotel_channel_connector/models/product_pricelist_item/common.py new file mode 100644 index 000000000..f80d542c5 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist_item/common.py @@ -0,0 +1,30 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +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 + +class ChannelProductPricelistItem(models.Model): + _name = 'channel.product.pricelist.item' + _inherit = 'channel.binding' + _inherits = {'product.pricelist.item': 'odoo_id'} + _description = 'Channel Product Pricelist Item' + + odoo_id = fields.Many2one(comodel_name='product.pricelist.item', + string='Pricelist Item', + required=True, + ondelete='cascade') + + channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, + old_name='wpushed') + +class ProductPricelistItem(models.Model): + _inherit = 'product.pricelist.item' + + channel_bind_ids = fields.One2many( + comodel_name='channel.product.pricelist.item', + inverse_name='odoo_id', + string='Hotel Channel Connector Bindings') diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 1f06b3c7b..3e2dcb8c5 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -95,6 +95,27 @@ string="Import in background"/> + + + + @@ -107,6 +128,17 @@ + + + + + + + + + + + diff --git a/hotel_channel_connector/views/channel_hotel_reservation_views.xml b/hotel_channel_connector/views/channel_hotel_reservation_views.xml index f75d53daa..bf6057427 100644 --- a/hotel_channel_connector/views/channel_hotel_reservation_views.xml +++ b/hotel_channel_connector/views/channel_hotel_reservation_views.xml @@ -7,7 +7,7 @@
- + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_availability_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_availability_views.xml index d55fa121f..c8e34c01c 100644 --- a/hotel_channel_connector/views/channel_hotel_room_type_availability_views.xml +++ b/hotel_channel_connector/views/channel_hotel_room_type_availability_views.xml @@ -6,6 +6,10 @@ channel.hotel.room.type.availability + + + + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_restriction_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_restriction_views.xml index 662cc317c..d8956faad 100644 --- a/hotel_channel_connector/views/channel_hotel_room_type_restriction_views.xml +++ b/hotel_channel_connector/views/channel_hotel_room_type_restriction_views.xml @@ -7,8 +7,11 @@ - - + + + + + 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 846964b59..5cc063650 100644 --- a/hotel_channel_connector/views/channel_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/channel_hotel_room_type_views.xml @@ -7,10 +7,11 @@
+ - + diff --git a/hotel_channel_connector/views/channel_ota_info_views.xml b/hotel_channel_connector/views/channel_ota_info_views.xml index 8dcc4dff8..168957bd8 100644 --- a/hotel_channel_connector/views/channel_ota_info_views.xml +++ b/hotel_channel_connector/views/channel_ota_info_views.xml @@ -8,6 +8,10 @@ + + + + diff --git a/hotel_channel_connector/views/channel_product_pricelist_item_views.xml b/hotel_channel_connector/views/channel_product_pricelist_item_views.xml new file mode 100644 index 000000000..b8cb4554d --- /dev/null +++ b/hotel_channel_connector/views/channel_product_pricelist_item_views.xml @@ -0,0 +1,27 @@ + + + + + channel.product.pricelist.item.form + channel.product.pricelist.item + + + + + + + + + + + + channel.hotel.product.pricelist.item.tree + channel.product.pricelist.item + + + + + + + + diff --git a/hotel_channel_connector/views/channel_product_pricelist_views.xml b/hotel_channel_connector/views/channel_product_pricelist_views.xml index 1e64681d2..5f23c0c21 100644 --- a/hotel_channel_connector/views/channel_product_pricelist_views.xml +++ b/hotel_channel_connector/views/channel_product_pricelist_views.xml @@ -7,14 +7,18 @@
- + + + + +
- + channel.hotel.product.pricelist.tree channel.product.pricelist diff --git a/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml b/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml index 5732014df..10e854da5 100644 --- a/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml +++ b/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml @@ -18,6 +18,9 @@ class="oe_stat_button" icon="fa-warning" attrs="{'invisible':['|', ['section', '!=', 'reservation'], ['channel_object_id', '=', False]]}"/> + + + @@ -42,6 +45,7 @@ tree + @@ -58,6 +62,7 @@ hotel.channel.connector.issue + diff --git a/hotel_channel_connector/views/inherited_hotel_reservation_views.xml b/hotel_channel_connector/views/inherited_hotel_reservation_views.xml index 143f9247c..ac6207ab6 100644 --- a/hotel_channel_connector/views/inherited_hotel_reservation_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_reservation_views.xml @@ -23,7 +23,7 @@ - + diff --git a/hotel_channel_connector/views/inherited_hotel_room_type_availability_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_availability_views.xml index c93b12a18..d7f17c58e 100644 --- a/hotel_channel_connector/views/inherited_hotel_room_type_availability_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_availability_views.xml @@ -8,7 +8,7 @@ - + diff --git a/hotel_channel_connector/views/inherited_hotel_room_type_restriction_item_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_item_views.xml new file mode 100644 index 000000000..f28fe7e7f --- /dev/null +++ b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_item_views.xml @@ -0,0 +1,24 @@ + + + + + hotel.room.type.restriction.item + + + + + + + + + + + + + + + + + + + diff --git a/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml index 86ef97d11..d640d760f 100644 --- a/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml @@ -5,11 +5,19 @@ 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 e75a7a7c0..70251b4b6 100644 --- a/hotel_channel_connector/views/inherited_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_views.xml @@ -7,7 +7,7 @@ - + diff --git a/hotel_channel_connector/views/inherited_product_pricelist_item_views.xml b/hotel_channel_connector/views/inherited_product_pricelist_item_views.xml index 696e56773..caef730f9 100644 --- a/hotel_channel_connector/views/inherited_product_pricelist_item_views.xml +++ b/hotel_channel_connector/views/inherited_product_pricelist_item_views.xml @@ -5,12 +5,18 @@ product.pricelist.item - - - - - - {'readonly': [('is_daily_plan', '=', True)]} + + + + + + + + + + + +
diff --git a/hotel_channel_connector/views/inherited_product_pricelist_views.xml b/hotel_channel_connector/views/inherited_product_pricelist_views.xml index ab4882128..0f1018f0b 100644 --- a/hotel_channel_connector/views/inherited_product_pricelist_views.xml +++ b/hotel_channel_connector/views/inherited_product_pricelist_views.xml @@ -5,14 +5,19 @@ product.pricelist - -
-
-
- - - + + + + + + + + + + + + +
From 52ff989dc4d33aace3f7232c8afbc46165c6f683 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 6 Nov 2018 00:56:22 +0100 Subject: [PATCH 5/7] [WIP] Channel Connector: Import Pricelist --- hotel_calendar/models/inherited_ir_default.py | 12 +- hotel_calendar/views/res_config_views.xml | 34 +++--- hotel_channel_connector/__manifest__.py | 3 +- .../components/backend_adapter.py | 4 +- .../models/channel_backend/common.py | 19 ++++ .../models/hotel_channel_connector_issue.py | 8 +- .../models/hotel_room_type/common.py | 8 +- .../models/hotel_room_type/importer.py | 2 +- .../hotel_room_type_availability/common.py | 14 ++- .../hotel_room_type_availability/exporter.py | 2 +- .../hotel_room_type_restriction/importer.py | 6 +- .../__init__.py | 1 + .../common.py | 48 +++++--- .../exporter.py | 107 ++++++++++++++++++ .../models/product_pricelist/__init__.py | 2 + .../models/product_pricelist/common.py | 93 +++++++-------- .../models/product_pricelist/exporter.py | 52 +++++++++ .../models/product_pricelist/importer.py | 65 +++++++++++ .../views/channel_connector_backend_views.xml | 18 +++ ...hotel_room_type_restriction_item_views.xml | 30 +++++ 20 files changed, 426 insertions(+), 102 deletions(-) create mode 100644 hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py create mode 100644 hotel_channel_connector/models/product_pricelist/exporter.py create mode 100644 hotel_channel_connector/models/product_pricelist/importer.py create mode 100644 hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml diff --git a/hotel_calendar/models/inherited_ir_default.py b/hotel_calendar/models/inherited_ir_default.py index 60bc254a0..ce5cf8f98 100644 --- a/hotel_calendar/models/inherited_ir_default.py +++ b/hotel_calendar/models/inherited_ir_default.py @@ -25,10 +25,10 @@ class IrDefault(models.Model): fixed_price = pitem.fixed_price room_type = room_type_obj.search([ ('product_id.product_tmpl_id', '=', product_tmpl_id), - ('date_start', '>=', fields.Date.today()) ], limit=1) - room_pr_cached_obj.create({ - 'room_type_id': room_type.id, - 'date': date_start, - 'price': fixed_price, - }) + if room_type: + room_pr_cached_obj.create({ + 'room_id': room_type.id, + 'date': date_start, + 'price': fixed_price, + }) diff --git a/hotel_calendar/views/res_config_views.xml b/hotel_calendar/views/res_config_views.xml index a3549f08b..0047838ad 100644 --- a/hotel_calendar/views/res_config_views.xml +++ b/hotel_calendar/views/res_config_views.xml @@ -8,24 +8,22 @@ - -
-

Calendar colors

-
-
- - - - - -
-
- - - - - -
+ +

Calendar colors

+
+
+ + + + + +
+
+ + + + +
diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 77b4d51f9..b478791b1 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -22,6 +22,7 @@ 'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_availability.xml', 'views/general.xml', + 'views/hotel_channel_connector_issue_views.xml', 'views/inherited_hotel_reservation_views.xml', 'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_type_availability_views.xml', @@ -32,11 +33,11 @@ 'views/inherited_hotel_room_type_restriction_item_views.xml', 'views/inherited_res_partner_views.xml', 'views/channel_ota_info_views.xml', - 'views/hotel_channel_connector_issue_views.xml', 'views/channel_hotel_reservation_views.xml', 'views/channel_hotel_room_type_views.xml', 'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_restriction_views.xml', + 'views/channel_hotel_room_type_restriction_item_views.xml', 'views/channel_product_pricelist_views.xml', 'views/channel_product_pricelist_item_views.xml', 'views/channel_connector_backend_views.xml', diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index 930b1dd92..07d2058ba 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -441,7 +441,7 @@ class WuBookAdapter(AbstractComponent): return results def update_plan_periods(self, channel_plan_id, periods): - rcode, results = self.SERVER.update_plan_periods( + rcode, results = self._server.update_plan_periods( self._session_info[0], self._session_info[1], channel_plan_id, @@ -454,7 +454,7 @@ class WuBookAdapter(AbstractComponent): return results def get_pricing_plans(self): - rcode, results = self.SERVER.get_pricing_plans( + rcode, results = self._server.get_pricing_plans( self._session_info[0], self._session_info[1]) if rcode != 0: diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 1901bfb36..32feb4c9a 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -39,6 +39,11 @@ class ChannelBackend(models.Model): restriction_id = fields.Many2one('channel.hotel.room.type.restriction', 'Channel Restriction') + pricelist_from = fields.Date('Pricelist From') + pricelist_to = fields.Date('Pricelist To') + pricelist_id = fields.Many2one('channel.product.pricelist', + 'Channel Product Pricelist') + issue_ids = fields.One2many('hotel.channel.connector.issue', 'backend_id', string='Issues', @@ -98,6 +103,20 @@ class ChannelBackend(models.Model): channel_hotel_restr_item_obj.import_restriction_values(backend) return True + @api.multi + def push_restriction(self): + channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item'] + for backend in self: + channel_hotel_restr_item_obj.push_restriction(backend) + return True + + @api.multi + def import_pricelist_plans(self): + channel_product_pricelist_obj = self.env['channel.product.pricelist'] + for backend in self: + channel_product_pricelist_obj.import_price_plans(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): diff --git a/hotel_channel_connector/models/hotel_channel_connector_issue.py b/hotel_channel_connector/models/hotel_channel_connector_issue.py index fba387289..e9cba78b0 100644 --- a/hotel_channel_connector/models/hotel_channel_connector_issue.py +++ b/hotel_channel_connector/models/hotel_channel_connector_issue.py @@ -11,6 +11,7 @@ class HotelChannelConnectorIssue(models.Model): backend_id = fields.Many2one('channel.backend', 'Restriction Plan', + required=True, ondelete='cascade', index=True) @@ -46,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model): reserv_ids.append(record.channel_object_id) record.to_read = False if any(reserv_ids): - res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids) - if not res: - raise ValidationError( - ("Can't mark reservation as readed in Channel!")) + with self.backend_id.work_on('channel.hotel.reservation') as work: + exporter = work.component(usage='hotel.reservation.exporter') + return exporter.mark_bookings(reserv_ids) @api.model def _needaction_domain_get(self): diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index ca490e8dd..fb2a84ec7 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -101,9 +101,7 @@ class HotelRoomType(models.Model): self._compute_capacity() @api.multi - def get_restrictions(self, date): - restriction_plan_id = int(self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_restrictions_id')) + def get_restrictions(self, date, restriction_plan_id): self.ensure_one() restriction = self.env['hotel.room.type.restriction.item'].search([ ('date', '=', date), @@ -141,7 +139,9 @@ class BindingHotelRoomTypeListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields: + if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \ + 'room_ids' in fields: + # FIXME: Supossed that only exists one channel connector per record record.channel_bind_ids[0].modify_room() # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) diff --git a/hotel_channel_connector/models/hotel_room_type/importer.py b/hotel_channel_connector/models/hotel_room_type/importer.py index 9a471dcd3..185ea9acf 100644 --- a/hotel_channel_connector/models/hotel_room_type/importer.py +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -165,7 +165,7 @@ class HotelRoomTypeImportMapper(Component): _apply_on = 'channel.hotel.room.type' direct = [ - ('id', 'externa_id'), + ('id', 'external_id'), ('shortname', 'channel_short_code'), ('occupancy', 'ota_capacity'), ('price', 'list_price'), diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 7210008e6..9a4f78e87 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -164,14 +164,22 @@ class BindingHotelRoomTypeAvailabilityListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'avail' in fields: - for binding in record.channel_bind_ids: - binding.channel_pushed = False + record.channel_bind_ids.write({'channel_pushed': False}) class ChannelBindingHotelRoomTypeAvailabilityListener(Component): _name = 'channel.binding.hotel.room.type.availability.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.availability'] + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if 'avail' in fields: + record.channel_pushed = False + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_fix_channel_availability(self, record, fields=None): - record.with_delay(priority=20).update_availability() + record.update_availability() diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py index 8eb17c2fc..589d7d27a 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -20,7 +20,7 @@ class HotelRoomTypeAvailabilityExporter(Component): if any(binding.room_type_id.channel_bind_ids): try: sday_dt = fields.Date.from_string(binding.date) - # Supossed that only exists one channel connector per record + # FIXME: Supossed that only exists one channel connector per record binding.channel_pushed = True return self.backend_adapter.update_availability({ 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py index a328904bf..ac687dbca 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -35,15 +35,15 @@ class HotelRoomTypeRestrictionImporter(Component): channel_restriction_obj.with_context({ 'wubook_action': False, 'rules': plan.get('rules'), - }).create(plan_record.values()) + }).create(plan_record.values(for_create=True)) else: plan_bind.with_context({'wubook_action': False}).write( - plan_record.values(for_create=True)) + plan_record.values()) count = count + 1 except ChannelConnectorError as err: self.create_issue( backend=self.backend_adapter.id, - section='rplan', + section='restriction', internal_message=_("Can't fetch restriction plans from wubook"), channel_message=err.data['message']) return count diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py index 06e54858b..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py @@ -3,3 +3,4 @@ from . import common from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py index c01c39197..5377b8cef 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -20,12 +20,6 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, old_name='wpushed') - @job(default_channel='root.channel') - @api.multi - def update_channel_pushed(self, status): - self.ensure_one() - self.channel_pushed = status - @job(default_channel='root.channel') @api.model def import_restriction_values(self, backend): @@ -36,6 +30,13 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): backend.restriction_to, channel_restr_id=backend.restriction_id) + @job(default_channel='root.channel') + @api.model + def push_restriction(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='hotel.room.type.restriction.item.exporter') + return exporter.push_restriction() + class HotelRoomTypeRestrictionItem(models.Model): _inherit = 'hotel.room.type.restriction.item' @@ -55,15 +56,34 @@ class HotelRoomTypeRestrictionItemAdapter(Component): date_to, channel_restriction_plan_id) -class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): - _name = 'channel.binding.hotel.room.type.restriction.item.listener' +class BindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'binding.hotel.room.type.restriction.item.listener' _inherit = 'base.connector.listener' - _apply_on = ['channel.hotel.room.type.restriction'] - - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_create(self, record, fields=None): - return True + _apply_on = ['hotel.room.type.restriction.item'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - return True + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_bind_ids.write({'channel_pushed': False}) + +class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'channel.binding.hotel.room.type.restriction.item.listener' + _inherit = 'base.connector.listener' + _apply_on = ['channel.hotel.room.type.restriction.item'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_pushed = False diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py new file mode 100644 index 000000000..b8788aaf1 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py @@ -0,0 +1,107 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + +class HotelRoomTypeRestrictionItemExporter(Component): + _name = 'channel.hotel.room.type.restriction.item.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type.restriction.item'] + _usage = 'hotel.room.type.restriction.item.exporter' + + @api.model + def update_restriction(self, binding): + if any(binding.restriction_id.channel_bind_ids): + try: + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_rplan_values( + binding.restriction_id.channel_bind_ids[0].external_id, + binding.date, + { + 'min_stay': binding.min_stay or 0, + 'min_stay_arrival': binding.min_stay_arrival or 0, + 'max_stay': binding.max_stay or 0, + 'max_stay_arrival': binding.max_stay_arrival or 0, + 'closed': binding.closed and 1 or 0, + 'closed_arrival': binding.closed_arrival and 1 or 0, + 'closed_departure': binding.closed_departure and 1 or 0, + }) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restriction in WuBook"), + channel_message=err.data['message']) + + @api.model + def push_restriction(self): + channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction'] + channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item'] + unpushed = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('date', '>=', fields.Date.today()) + ], order="date ASC") + if any(unpushed): + date_start = fields.Date.from_string(unpushed[0].date) + date_end = fields.Date.from_string(unpushed[-1].date) + days_diff = (date_end-date_start).days + 1 + restrictions = {} + channel_restr_plan_ids = channel_room_type_rest_obj.search([]) + for rp in channel_restr_plan_ids: + restrictions.update({rp.external_id: {}}) + unpushed_rp = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('restriction_id', '=', rp.odoo_id.id) + ]) + room_type_ids = unpushed_rp.mapped('room_type_id') + for room_type in room_type_ids: + if any(room_type.channel_bind_ids): + # FIXME: Supossed that only exists one channel connector per record + room_type_external_id = room_type.channel_bind_ids[0].external_id + restrictions[rp.external_id].update({ + room_type_external_id: [], + }) + for i in range(0, days_diff): + ndate_dt = date_start + timedelta(days=i) + restr = room_type.get_restrictions( + ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + rp.odoo_id.id) + if restr: + restrictions[rp.external_id][room_type_external_id].append({ + 'min_stay': restr.min_stay or 0, + 'min_stay_arrival': restr.min_stay_arrival or 0, + 'max_stay': restr.max_stay or 0, + 'max_stay_arrival': restr.max_stay_arrival or 0, + 'closed': restr.closed and 1 or 0, + 'closed_arrival': restr.closed_arrival and 1 or 0, + 'closed_departure': restr.closed_departure and 1 or 0, + }) + else: + restrictions[rp.external_id][room_type_external_id].append({}) + _logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==") + _logger.info(restrictions) + for k_res, v_res in restrictions.items(): + if any(v_res): + try: + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restrictions in WuBook"), + channel_message=err.data['message']) + unpushed.with_context({ + 'wubook_action': False}).write({'channel_pushed': True}) + return True diff --git a/hotel_channel_connector/models/product_pricelist/__init__.py b/hotel_channel_connector/models/product_pricelist/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/product_pricelist/__init__.py +++ b/hotel_channel_connector/models/product_pricelist/__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/product_pricelist/common.py b/hotel_channel_connector/models/product_pricelist/common.py index 3430fcc87..775bd698a 100644 --- a/hotel_channel_connector/models/product_pricelist/common.py +++ b/hotel_channel_connector/models/product_pricelist/common.py @@ -17,7 +17,6 @@ class ChannelProductPricelist(models.Model): string='Pricelist', required=True, ondelete='cascade') - channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid') is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan') @job(default_channel='root.channel') @@ -25,61 +24,37 @@ class ChannelProductPricelist(models.Model): @api.multi def create_plan(self): self.ensure_one() - if self._context.get('channel_action', True): + if not self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - channel_plan_id = adapter.create_plan(self.name, - self.is_daily_plan and 1 or 0) - if channel_plan_id: - self.channel_plan_id = channel_plan_id - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't create plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.create_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def update_plan_name(self): self.ensure_one() - if self._context.get('channel_action', True): + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.update_plan_name( - self.channel_plan_id, - self.name) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't update plan name on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.rename_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def delete_plan(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.delete_plan(self.channel_plan_id) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't delete plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.delete_plan(self) @job(default_channel='root.channel') - @api.multi - def import_price_plans(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_pricing_plans() + @api.model + def import_price_plans(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='product.pricelist.importer') + return importer.import_pricing_plans() class ProductPricelist(models.Model): _inherit = 'product.pricelist' @@ -92,19 +67,47 @@ class ProductPricelist(models.Model): @api.multi @api.depends('name') def name_get(self): - self.ensure_one() pricelist_obj = self.env['product.pricelist'] org_names = super(ProductPricelist, self).name_get() names = [] for name in org_names: priclist_id = pricelist_obj.browse(name[0]) if any(priclist_id.channel_bind_ids) and \ - priclist_id.channel_bind_ids[0].channel_plan_id: - names.append((name[0], '%s (Channel)' % name[1])) + priclist_id.channel_bind_ids[0].external_id: + names.append((name[0], '%s (%s Backend)' % ( + name[1], + priclist_id.channel_bind_ids[0].backend_id.name))) else: names.append((name[0], name[1])) return names +class ProductPricelistAdapter(Component): + _name = 'channel.product.pricelist.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.product.pricelist' + + def get_pricing_plans(self): + return super(ProductPricelistAdapter, self).get_pricing_plans() + + def create_plan(self, name): + return super(ProductPricelistAdapter, self).create_plan(name) + + def delete_plan(self, external_id): + return super(ProductPricelistAdapter, self).delete_plan(external_id) + + def rename_plan(self, external_id, new_name): + return super(ProductPricelistAdapter, self).rename_plan(external_id, new_name) + +class BindingProductPricelistListener(Component): + _name = 'binding.product.pricelist.listener' + _inherit = 'base.connector.listener' + _apply_on = ['product.pricelist'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if any(record.channel_bind_ids) and 'name' in fields: + record.channel_bind_ids[0].update_plan_name() + class ChannelBindingProductPricelistListener(Component): _name = 'channel.binding.product.pricelist.listener' _inherit = 'base.connector.listener' @@ -112,13 +115,13 @@ class ChannelBindingProductPricelistListener(Component): @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_plan() + record.create_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_unlink(self, record, fields=None): - record.with_delay(priority=20).delete_plan() + record.delete_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'name' in fields: - record.with_delay(priority=20).update_plan_name() + record.update_plan_name() diff --git a/hotel_channel_connector/models/product_pricelist/exporter.py b/hotel_channel_connector/models/product_pricelist/exporter.py new file mode 100644 index 000000000..45b028d84 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/exporter.py @@ -0,0 +1,52 @@ +# 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 ProductPricelistExporter(Component): + _name = 'channel.product.pricelist.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.exporter' + + @api.model + def rename_plan(self, binding): + try: + return self.backend_adapter.rename_plan( + binding.external_id, + binding.name) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't modify pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def delete_plan(self, binding): + try: + return self.backend_adapter.delete_plan(binding.external_id) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't delete pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def create_plan(self, binding): + try: + external_id = self.backend_adapter.create_plan(binding.name) + binding.external_id = external_id + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't create pricelist plan in WuBook"), + channel_message=err.data['message']) + else: + self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/product_pricelist/importer.py b/hotel_channel_connector/models/product_pricelist/importer.py new file mode 100644 index 000000000..74dabe6e8 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/importer.py @@ -0,0 +1,65 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +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, only_create +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + + +class ProductPricelistImporter(Component): + _name = 'channel.product.pricelist.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.importer' + + @api.model + def import_pricing_plans(self): + channel_product_listprice_obj = self.env['channel.product.pricelist'] + pricelist_mapper = self.component(usage='import.mapper', + model_name='channel.product.pricelist') + count = 0 + try: + results = self.backend_adapter.get_pricing_plans() + for plan in results: + if 'vpid' in plan: + continue # FIXME: Ignore Virtual Plans + plan_record = pricelist_mapper.map_record(plan) + plan_bind = channel_product_listprice_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_product_listprice_obj.with_context({ + 'wubook_action': False}).create(plan_record.values(for_create=True)) + else: + channel_product_listprice_obj.write(plan_record.values()) + count = count + 1 + except ChannelConnectorError as err: + self.create_issue( + section='pricelist', + internal_message=_("Can't get pricing plans from wubook"), + channel_message=err.data['message']) + return 0 + return count + + +class ProductPricelistMapper(Component): + _name = 'channel.product.pricelist.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.product.pricelist' + + direct = [ + ('id', 'external_id'), + ('name', 'name'), + ('daily', 'is_daily_plan'), + ] + + @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 3e2dcb8c5..2b947a5d0 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -116,6 +116,15 @@ string="Import in background"/>
+ + @@ -127,6 +136,15 @@ string="Export in background"/>
+ + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml new file mode 100644 index 000000000..da7933657 --- /dev/null +++ b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml @@ -0,0 +1,30 @@ + + + + + channel.hotel.room.type.restriction.item.form + channel.hotel.room.type.restriction.item + +
+ + + + + + + +
+
+
+ + + channel.hotel.room.type.restriction.item.tree + channel.hotel.room.type.restriction.item + + + + + + + +
From 044640990d6fc9e79a94e36562fe266cb8073867 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 6 Nov 2018 13:37:22 +0100 Subject: [PATCH 6/7] [WIP] Channel Connector: Pricelist Items --- hotel_channel_connector/components/core.py | 12 -- hotel_channel_connector/data/menus.xml | 6 - hotel_channel_connector/models/__init__.py | 1 - .../models/channel_backend/common.py | 13 +- .../models/channel_binding/common.py | 12 ++ .../models/channel_ota_info/common.py | 10 +- .../models/channel_ota_info/importer.py | 47 +++---- .../models/hotel_room_type/common.py | 37 +++++- .../models/hotel_room_type/exporter.py | 67 ++++------ .../models/hotel_room_type/importer.py | 61 +++------ .../hotel_room_type_availability/common.py | 32 ++++- .../hotel_room_type_availability/exporter.py | 38 ++---- .../hotel_room_type_availability/importer.py | 80 ++++++------ .../hotel_room_type_restriction/common.py | 37 +++++- .../hotel_room_type_restriction/exporter.py | 37 +----- .../hotel_room_type_restriction/importer.py | 45 +++---- .../common.py | 31 +++-- .../exporter.py | 50 +++----- .../importer.py | 27 ++-- .../models/product_pricelist/common.py | 37 +++++- .../models/product_pricelist/exporter.py | 36 +----- .../models/product_pricelist/importer.py | 38 +++--- .../models/product_pricelist_item/__init__.py | 2 + .../models/product_pricelist_item/common.py | 72 ++++++++++- .../models/product_pricelist_item/exporter.py | 106 ++++++++++++++++ .../models/product_pricelist_item/importer.py | 120 ++++++++++++++++++ hotel_channel_connector/models/res_config.py | 32 ----- .../views/channel_connector_backend_views.xml | 22 +++- .../views/channel_ota_info_views.xml | 7 - ...inherited_product_pricelist_item_views.xml | 1 + 30 files changed, 686 insertions(+), 430 deletions(-) create mode 100644 hotel_channel_connector/models/product_pricelist_item/exporter.py create mode 100644 hotel_channel_connector/models/product_pricelist_item/importer.py delete mode 100644 hotel_channel_connector/models/res_config.py diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py index 629108ea3..0400e7fba 100644 --- a/hotel_channel_connector/components/core.py +++ b/hotel_channel_connector/components/core.py @@ -9,18 +9,6 @@ class BaseHotelChannelConnectorComponent(AbstractComponent): _inherit = 'base.connector' _collection = 'channel.backend' - @api.model - def create_issue(self, **kwargs): - self.env['hotel.channel.connector.issue'].sudo().create({ - 'backend_id': kwargs.get('backend', False), - 'section': kwargs.get('section', False), - 'internal_message': kwargs.get('internal_message', False), - 'channel_object_id': kwargs.get('channel_object_id', False), - 'channel_message': kwargs.get('channel_message', False), - 'date_start': kwargs.get('dfrom', False), - 'date_end': kwargs.get('dto', False), - }) - class ChannelConnectorError(Exception): def __init__(self, message, data): super().__init__(message) diff --git a/hotel_channel_connector/data/menus.xml b/hotel_channel_connector/data/menus.xml index 06b13e837..3b6264619 100644 --- a/hotel_channel_connector/data/menus.xml +++ b/hotel_channel_connector/data/menus.xml @@ -13,12 +13,6 @@ parent="menu_channel_connector_root" action="action_channel_backend"/> - - 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( - backend=self.backend_adapter.id, - section='room', - internal_message=_("Can't fetch rooms values from WuBook"), - channel_message=err.data['message'], dfrom=dfrom, dto=dto) - return False - return True + 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) @api.model def _map_room_values_availability(self, day_vals, set_max_avail): diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 9a4f78e87..4fb1ea017 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -8,6 +8,7 @@ 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 +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel_channel_connector.components.backend_adapter import ( DEFAULT_WUBOOK_DATE_FORMAT) @@ -47,21 +48,42 @@ class ChannelHotelRoomTypeAvailability(models.Model): def update_availability(self, backend): with backend.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.availability.exporter') - return exporter.update_availability(self) + try: + return exporter.update_availability(self) + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='avail', + internal_message=_("Can't update availability in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @api.model def import_availability(self, backend): with backend.work_on(self._name) as work: importer = work.component(usage='hotel.room.type.availability.importer') - return importer.get_availability(backend.avail_from, backend.avail_to) + try: + return importer.get_availability(backend.avail_from, backend.avail_to) + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='avail', + internal_message=_("Can't import availability from WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @api.model def push_availability(self, backend): with backend.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.availability.exporter') - return exporter.push_availability() + try: + return exporter.push_availability() + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='avail', + internal_message=_("Can't update availability in WuBook"), + channel_message=err.data['message']) class HotelRoomTypeAvailability(models.Model): _inherit = 'hotel.room.type.availability' @@ -171,10 +193,6 @@ class ChannelBindingHotelRoomTypeAvailabilityListener(Component): _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.availability'] - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_create(self, record, fields=None): - record.channel_pushed = False - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'avail' in fields: diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py index 589d7d27a..3c5cfae81 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -18,24 +18,17 @@ class HotelRoomTypeAvailabilityExporter(Component): @api.model def update_availability(self, binding): if any(binding.room_type_id.channel_bind_ids): - try: - sday_dt = fields.Date.from_string(binding.date) - # FIXME: Supossed that only exists one channel connector per record - binding.channel_pushed = True - return self.backend_adapter.update_availability({ - 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, - 'days': [{ - 'date': sday_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - 'avail': binding.avail, - 'no_ota': binding.no_ota, - }], - }) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='avail', - internal_message=_("Can't update availability in WuBook"), - channel_message=err.data['message']) + sday_dt = fields.Date.from_string(binding.date) + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_availability({ + 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, + 'days': [{ + 'date': sday_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 'avail': binding.avail, + 'no_ota': binding.no_ota, + }], + }) def push_availability(self): channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([ @@ -66,11 +59,4 @@ class HotelRoomTypeAvailabilityExporter(Component): _logger.info("UPDATING AVAILABILITY IN WUBOOK...") _logger.info(avails) if any(avails): - try: - self.backend_adapter.update_availability(avails) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='avail', - internal_message=_("Can't update availability in WuBook"), - channel_message=err.data['message']) + self.backend_adapter.update_availability(avails) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/importer.py b/hotel_channel_connector/models/hotel_room_type_availability/importer.py index 843fdd678..a10301fd5 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/importer.py @@ -6,7 +6,7 @@ from datetime import date, timedelta 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, external_to_m2o +from odoo.addons.connector.components.mapper import mapping, only_create from odoo.addons.hotel import date_utils from odoo import fields, api, _ _logger = logging.getLogger(__name__) @@ -29,47 +29,40 @@ class HotelRoomTypeAvailabilityImporter(Component): dfrom_dt, dto_dt = dto_dt, dfrom_dt if dto_dt < now_dt: return True - count = 0 - try: - results = self.backend_adapter.fetch_rooms_values(date_from, date_to) - channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] - channel_room_type_obj = self.env['channel.hotel.room.type'] - room_avail_mapper = self.component( - usage='import.mapper', - model_name='channel.hotel.room.type.availability') - count = len(results) - for room_k, room_v in results.items(): - iter_day = dfrom_dt - channel_room_type = channel_room_type_obj.search([ - ('channel_room_id', '=', room_k) - ], limit=1) - if channel_room_type: - for room in room_v: - room.update({ - 'room_type_id': channel_room_type.odoo_id.id, - 'date': fields.Date.to_string(iter_day), - }) - map_record = room_avail_mapper.map_record(room) - room_type_avail_bind = channel_room_type_avail_obj.search([ - ('room_type_id', '=', room['room_type_id']), - ('date', '=', room['date']) - ], limit=1) - if room_type_avail_bind: - room_type_avail_bind.with_context({ - 'wubook_action': False - }).write(map_record.values()) - else: - room_type_avail_bind = channel_room_type_avail_obj.with_context({ - 'wubook_action': False - }).create(map_record.values(for_create=True)) - iter_day += timedelta(days=1) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='avail', - internal_message=_("Can't import availability from WuBook"), - channel_message=err.data['message']) + results = self.backend_adapter.fetch_rooms_values(date_from, date_to) + + channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] + channel_room_type_obj = self.env['channel.hotel.room.type'] + room_avail_mapper = self.component( + usage='import.mapper', + model_name='channel.hotel.room.type.availability') + count = len(results) + for room_k, room_v in results.items(): + iter_day = dfrom_dt + channel_room_type = channel_room_type_obj.search([ + ('channel_room_id', '=', room_k) + ], limit=1) + if channel_room_type: + for room in room_v: + room.update({ + 'room_type_id': channel_room_type.odoo_id.id, + 'date': fields.Date.to_string(iter_day), + }) + map_record = room_avail_mapper.map_record(room) + room_type_avail_bind = channel_room_type_avail_obj.search([ + ('room_type_id', '=', room['room_type_id']), + ('date', '=', room['date']) + ], limit=1) + if room_type_avail_bind: + room_type_avail_bind.with_context({ + 'wubook_action': False + }).write(map_record.values()) + else: + room_type_avail_bind = channel_room_type_avail_obj.with_context({ + 'wubook_action': False + }).create(map_record.values(for_create=True)) + iter_day += timedelta(days=1) return count @@ -85,6 +78,11 @@ class HotelRoomTypeAvailabilityImportMapper(Component): ('date', 'date'), ] + @only_create + @mapping + def channel_pushed(self, record): + return {'channel_pushed': True} + @mapping def backend_id(self, record): return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/common.py b/hotel_channel_connector/models/hotel_room_type_restriction/common.py index d0b5908d6..fef19def4 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/common.py @@ -7,6 +7,7 @@ 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 +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError _logger = logging.getLogger(__name__) class ChannelHotelRoomTypeRestriction(models.Model): @@ -28,7 +29,14 @@ class ChannelHotelRoomTypeRestriction(models.Model): if not self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.restriction.exporter') - exporter.create_rplan(self) + try: + exporter.create_rplan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't create restriction plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -38,7 +46,14 @@ class ChannelHotelRoomTypeRestriction(models.Model): if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.restriction.exporter') - exporter.rename_rplan(self) + try: + exporter.rename_rplan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't modify restriction plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -48,14 +63,28 @@ class ChannelHotelRoomTypeRestriction(models.Model): if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.restriction.exporter') - exporter.delete_rplan(self) + try: + exporter.delete_rplan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't delete restriction plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @api.model def import_restriction_plans(self, backend): with backend.work_on(self._name) as work: importer = work.component(usage='hotel.room.type.restriction.importer') - return importer.import_restriction_plans() + try: + return importer.import_restriction_plans() + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='restriction', + internal_message=_("Can't fetch restriction plans from wubook"), + channel_message=err.data['message']) class HotelRoomTypeRestriction(models.Model): _inherit = 'hotel.room.type.restriction' diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py index b60878830..9348347d3 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/exporter.py @@ -3,7 +3,6 @@ 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__) @@ -15,38 +14,16 @@ class HotelRoomTypeRestrictionExporter(Component): @api.model def rename_rplan(self, binding): - try: - return self.backend_adapter.rename_rplan( - binding.external_id, - binding.name) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't modify restriction plan in WuBook"), - channel_message=err.data['message']) + return self.backend_adapter.rename_rplan( + binding.external_id, + binding.name) @api.model def delete_rplan(self, binding): - try: - return self.backend_adapter.delete_rplan(binding.external_id) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't delete restriction plan in WuBook"), - channel_message=err.data['message']) + return self.backend_adapter.delete_rplan(binding.external_id) @api.model def create_rplan(self, binding): - try: - external_id = self.backend_adapter.create_rplan(binding.name) - binding.external_id = external_id - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't create restriction plan in WuBook"), - channel_message=err.data['message']) - else: - self.binder.bind(external_id, binding) + external_id = self.backend_adapter.create_rplan(binding.name) + binding.external_id = external_id + self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py index ac687dbca..c20db4ce0 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -5,7 +5,6 @@ import logging from datetime import timedelta 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.addons.hotel import date_utils from odoo import fields, api, _ @@ -20,32 +19,24 @@ class HotelRoomTypeRestrictionImporter(Component): @api.model def import_restriction_plans(self): - count = 0 - try: - results = self.backend_adapter.rplan_rplans() - channel_restriction_obj = self.env['channel.hotel.room.type.restriction'] - restriction_mapper = self.component(usage='import.mapper', - model_name='channel.hotel.room.type.restriction') - for plan in results: - plan_record = restriction_mapper.map_record(plan) - plan_bind = channel_restriction_obj.search([ - ('external_id', '=', str(plan['id'])) - ], limit=1) - if not plan_bind: - channel_restriction_obj.with_context({ - 'wubook_action': False, - 'rules': plan.get('rules'), - }).create(plan_record.values(for_create=True)) - else: - plan_bind.with_context({'wubook_action': False}).write( - plan_record.values()) - count = count + 1 - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't fetch restriction plans from wubook"), - channel_message=err.data['message']) + results = self.backend_adapter.rplan_rplans() + channel_restriction_obj = self.env['channel.hotel.room.type.restriction'] + restriction_mapper = self.component(usage='import.mapper', + model_name='channel.hotel.room.type.restriction') + for plan in results: + plan_record = restriction_mapper.map_record(plan) + plan_bind = channel_restriction_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_restriction_obj.with_context({ + 'wubook_action': False, + 'rules': plan.get('rules'), + }).create(plan_record.values(for_create=True)) + else: + plan_bind.with_context({'wubook_action': False}).write( + plan_record.values()) + count = count + 1 return count diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py index 5377b8cef..c57c8db29 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -6,6 +6,7 @@ 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 +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError class ChannelHotelRoomTypeRestrictionItem(models.Model): _name = 'channel.hotel.room.type.restriction.item' @@ -25,17 +26,33 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): def import_restriction_values(self, backend): with backend.work_on(self._name) as work: importer = work.component(usage='hotel.room.type.restriction.item.importer') - return importer.import_restriction_values( - backend.restriction_from, - backend.restriction_to, - channel_restr_id=backend.restriction_id) + try: + return importer.import_restriction_values( + backend.restriction_from, + backend.restriction_to, + channel_restr_id=backend.restriction_id) + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='restriction', + internal_message=_("Can't fetch plan restrictions from wubook"), + channel_message=err.data['message'], + channel_object_id=backend.restriction_id, + dfrom=backend.restriction_from, dto=backend.restriction_to) @job(default_channel='root.channel') @api.model def push_restriction(self, backend): with backend.work_on(self._name) as work: exporter = work.component(usage='hotel.room.type.restriction.item.exporter') - return exporter.push_restriction() + try: + return exporter.push_restriction() + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='restriction', + internal_message=_("Can't update restrictions in WuBook"), + channel_message=err.data['message']) class HotelRoomTypeRestrictionItem(models.Model): _inherit = 'hotel.room.type.restriction.item' @@ -75,10 +92,6 @@ class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.restriction.item'] - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_create(self, record, fields=None): - record.channel_pushed = False - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py index b8788aaf1..ac44d4c58 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py @@ -20,27 +20,20 @@ class HotelRoomTypeRestrictionItemExporter(Component): @api.model def update_restriction(self, binding): if any(binding.restriction_id.channel_bind_ids): - try: - # FIXME: Supossed that only exists one channel connector per record - binding.channel_pushed = True - return self.backend_adapter.update_rplan_values( - binding.restriction_id.channel_bind_ids[0].external_id, - binding.date, - { - 'min_stay': binding.min_stay or 0, - 'min_stay_arrival': binding.min_stay_arrival or 0, - 'max_stay': binding.max_stay or 0, - 'max_stay_arrival': binding.max_stay_arrival or 0, - 'closed': binding.closed and 1 or 0, - 'closed_arrival': binding.closed_arrival and 1 or 0, - 'closed_departure': binding.closed_departure and 1 or 0, - }) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't update restriction in WuBook"), - channel_message=err.data['message']) + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_rplan_values( + binding.restriction_id.channel_bind_ids[0].external_id, + binding.date, + { + 'min_stay': binding.min_stay or 0, + 'min_stay_arrival': binding.min_stay_arrival or 0, + 'max_stay': binding.max_stay or 0, + 'max_stay_arrival': binding.max_stay_arrival or 0, + 'closed': binding.closed and 1 or 0, + 'closed_arrival': binding.closed_arrival and 1 or 0, + 'closed_departure': binding.closed_departure and 1 or 0, + }) @api.model def push_restriction(self): @@ -91,17 +84,10 @@ class HotelRoomTypeRestrictionItemExporter(Component): _logger.info(restrictions) for k_res, v_res in restrictions.items(): if any(v_res): - try: - self.backend_adapter.update_rplan_values( - int(k_res), - date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), - v_res) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't update restrictions in WuBook"), - channel_message=err.data['message']) + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) unpushed.with_context({ 'wubook_action': False}).write({'channel_pushed': True}) return True diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py index 426e11028..4c6a925b8 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py @@ -66,21 +66,12 @@ class HotelRoomTypeRestrictionImporter(Component): @api.model def import_restriction_values(self, date_from, date_to, channel_restr_id=False): channel_restr_plan_id = channel_restr_id.external_id if channel_restr_id else False - try: - results = self.backend_adapter.wired_rplan_get_rplan_values( - date_from, - date_to, - int(channel_restr_plan_id)) - if any(results): - self._generate_restriction_items(results) - except ChannelConnectorError as err: - self.create_issue( - section='restriction', - internal_message=_("Can't fetch plan restrictions from wubook"), - channel_message=err.data['message'], - channel_object_id=channel_restr_plan_id, - dfrom=date_from, dto=date_to) - return False + results = self.backend_adapter.wired_rplan_get_rplan_values( + date_from, + date_to, + int(channel_restr_plan_id)) + if any(results): + self._generate_restriction_items(results) class HotelRoomTypeRestrictionItemImportMapper(Component): _name = 'channel.hotel.room.type.restriction.item.import.mapper' @@ -103,6 +94,12 @@ class HotelRoomTypeRestrictionItemImportMapper(Component): def applied_on(self, record): return {'applied_on': '0_room_type'} + @only_create + @mapping + def channel_pushed(self, record): + return {'channel_pushed': True} + + @mapping def room_type_id(self, record): return {'room_type_id': record['room_type_id']} diff --git a/hotel_channel_connector/models/product_pricelist/common.py b/hotel_channel_connector/models/product_pricelist/common.py index 775bd698a..9f45630da 100644 --- a/hotel_channel_connector/models/product_pricelist/common.py +++ b/hotel_channel_connector/models/product_pricelist/common.py @@ -6,6 +6,7 @@ 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 +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError class ChannelProductPricelist(models.Model): _name = 'channel.product.pricelist' @@ -27,7 +28,14 @@ class ChannelProductPricelist(models.Model): if not self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='product.pricelist.exporter') - exporter.create_plan(self) + try: + exporter.create_plan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't create pricelist plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -37,7 +45,14 @@ class ChannelProductPricelist(models.Model): if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='product.pricelist.exporter') - exporter.rename_plan(self) + try: + exporter.rename_plan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't modify pricelist plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @@ -47,14 +62,28 @@ class ChannelProductPricelist(models.Model): if self.external_id: with self.backend_id.work_on(self._name) as work: exporter = work.component(usage='product.pricelist.exporter') - exporter.delete_plan(self) + try: + exporter.delete_plan(self) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_id.id, + section='restriction', + internal_message=_("Can't delete pricelist plan in WuBook"), + channel_message=err.data['message']) @job(default_channel='root.channel') @api.model def import_price_plans(self, backend): with backend.work_on(self._name) as work: importer = work.component(usage='product.pricelist.importer') - return importer.import_pricing_plans() + try: + return importer.import_pricing_plans() + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='pricelist', + internal_message=_("Can't get pricing plans from wubook"), + channel_message=err.data['message']) class ProductPricelist(models.Model): _inherit = 'product.pricelist' diff --git a/hotel_channel_connector/models/product_pricelist/exporter.py b/hotel_channel_connector/models/product_pricelist/exporter.py index 45b028d84..fb990cfb2 100644 --- a/hotel_channel_connector/models/product_pricelist/exporter.py +++ b/hotel_channel_connector/models/product_pricelist/exporter.py @@ -3,7 +3,6 @@ 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__) @@ -15,38 +14,15 @@ class ProductPricelistExporter(Component): @api.model def rename_plan(self, binding): - try: - return self.backend_adapter.rename_plan( - binding.external_id, - binding.name) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't modify pricelist plan in WuBook"), - channel_message=err.data['message']) + return self.backend_adapter.rename_plan( + binding.external_id, + binding.name) @api.model def delete_plan(self, binding): - try: - return self.backend_adapter.delete_plan(binding.external_id) - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't delete pricelist plan in WuBook"), - channel_message=err.data['message']) + return self.backend_adapter.delete_plan(binding.external_id) @api.model def create_plan(self, binding): - try: - external_id = self.backend_adapter.create_plan(binding.name) - binding.external_id = external_id - except ChannelConnectorError as err: - self.create_issue( - backend=self.backend_adapter.id, - section='restriction', - internal_message=_("Can't create pricelist plan in WuBook"), - channel_message=err.data['message']) - else: - self.binder.bind(external_id, binding) + external_id = self.backend_adapter.create_plan(binding.name) + binding.external_id = external_id diff --git a/hotel_channel_connector/models/product_pricelist/importer.py b/hotel_channel_connector/models/product_pricelist/importer.py index 74dabe6e8..c99add698 100644 --- a/hotel_channel_connector/models/product_pricelist/importer.py +++ b/hotel_channel_connector/models/product_pricelist/importer.py @@ -4,7 +4,6 @@ import logging from datetime import datetime, timedelta 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, only_create from odoo.addons.hotel_channel_connector.components.backend_adapter import ( DEFAULT_WUBOOK_DATE_FORMAT) @@ -24,32 +23,25 @@ class ProductPricelistImporter(Component): channel_product_listprice_obj = self.env['channel.product.pricelist'] pricelist_mapper = self.component(usage='import.mapper', model_name='channel.product.pricelist') + results = self.backend_adapter.get_pricing_plans() count = 0 - try: - results = self.backend_adapter.get_pricing_plans() - for plan in results: - if 'vpid' in plan: - continue # FIXME: Ignore Virtual Plans - plan_record = pricelist_mapper.map_record(plan) - plan_bind = channel_product_listprice_obj.search([ - ('external_id', '=', str(plan['id'])) - ], limit=1) - if not plan_bind: - channel_product_listprice_obj.with_context({ - 'wubook_action': False}).create(plan_record.values(for_create=True)) - else: - channel_product_listprice_obj.write(plan_record.values()) - count = count + 1 - except ChannelConnectorError as err: - self.create_issue( - section='pricelist', - internal_message=_("Can't get pricing plans from wubook"), - channel_message=err.data['message']) - return 0 + for plan in results: + if 'vpid' in plan: + continue # FIXME: Ignore Virtual Plans + plan_record = pricelist_mapper.map_record(plan) + plan_bind = channel_product_listprice_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_product_listprice_obj.with_context({ + 'wubook_action': False}).create(plan_record.values(for_create=True)) + else: + channel_product_listprice_obj.write(plan_record.values()) + count = count + 1 return count -class ProductPricelistMapper(Component): +class ProductPricelistImportMapper(Component): _name = 'channel.product.pricelist.import.mapper' _inherit = 'channel.import.mapper' _apply_on = 'channel.product.pricelist' diff --git a/hotel_channel_connector/models/product_pricelist_item/__init__.py b/hotel_channel_connector/models/product_pricelist_item/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/product_pricelist_item/__init__.py +++ b/hotel_channel_connector/models/product_pricelist_item/__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/product_pricelist_item/common.py b/hotel_channel_connector/models/product_pricelist_item/common.py index f80d542c5..03457d231 100644 --- a/hotel_channel_connector/models/product_pricelist_item/common.py +++ b/hotel_channel_connector/models/product_pricelist_item/common.py @@ -6,6 +6,7 @@ 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 +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError class ChannelProductPricelistItem(models.Model): _name = 'channel.product.pricelist.item' @@ -14,13 +15,44 @@ class ChannelProductPricelistItem(models.Model): _description = 'Channel Product Pricelist Item' odoo_id = fields.Many2one(comodel_name='product.pricelist.item', - string='Pricelist Item', + string='Hotel Product Pricelist Item', required=True, ondelete='cascade') - channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, old_name='wpushed') + @job(default_channel='root.channel') + @api.model + def import_pricelist_values(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='product.pricelist.item.importer') + try: + if not backend.pricelist_id: + return importer.import_all_pricelist_values( + backend.pricelist_from, + backend.pricelist_to) + return importer.import_pricelist_values( + backend.pricelist_id.external_id, + backend.pricelist_from, + backend.pricelist_to) + except ChannelConnectorError as err: + self.create_issue( + backend=backend.id, + section='pricelist', + internal_message="Can't fetch plan prices from wubook!", + channel_message=err.data['message'], + channel_object_id=backend.pricelist_id.external_id, + dfrom=backend.pricelist_from, + dto=backend.pricelist_to) + return False + + @job(default_channel='root.channel') + @api.model + def push_pricelist(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='product.pricelist.item.exporter') + return exporter.push_pricelist() + class ProductPricelistItem(models.Model): _inherit = 'product.pricelist.item' @@ -28,3 +60,39 @@ class ProductPricelistItem(models.Model): comodel_name='channel.product.pricelist.item', inverse_name='odoo_id', string='Hotel Channel Connector Bindings') + +class ProducrPricelistItemAdapter(Component): + _name = 'channel.product.pricelist.item.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.product.pricelist.item' + + def fetch_plan_prices(self, external_id, date_from, date_to, rooms): + return super(ProducrPricelistItemAdapter, self).fetch_plan_prices( + external_id, + date_from, + date_to, + rooms) + +class BindingProductPricelistItemListener(Component): + _name = 'binding.product.pricelist.item.listener' + _inherit = 'base.connector.listener' + _apply_on = ['product.pricelist.item'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + fields_to_check = ('date_start', 'date_end', 'fixed_price', 'product_tmpl_id') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_bind_ids.write({'channel_pushed': False}) + +class ChannelBindingProductPricelistItemListener(Component): + _name = 'channel.binding.product.pricelist.item.listener' + _inherit = 'base.connector.listener' + _apply_on = ['channel.product.pricelist.item'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + fields_to_check = ('date_start', 'date_end', 'fixed_price', 'product_tmpl_id') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_pushed = False diff --git a/hotel_channel_connector/models/product_pricelist_item/exporter.py b/hotel_channel_connector/models/product_pricelist_item/exporter.py new file mode 100644 index 000000000..1498a354b --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist_item/exporter.py @@ -0,0 +1,106 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + +class ProductPricelistItemExporter(Component): + _name = 'channel.product.pricelist.item.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.product.pricelist.item'] + _usage = 'product.pricelist.item.exporter' + + @api.model + def update_restriction(self, binding): + if any(binding.restriction_id.channel_bind_ids): + try: + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_rplan_values( + binding.restriction_id.channel_bind_ids[0].external_id, + binding.date, + { + 'min_stay': binding.min_stay or 0, + 'min_stay_arrival': binding.min_stay_arrival or 0, + 'max_stay': binding.max_stay or 0, + 'max_stay_arrival': binding.max_stay_arrival or 0, + 'closed': binding.closed and 1 or 0, + 'closed_arrival': binding.closed_arrival and 1 or 0, + 'closed_departure': binding.closed_departure and 1 or 0, + }) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restriction in WuBook"), + channel_message=err.data['message']) + + @api.model + def push_restriction(self): + channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction'] + channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item'] + unpushed = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('date', '>=', fields.Date.today()) + ], order="date ASC") + if any(unpushed): + date_start = fields.Date.from_string(unpushed[0].date) + date_end = fields.Date.from_string(unpushed[-1].date) + days_diff = (date_end-date_start).days + 1 + restrictions = {} + channel_restr_plan_ids = channel_room_type_rest_obj.search([]) + for rp in channel_restr_plan_ids: + restrictions.update({rp.external_id: {}}) + unpushed_rp = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('restriction_id', '=', rp.odoo_id.id) + ]) + room_type_ids = unpushed_rp.mapped('room_type_id') + for room_type in room_type_ids: + if any(room_type.channel_bind_ids): + # FIXME: Supossed that only exists one channel connector per record + room_type_external_id = room_type.channel_bind_ids[0].external_id + restrictions[rp.external_id].update({ + room_type_external_id: [], + }) + for i in range(0, days_diff): + ndate_dt = date_start + timedelta(days=i) + restr = room_type.get_restrictions( + ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + rp.odoo_id.id) + if restr: + restrictions[rp.external_id][room_type_external_id].append({ + 'min_stay': restr.min_stay or 0, + 'min_stay_arrival': restr.min_stay_arrival or 0, + 'max_stay': restr.max_stay or 0, + 'max_stay_arrival': restr.max_stay_arrival or 0, + 'closed': restr.closed and 1 or 0, + 'closed_arrival': restr.closed_arrival and 1 or 0, + 'closed_departure': restr.closed_departure and 1 or 0, + }) + else: + restrictions[rp.external_id][room_type_external_id].append({}) + _logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==") + _logger.info(restrictions) + for k_res, v_res in restrictions.items(): + if any(v_res): + try: + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restrictions in WuBook"), + channel_message=err.data['message']) + unpushed.with_context({ + 'wubook_action': False}).write({'channel_pushed': True}) + return True diff --git a/hotel_channel_connector/models/product_pricelist_item/importer.py b/hotel_channel_connector/models/product_pricelist_item/importer.py new file mode 100644 index 000000000..bb7bb30b7 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist_item/importer.py @@ -0,0 +1,120 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import timedelta +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import mapping, only_create +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + + +class ProductPricelistItemImporter(Component): + _name = 'channel.product.pricelist.item.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.product.pricelist.item'] + _usage = 'product.pricelist.item.importer' + + @api.model + def _generate_pricelist_items(self, channel_plan_id, date_from, date_to, plan_prices): + channel_hotel_room_type_obj = self.env['channel.hotel.room.type'] + pricelist_bind = self.env['channel.product.pricelist'].search([ + ('external_id', '=', channel_plan_id) + ], limit=1) + pricelist_item_mapper = self.component( + usage='import.mapper', + model_name='channel.product.pricelist.item') + if pricelist_bind: + channel_pricelist_item_obj = self.env['channel.product.pricelist.item'] + dfrom_dt = fields.Date.from_string(date_from) + dto_dt = fields.Date.from_string(date_to) + days_diff = (dto_dt-dfrom_dt).days + 1 + for i in range(0, days_diff): + ndate_dt = dfrom_dt + timedelta(days=i) + for k_rid, v_rid in plan_prices.items(): + channel_room_type = channel_hotel_room_type_obj.search([ + ('external_id', '=', k_rid) + ], limit=1) + if channel_room_type: + ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + item = { + 'price': plan_prices[k_rid][i], + 'channel_room_type': channel_room_type, + 'pricelist_id': pricelist_bind.odoo_id.id, + 'date': ndate_str, + } + map_record = pricelist_item_mapper.map_record(item) + pricelist_item = channel_pricelist_item_obj.search([ + ('pricelist_id', '=', pricelist_bind.odoo_id.id), + ('date_start', '=', ndate_str), + ('date_end', '=', ndate_str), + ('compute_price', '=', 'fixed'), + ('applied_on', '=', '1_product'), + ('product_tmpl_id', '=', + channel_room_type.product_id.product_tmpl_id.id) + ], limit=1) + if pricelist_item: + pricelist_item.with_context({ + 'wubook_action': False}).write(map_record.values()) + else: + channel_pricelist_item_obj.with_context({ + 'wubook_action': False}).create(map_record.values(for_create=True)) + return True + + @api.model + def import_all_pricelist_values(self, date_from, date_to, rooms=None): + external_ids = self.env['channel.product.pricelist'].search([]).mapped('external_id') + for external_id in external_ids: + if external_id: + self.import_pricelist_values(external_id, date_from, date_to, rooms=rooms) + return True + + @api.model + def import_pricelist_values(self, external_id, date_from, date_to, rooms=None): + results = self.backend_adapter.fetch_plan_prices( + external_id, + date_from, + date_to, + rooms) + self._generate_pricelist_items(external_id, date_from, date_to, results) + +class ProductPricelistItemImportMapper(Component): + _name = 'channel.product.pricelist.item.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.product.pricelist.item' + + direct = [ + ('price', 'fixed_price'), + ('date', 'date_start'), + ('date', 'date_end'), + ] + + @only_create + @mapping + def compute_price(self, record): + return {'compute_price': 'fixed'} + + @only_create + @mapping + def channel_pushed(self, record): + return {'channel_pushed': True} + + @only_create + @mapping + def applied_on(self, record): + return {'applied_on': '1_product'} + + @mapping + def product_tmpl_id(self, record): + return {'product_tmpl_id': record['channel_room_type'].product_id.product_tmpl_id.id} + + @mapping + def pricelist_id(self, record): + return {'pricelist_id': record['pricelist_id']} + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/models/res_config.py b/hotel_channel_connector/models/res_config.py deleted file mode 100644 index 25de1266d..000000000 --- a/hotel_channel_connector/models/res_config.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api - - -class HotelConfiguration(models.TransientModel): - _inherit = 'res.config.settings' - - default_channel_connector = fields.Many2one( - 'channel.backend', - 'Default Channel Connector Backend') - - @api.multi - def set_values(self): - super(HotelConfiguration, self).set_values() - - self.env['ir.default'].sudo().set( - 'res.config.settings', 'default_channel_connector', - self.default_channel_connector.id) - - @api.model - def get_values(self): - res = super(HotelConfiguration, self).get_values() - - # ONLY FOR v11. DO NOT FORWARD-PORT - default_channel_connector = self.env['ir.default'].sudo().get( - 'res.config.settings', 'default_channel_connector') - - res.update( - default_channel_connector=default_channel_connector, - ) - return res diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 2b947a5d0..76f7bd117 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -28,6 +28,8 @@
+ +