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"/> + + + + + +