diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 3d2afafb6..06a070f6b 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -47,12 +47,12 @@ 'views/inherit_product_product.xml', 'views/hotel_room_amenities_type.xml', 'views/hotel_room_amenities.xml', - 'views/reservation_restriction_views.xml', - 'views/reservation_restriction_item_views.xml', + 'views/hotel_room_type_restriction_views.xml', + 'views/hotel_room_type_restriction_item_views.xml', 'views/hotel_reservation.xml', # 'views/room_type_views.xml', 'views/cardex.xml', - 'views/room_type_availability.xml', + 'views/hotel_room_type_availability.xml', # 'views/hotel_dashboard.xml', 'data/cron_jobs.xml', 'data/records.xml', diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 8ce8d5f3d..0c055ea6b 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -81,7 +81,7 @@ class HotelRoomType(models.Model): ('id', '=', room_type_id) ]) # QUESTION What linked represent? Rooms in this type ? - rooms_linked = self.room_ids + rooms_linked = room_type_id.room_ids free_rooms = free_rooms & rooms_linked return free_rooms.sorted(key=lambda r: r.sequence) diff --git a/hotel/models/hotel_room_type_restriction_item.py b/hotel/models/hotel_room_type_restriction_item.py index 2533bc4b5..5da342f08 100644 --- a/hotel/models/hotel_room_type_restriction_item.py +++ b/hotel/models/hotel_room_type_restriction_item.py @@ -16,8 +16,7 @@ class HotelRoomTypeRestrictionItem(models.Model): # required=True, ondelete='cascade') room_type_id = fields.Many2one('hotel.room.type', 'Room Type', required=True, ondelete='cascade') - date_start = fields.Date('From') - date_end = fields.Date("To") + date = fields.Date('Date') applied_on = fields.Selection([ ('1_global', 'Global'), # ('0_room_type', 'Virtual Room')], string="Apply On", required=True, @@ -35,7 +34,7 @@ class HotelRoomTypeRestrictionItem(models.Model): closed_arrival = fields.Boolean('Closed Arrival') _sql_constraints = [('room_type_registry_unique', - 'unique(restriction_id, room_type_id, date_start, date_end)', + 'unique(restriction_id, room_type_id, date)', 'Only can exists one restriction in the same day for the same room type!')] @api.constrains('min_stay', 'min_stay_arrival', 'max_stay', @@ -52,17 +51,6 @@ class HotelRoomTypeRestrictionItem(models.Model): raise ValidationError( ("Max. Stay Arrival can't be less than zero")) - @api.constrains('date_start', 'date_end') - def _check_date_start_date_end(self): - if self.applied_on == '1_global': - self.date_start = False - self.date_end = False - elif self.date_start and self.date_end: - date_start_dt = fields.Date.from_string(self.date_start) - date_end_dt = fields.Date.from_string(self.date_end) - if date_end_dt < date_start_dt: - raise ValidationError(_("Invalid Dates")) - @api.constrains('applied_on') def _check_applied_on(self): count = self.search_count([('applied_on', '=', '1_global')]) diff --git a/hotel/views/room_type_availability.xml b/hotel/views/hotel_room_type_availability.xml similarity index 100% rename from hotel/views/room_type_availability.xml rename to hotel/views/hotel_room_type_availability.xml diff --git a/hotel/views/reservation_restriction_item_views.xml b/hotel/views/hotel_room_type_restriction_item_views.xml similarity index 81% rename from hotel/views/reservation_restriction_item_views.xml rename to hotel/views/hotel_room_type_restriction_item_views.xml index 459da61cb..64c4dbde4 100644 --- a/hotel/views/reservation_restriction_item_views.xml +++ b/hotel/views/hotel_room_type_restriction_item_views.xml @@ -2,7 +2,7 @@ - + hotel.room.type.restriction.item.form hotel.room.type.restriction.item @@ -13,12 +13,7 @@ - - - - - - + @@ -37,7 +32,7 @@ - + hotel.room.type.restriction.item.tree hotel.room.type.restriction.item @@ -45,8 +40,7 @@ - - + diff --git a/hotel/views/reservation_restriction_views.xml b/hotel/views/hotel_room_type_restriction_views.xml similarity index 83% rename from hotel/views/reservation_restriction_views.xml rename to hotel/views/hotel_room_type_restriction_views.xml index 03ee0bf7e..7ae67cd12 100644 --- a/hotel/views/reservation_restriction_views.xml +++ b/hotel/views/hotel_room_type_restriction_views.xml @@ -2,7 +2,7 @@ - + hotel.room.type.restriction.form hotel.room.type.restriction @@ -21,8 +21,7 @@ - - + @@ -34,7 +33,7 @@ - + hotel.room.type.restriction.tree hotel.room.type.restriction @@ -46,7 +45,7 @@ - + Reservation restrictions hotel.room.type.restriction form @@ -55,7 +54,7 @@ diff --git a/hotel/views/room_type_views.xml b/hotel/views/hotel_room_type_views.xml similarity index 100% rename from hotel/views/room_type_views.xml rename to hotel/views/hotel_room_type_views.xml diff --git a/hotel_calendar/models/hotel_calendar_management.py b/hotel_calendar/models/hotel_calendar_management.py index d282c1b4f..5a7ab9a8c 100644 --- a/hotel_calendar/models/hotel_calendar_management.py +++ b/hotel_calendar/models/hotel_calendar_management.py @@ -84,8 +84,7 @@ class HotelCalendarManagement(models.TransientModel): for k_res in restrictions.keys(): for restriction in restrictions[k_res]: res_id = room_type_rest_item_obj.search([ - ('date_start', '>=', restriction['date']), - ('date_end', '<=', restriction['date']), + ('date', '=', restriction['date']), ('restriction_id', '=', int(restriction_id)), ('applied_on', '=', '0_room_type'), ('room_type_id', '=', int(k_res)), @@ -93,8 +92,7 @@ class HotelCalendarManagement(models.TransientModel): vals = self._get_restrictions_values(restriction) if not res_id: vals.update({ - 'date_start': restriction['date'], - 'date_end': restriction['date'], + 'date': restriction['date'], 'restriction_id': int(restriction_id), 'applied_on': '0_room_type', 'room_type_id': int(k_res), @@ -159,10 +157,9 @@ class HotelCalendarManagement(models.TransientModel): def _hcalendar_restriction_json_data(self, restrictions): json_data = {} for rec in restrictions: - # TODO: date_end - date_start loop json_data.setdefault(rec.room_type_id.id, []).append({ 'id': rec.id, - 'date': rec.date_start, + 'date': rec.date, 'min_stay': rec.min_stay, 'min_stay_arrival': rec.min_stay_arrival, 'max_stay': rec.max_stay, @@ -282,7 +279,7 @@ class HotelCalendarManagement(models.TransientModel): room_type_rest_it_obj = self.env['hotel.room.type.restriction.item'] restriction_item_ids = room_type_rest_it_obj.search([ - ('date_start', '>=', dfrom), ('date_end', '<=', dto), + ('date', '>=', dfrom), ('date', '<=', dto), ('restriction_id', '=', restriction_id), ('applied_on', '=', '0_room_type'), ]) diff --git a/hotel_calendar/models/inherited_hotel_reservation.py b/hotel_calendar/models/inherited_hotel_reservation.py index 1034532f1..4c7baf716 100644 --- a/hotel_calendar/models/inherited_hotel_reservation.py +++ b/hotel_calendar/models/inherited_hotel_reservation.py @@ -162,7 +162,7 @@ class HotelReservation(models.Model): restriction_id = int(restriction_id) date_start = fields.Date.from_string(dfrom) - timedelta(days=1) date_end = fields.Date.from_string(dto) - date_diff = abs((date_end - date_sart).days) + 1 + date_diff = abs((date_end - date_start).days) + 1 # Get Prices json_rooms_rests = {} room_types = self.env['hotel.room.type'].search( @@ -176,8 +176,7 @@ class HotelReservation(models.Model): ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT) rest_id = room_type_rest_obj.search([ ('room_type_id', '=', room_type.id), - ('date_start', '>=', ndate_str), - ('date_end', '<=', ndate_str), + ('date', '>=', ndate_str), ('applied_on', '=', '0_room_type'), ('restriction_id', '=', restriction_id) ], limit=1) diff --git a/hotel_calendar/models/inherited_hotel_room_type.py b/hotel_calendar/models/inherited_hotel_room_type.py index 87d160e07..eb254cc50 100644 --- a/hotel_calendar/models/inherited_hotel_room_type.py +++ b/hotel_calendar/models/inherited_hotel_room_type.py @@ -10,7 +10,7 @@ class HotelRoomType(models.Model): @api.multi def unlink(self): - room_type_pr_cached_obj = self.env['hotel.room.pricelist.cached'] + room_type_pr_cached_obj = self.env['room.pricelist.cached'] for record in self: pr_chached = room_type_pr_cached_obj.search([ ('room_id', '=', record.id) diff --git a/hotel_calendar/models/inherited_hotel_room_type_restriction_item.py b/hotel_calendar/models/inherited_hotel_room_type_restriction_item.py index cfab8f697..651237951 100644 --- a/hotel_calendar/models/inherited_hotel_room_type_restriction_item.py +++ b/hotel_calendar/models/inherited_hotel_room_type_restriction_item.py @@ -10,7 +10,7 @@ class HotelRoomTypeResrtrictionItem(models.Model): @api.model def create(self, vals): - res = super(HotelVirtualRoomResrtrictionItem, self).create(vals) + res = super(HotelRoomTypeResrtrictionItem, self).create(vals) restrictions_parity_id = self.env['ir.default'].sudo().get( 'res.config.settings', 'parity_restrictions_id') if restrictions_parity_id: @@ -20,7 +20,7 @@ class HotelRoomTypeResrtrictionItem(models.Model): self.applied_on == '0_room_type': self.env['bus.hotel.calendar'].send_restriction_notification({ 'restriction_id': self.restriction_id.id, - 'date': self.date_start, + 'date': self.date, 'min_stay': self.min_stay, 'min_stay_arrival': self.min_stay_arrival, 'max_stay': self.max_stay, @@ -39,7 +39,7 @@ class HotelRoomTypeResrtrictionItem(models.Model): 'res.config.settings', 'parity_restrictions_id') if restrictions_parity_id: restrictions_parity_id = int(restrictions_parity_id) - ret_vals = super(HotelVirtualRoomResrtrictionItem, self).write(vals) + ret_vals = super(HotelRoomTypeResrtrictionItem, self).write(vals) bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for record in self: @@ -48,7 +48,7 @@ class HotelRoomTypeResrtrictionItem(models.Model): continue bus_hotel_calendar_obj.send_restriction_notification({ 'restriction_id': record.restriction_id.id, - 'date': record.date_start, + 'date': record.date, 'min_stay': record.min_stay, 'min_stay_arrival': record.min_stay_arrival, 'max_stay': record.max_stay, @@ -86,7 +86,7 @@ class HotelRoomTypeResrtrictionItem(models.Model): 'room_type_id': record.room_type_id.id, 'id': record.id, }) - res = super(HotelVirtualRoomResrtrictionItem, self).unlink() + res = super(HotelRoomTypeResrtrictionItem, self).unlink() bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for uval in unlink_vals: bus_hotel_calendar_obj.send_restriction_notification(uval) diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 7d82409d5..8bb150279 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -18,7 +18,6 @@ }, 'data': [ 'data/cron_jobs.xml', - 'wizard/wubook_installer.xml', 'wizard/wubook_import_plan_prices.xml', 'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_availability.xml', @@ -29,10 +28,9 @@ 'views/inherited_hotel_folio_views.xml', 'views/inherited_product_pricelist_views.xml', 'views/inherited_product_pricelist_item_views.xml', - 'views/inherited_reservation_restriction_views.xml', - 'views/inherited_reservation_restriction_item_views.xml', + 'views/inherited_hotel_room_type_restriction_views.xml', 'views/inherited_res_partner_views.xml', - 'views/hotel_channel_connector_ota_info_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', @@ -40,7 +38,6 @@ 'views/channel_hotel_room_type_restriction_views.xml', 'views/channel_product_pricelist_views.xml', 'views/channel_connector_backend_views.xml', - 'views/channel_connector_menu.xml', 'data/menus.xml', 'data/sequences.xml', #'security/ir.model.access.csv', diff --git a/hotel_channel_connector/components/__init__.py b/hotel_channel_connector/components/__init__.py index 0d9ff44a4..fbeb3cccb 100644 --- a/hotel_channel_connector/components/__init__.py +++ b/hotel_channel_connector/components/__init__.py @@ -4,5 +4,6 @@ from . import core from . import backend_adapter from . import binder -from . import exporter from . import importer +from . import exporter +from . import mapper diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index ea5fd6836..e5b64afd7 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -1,8 +1,8 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _ -from odoo.exceptions import ValidationError +import xmlrpc.client +import logging from odoo.addons.component.core import AbstractComponent from odoo.addons.queue_job.exception import RetryableJobError from odoo.tools import ( @@ -10,7 +10,9 @@ from odoo.tools import ( DEFAULT_SERVER_DATETIME_FORMAT) from odoo.addons.payment.models.payment_acquirer import _partner_split_name from odoo.addons.hotel import date_utils -from odoo import fields +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo import fields, _ +_logger = logging.getLogger(__name__) # GLOBAL VARS DEFAULT_WUBOOK_DATE_FORMAT = "%d/%m/%Y" @@ -62,9 +64,11 @@ class WuBookServer(object): @property def server(self): - if self._server is None and self._login_data.is_valid(): + if not self._login_data.is_valid(): + raise ChannelConnectorError("Invalid Channel Parameters!") + if self._server is None: try: - self._server = xmlrpclib.Server(self._login_data.address) + self._server = xmlrpc.client.ServerProxy(self._login_data.address) res, tok = self._server.acquire_token( self._login_data.user, self._login_data.passwd, @@ -179,10 +183,10 @@ class HotelChannelInterfaceAdapter(AbstractComponent): @property def _server(self): try: - channel_server = getattr(self.work, 'hotel_channel_server') + channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( - 'You must provide a hotel_channel_server attribute with a ' + 'You must provide a channel_api attribute with a ' 'WuBookServer instance to be able to use the ' 'Backend Adapter.' ) @@ -191,14 +195,14 @@ class HotelChannelInterfaceAdapter(AbstractComponent): @property def _session_info(self): try: - channel_server = getattr(self.work, 'hotel_channel_server') + channel_server = getattr(self.work, 'channel_api') except AttributeError: raise AttributeError( - 'You must provide a hotel_channel_server attribute with a ' + 'You must provide a channel_api attribute with a ' 'WuBookServer instance to be able to use the ' 'Backend Adapter.' ) - return (channel_server.session_token, channel_server.lcode) + return (channel_server.session_token, channel_server._login_data.lcode) class WuBookAdapter(AbstractComponent): _name = 'wubook.adapter' @@ -219,7 +223,7 @@ class WuBookAdapter(AbstractComponent): # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: - raise ValidationError(_("Can't create room in WuBook"), { + raise ChannelConnectorError("Can't create room in WuBook", { 'message': results, }) return results @@ -238,7 +242,7 @@ class WuBookAdapter(AbstractComponent): # rtype=('name' in vals and vals['name'] and 3) or 1 ) if rcode != 0: - raise ValidationError(_("Can't modify room in WuBook"), { + raise ChannelConnectorError("Can't modify room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -250,7 +254,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_room_id) if rcode != 0: - raise ValidationError(_("Can't delete room in WuBook"), { + raise ChannelConnectorError("Can't delete room in WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -262,7 +266,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_room_id) if rcode != 0: - raise ValidationError(_("Can't fetch room values from WuBook"), { + raise ChannelConnectorError("Can't fetch room values from WuBook", { 'message': results, 'channel_id': channel_room_id, }) @@ -276,7 +280,7 @@ class WuBookAdapter(AbstractComponent): date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms) if rcode != 0: - raise ValidationError(_("Can't fetch rooms values from WuBook"), { + raise ChannelConnectorError("Can't fetch rooms values from WuBook", { 'message': results, }) return results @@ -287,7 +291,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], rooms_avail) if rcode != 0: - raise ValidationError(_("Can't update rooms availability in WuBook"), { + raise ChannelConnectorError("Can't update rooms availability in WuBook", { 'message': results, }) return results @@ -295,7 +299,7 @@ class WuBookAdapter(AbstractComponent): def corporate_fetch(self): rcode, results = self._server.corporate_fetchable_properties(self.TOKEN) if rcode != 0: - raise ValidationError(_("Can't call 'corporate_fetch' from WuBook"), { + raise ChannelConnectorError("Can't call 'corporate_fetch' from WuBook", { 'message': results, }) return results @@ -325,7 +329,7 @@ class WuBookAdapter(AbstractComponent): customer, adults+children) if rcode != 0: - raise ValidationError(_("Can't create reservations in wubook"), { + raise ChannelConnectorError("Can't create reservations in wubook", { 'message': results, 'date_from': checkin, 'date_to': checkout, @@ -339,7 +343,7 @@ class WuBookAdapter(AbstractComponent): channel_reservation_id, reason) if rcode != 0: - raise ValidationError(_("Can't cancel reservation in WuBook"), { + raise ChannelConnectorError("Can't cancel reservation in WuBook", { 'message': results, 'channel_reservation_id': channel_reservation_id, }) @@ -352,7 +356,7 @@ class WuBookAdapter(AbstractComponent): 1, 0) if rcode != 0: - raise ValidationError(_("Can't process reservations from wubook"), { + raise ChannelConnectorError("Can't process reservations from wubook", { 'message': results, }) return results @@ -363,7 +367,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_reservation_id) if rcode != 0: - raise ValidationError(_("Can't process reservation from wubook"), { + raise ChannelConnectorError("Can't process reservation from wubook", { 'message': results, }) return results @@ -378,7 +382,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_reservation_ids) if rcode != 0: - raise ValidationError(_("Can't mark as readed a reservation in wubook"), { + raise ChannelConnectorError("Can't mark as readed a reservation in wubook", { 'message': results, 'channel_reservation_ids': str(channel_reservation_ids), }) @@ -392,7 +396,7 @@ class WuBookAdapter(AbstractComponent): name, daily) if rcode != 0: - raise ValidationError(_("Can't add pricing plan to wubook"), { + raise ChannelConnectorError("Can't add pricing plan to wubook", { 'message': results, }) return results @@ -403,7 +407,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_plan_id) if rcode != 0: - raise ValidationError(_("Can't delete pricing plan from wubook"), { + raise ChannelConnectorError("Can't delete pricing plan from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -416,7 +420,7 @@ class WuBookAdapter(AbstractComponent): channel_plan_id, new_name) if rcode != 0: - raise ValidationError(_("Can't update pricing plan name in wubook"), { + raise ChannelConnectorError("Can't update pricing plan name in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -430,7 +434,7 @@ class WuBookAdapter(AbstractComponent): date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), prices) if rcode != 0: - raise ValidationError(_("Can't update pricing plan in wubook"), { + raise ChannelConnectorError("Can't update pricing plan in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, @@ -444,7 +448,7 @@ class WuBookAdapter(AbstractComponent): channel_plan_id, periods) if rcode != 0: - raise ValidationError(_("Can't update pricing plan period in wubook"), { + raise ChannelConnectorError("Can't update pricing plan period in wubook", { 'message': results, 'channel_plan_id': channel_plan_id, }) @@ -455,7 +459,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1]) if rcode != 0: - raise ValidationError(_("Can't get pricing plans from wubook"), { + raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, }) return results @@ -469,7 +473,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms or []) if rcode != 0: - raise ValidationError(_("Can't get pricing plans from wubook"), { + raise ChannelConnectorError("Can't get pricing plans from wubook", { 'message': results, 'channel_plan_id': channel_plan_id, 'date_from': date_from, @@ -483,7 +487,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[0], self._session_info[1]) if rcode != 0: - raise ValidationError(_("Can't fetch restriction plans from wubook"), { + raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, }) return results @@ -496,7 +500,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), channel_restriction_plan_id) if rcode != 0: - raise ValidationError(_("Can't fetch restriction plans from wubook"), { + raise ChannelConnectorError("Can't fetch restriction plans from wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, @@ -512,7 +516,7 @@ class WuBookAdapter(AbstractComponent): date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), values) if rcode != 0: - raise ValidationError(_("Can't update plan restrictions on wubook"), { + raise ChannelConnectorError("Can't update plan restrictions on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, 'date_from': date_from, @@ -526,7 +530,7 @@ class WuBookAdapter(AbstractComponent): name, compact and 1 or 0) if rcode != 0: - raise ValidationError(_("Can't create plan restriction in wubook"), { + raise ChannelConnectorError("Can't create plan restriction in wubook", { 'message': results, }) return results @@ -538,7 +542,7 @@ class WuBookAdapter(AbstractComponent): channel_restriction_plan_id, new_name) if rcode != 0: - raise ValidationError(_("Can't rename plan restriction in wubook"), { + raise ChannelConnectorError("Can't rename plan restriction in wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) @@ -550,7 +554,7 @@ class WuBookAdapter(AbstractComponent): self._session_info[1], channel_restriction_plan_id) if rcode != 0: - raise ValidationError(_("Can't delete plan restriction on wubook"), { + raise ChannelConnectorError("Can't delete plan restriction on wubook", { 'message': results, 'channel_restriction_plan_id': channel_restriction_plan_id, }) @@ -559,7 +563,7 @@ class WuBookAdapter(AbstractComponent): def get_channels_info(self): results = self._server.get_channels_info(self._session_info[0]) if not any(results): - raise ValidationError(_("Can't import channels info from wubook"), { + raise ChannelConnectorError("Can't import channels info from wubook", { 'message': results, }) return results diff --git a/hotel_channel_connector/components/binder.py b/hotel_channel_connector/components/binder.py index 935773804..3276998d5 100644 --- a/hotel_channel_connector/components/binder.py +++ b/hotel_channel_connector/components/binder.py @@ -12,4 +12,5 @@ class HotelConnectorModelBinder(Component): 'channel.hotel.room.type.availability', 'channel.hotel.room.type.restriction', 'channel.product.pricelist', + 'channel.ota.info', ] diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py index d47d6b2fc..951c40996 100644 --- a/hotel_channel_connector/components/core.py +++ b/hotel_channel_connector/components/core.py @@ -7,16 +7,21 @@ from odoo import api class BaseHotelChannelConnectorComponent(AbstractComponent): _name = 'base.hotel.channel.connector' _inherit = 'base.connector' - _collection = 'hotel.channel.backend' + _collection = 'channel.backend' @api.model def create_issue(self, section, message, channel_message, channel_object_id=False, dfrom=False, dto=False): self.env['hotel.channel.connector.issue'].sudo().create({ 'section': section, - 'message': message, + 'internal_message': message, 'channel_object_id': channel_object_id, 'channel_message': channel_message, 'date_start': dfrom, 'date_end': dto, }) + +class ChannelConnectorError(Exception): + def __init__(self, message, data): + super().__init__(message) + self.data = data diff --git a/hotel_channel_connector/components/exporter.py b/hotel_channel_connector/components/exporter.py index a82ea45ad..fb42e8e61 100644 --- a/hotel_channel_connector/components/exporter.py +++ b/hotel_channel_connector/components/exporter.py @@ -2,6 +2,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging +import psycopg2 +from contextlib import contextmanager +from odoo.addons.connector.exception import (IDMissingInBackend, + RetryableJobError) from odoo.addons.component.core import AbstractComponent from odoo.tools import ( DEFAULT_SERVER_DATE_FORMAT, diff --git a/hotel_channel_connector/components/importer.py b/hotel_channel_connector/components/importer.py index 13bf9402f..dc94680dd 100644 --- a/hotel_channel_connector/components/importer.py +++ b/hotel_channel_connector/components/importer.py @@ -7,6 +7,7 @@ import json from datetime import timedelta from odoo.exceptions import ValidationError from odoo.addons.component.core import AbstractComponent, Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel import date_utils from odoo import _ from odoo.tools import ( @@ -130,7 +131,7 @@ class HotelChannelConnectorImporter(AbstractComponent): @api.model def _generate_booking_vals(self, broom, checkin_str, checkout_str, - is_cancellation, wchannel_info, wstatus, crcode, + is_cancellation, channel_info, wstatus, crcode, rcode, room_type, split_booking, dates_checkin, dates_checkout, book): # Generate Reservation Day Lines @@ -154,24 +155,27 @@ class HotelChannelConnectorImporter(AbstractComponent): persons = room_type.wcapacity if 'ancillary' in broom and 'guests' in broom['ancillary']: persons = broom['ancillary']['guests'] + vals = { - 'checkin': checkin_str, - 'checkout': checkout_str, - 'adults': persons, - 'children': book['children'], - 'reservation_line_ids': reservation_line_ids, - 'price_unit': tprice, - 'to_assign': True, - 'wrid': rcode, - 'wchannel_id': wchannel_info and wchannel_info.id, - 'wchannel_reservation_code': crcode, + 'channel_reservation_id': rcode, + 'ota_id': channel_info and channel_info.id, + 'ota_reservation_id': crcode, + 'channel_raw_data': json.dumps(book), 'wstatus': wstatus, - 'to_read': True, - 'state': is_cancellation and 'cancelled' or 'draft', - 'room_type_id': room_type.id, - 'splitted': split_booking, - 'wbook_json': json.dumps(book), - 'wmodified': book['was_modified'] + 'wmodified': book['was_modified'], + 'odoo_id': [0, False, { + 'checkin': checkin_str, + 'checkout': checkout_str, + 'adults': persons, + 'children': book['children'], + 'reservation_line_ids': reservation_line_ids, + 'price_unit': tprice, + 'to_assign': True, + 'to_read': True, + 'state': is_cancellation and 'cancelled' or 'draft', + 'room_type_id': room_type.id, + 'splitted': split_booking, + }], } _logger.info("===== CONTRUCT RESERV") _logger.info(vals) @@ -210,8 +214,10 @@ class HotelChannelConnectorImporter(AbstractComponent): tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') res_partner_obj = self.env['res.partner'] + channel_reserv_obj = self.env['channel.hotel.reservation'] hotel_reserv_obj = self.env['hotel.reservation'] hotel_folio_obj = self.env['hotel.folio'] + channel_room_type_obj = self.env['channel.hotel.room.type'] hotel_room_type_obj = self.env['hotel.room.type'] # Space for store some data for construct folios processed_rids = [] @@ -231,10 +237,10 @@ class HotelChannelConnectorImporter(AbstractComponent): # (for example set a invalid new reservation and receive in # the same transaction an cancellation) if crcode in failed_reservations: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Can't process a reservation that previusly failed!", - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) continue # Get dates for the reservation (GMT->UTC) @@ -260,34 +266,37 @@ class HotelChannelConnectorImporter(AbstractComponent): # Search Folio. If exists. folio_id = False if crcode != 'undefined': - reserv_folio = hotel_reserv_obj.search([ - ('wchannel_reservation_code', '=', crcode) + reserv_folio = channel_reserv_obj.search([ + ('ota_reservation_id', '=', crcode) ], limit=1) if reserv_folio: - folio_id = reserv_folio.folio_id + folio_id = reserv_folio.odoo_id.folio_id else: - reserv_folio = hotel_reserv_obj.search([ - ('wrid', '=', rcode) + reserv_folio = channel_reserv_obj.search([ + ('channel_reservation_id', '=', rcode) ], limit=1) if reserv_folio: - folio_id = reserv_folio.folio_id + folio_id = reserv_folio.odoo_id.folio_id # Need update reservations? - sreservs = hotel_reserv_obj.search([('wrid', '=', rcode)]) - reservs = folio_id.room_lines if folio_id else sreservs + sreservs = channel_reserv_obj.search([('channel_reservation_id', '=', rcode)]) + reservs = folio_id.room_lines if folio_id else sreservs.mapped(lambda x: x.odoo_id) reservs_processed = False if any(reservs): folio_id = reservs[0].folio_id for reserv in reservs: - if reserv.wrid == rcode: - reserv.with_context({'wubook_action': False}).write({ + if reserv.channel_reservation_id == rcode: + binding_id = reserv.channel_bind_ids[0] + binding_id.write({ + 'channel_raw_data': json.dumps(book), 'wstatus': str(book['status']), 'wstatus_reason': book.get('status_reason', ''), + }) + reserv.with_context({'wubook_action': False}).write({ 'to_read': True, 'to_assign': True, 'price_unit': book['amount'], - 'wcustomer_notes': book['customer_notes'], - 'wbook_json': json.dumps(book), + 'customer_notes': book['customer_notes'], }) if reserv.partner_id.unconfirmed: reserv.partner_id.write( @@ -321,22 +330,22 @@ class HotelChannelConnectorImporter(AbstractComponent): partner_id = res_partner_obj.create(self._generate_partner_vals(book)) # Search Wubook Channel Info - wchannel_info = self.env['wubook.channel.info'].search( - [('wid', '=', str(book['id_channel']))], limit=1) + channel_info = self.env['hotel.channel.connector.ota.info'].search( + [('ota_id', '=', str(book['id_channel']))], limit=1) reservations = [] used_rooms = [] # Iterate booked rooms for broom in book['booked_rooms']: - room_type = hotel_room_type_obj.search([ - ('wrid', '=', broom['room_id']) + room_type = channel_room_type_obj.search([ + ('channel_room_id', '=', broom['room_id']) ], limit=1) if not room_type: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Can't found any room type associated to '%s' \ in this hotel" % book['rooms'], - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) failed_reservations.append(crcode) continue @@ -355,26 +364,26 @@ class HotelChannelConnectorImporter(AbstractComponent): checkin_str, checkout_str, is_cancellation, - wchannel_info, + channel_info, bstatus, crcode, rcode, - room_type, + room_type.odoo_id, split_booking, dates_checkin, dates_checkout, book, ) if vals['price_unit'] != book['amount']: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), - '', wid=book['reservation_code']) + '', channel_object_id=book['reservation_code']) - free_rooms = hotel_room_type_obj.check_availability_room( + free_rooms = room_type.odoo_id.check_availability_room( checkin_str, checkout_str, - room_type_id=room_type.id, + room_type_id=room_type.odoo_id.id, notthis=used_rooms) if any(free_rooms): vals.update({ @@ -412,11 +421,11 @@ class HotelChannelConnectorImporter(AbstractComponent): checkin_utc_dt, checkout_utc_dt, is_cancellation, - wchannel_info, + channel_info, bstatus, crcode, rcode, - room_type, + room_type.odoo_id, False, (checkin_utc_dt, False), (checkout_utc_dt, False), @@ -424,15 +433,15 @@ class HotelChannelConnectorImporter(AbstractComponent): ) vals.update({ 'product_id': - room_type.room_ids[0].product_id.id, - 'name': room_type.name, + room_type.odoo_id.room_ids[0].product_id.id, + 'name': room_type.odoo_id.name, 'overbooking': True, }) reservations.append((0, False, vals)) - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Reservation imported with overbooking state", - '', wid=rcode) + '', channel_object_id=rcode) dates_checkin = [False, False] dates_checkout = [False, False] split_booking = False @@ -448,10 +457,10 @@ class HotelChannelConnectorImporter(AbstractComponent): ] if split_booking: - self.create_channel_connector_issue( + self.create_issue( 'reservation', "Reservation Splitted", - '', wid=rcode) + '', channel_object_id=rcode) # Create Folio if not any(failed_reservations) and any(reservations): @@ -487,11 +496,11 @@ class HotelChannelConnectorImporter(AbstractComponent): creserv.parent_reservation = preserv.id processed_rids.append(rcode) - except Exception as e_msg: - self.create_channel_connector_issue( + except ChannelConnectorError as err: + self.create_issue( 'reservation', - e_msg[0], - '', wid=rcode) + err.data['message'], + '', channel_object_id=rcode) failed_reservations.append(crcode) return (processed_rids, any(failed_reservations), checkin_utc_dt, checkout_utc_dt) @@ -654,84 +663,6 @@ class HotelChannelConnectorImporter(AbstractComponent): return True - @api.model - def _generate_wubook_channel_info(self, channels): - channel_info_obj = self.env['wubook.channel.info'] - count = 0 - for k_cid, v_cid in channels.iteritems(): - vals = { - 'name': v_cid['name'], - 'ical': v_cid['ical'] == 1, - } - channel_info = channel_info_obj.search([ - ('wid', '=', k_cid) - ], limit=1) - if channel_info: - channel_info.write(vals) - else: - vals.update({ - 'wid': k_cid - }) - channel_info_obj.create(vals) - count = count + 1 - return count - - @api.model - def get_rooms(self): - count = 0 - try: - results = self.backend_adapter.fetch_rooms() - - room_type_obj = self.env['hotel.room.type'] - count = len(results) - for room in results: - vals = { - 'name': room['name'], - 'wrid': room['id'], - 'wscode': room['shortname'], - 'list_price': room['price'], - 'wcapacity': room['occupancy'], - # 'max_real_rooms': room['availability'], - } - room_type = room_type_obj.search([('wrid', '=', room['id'])], limit=1) - if room_type: - room_type.with_context({'wubook_action': False}).write(vals) - else: - room_type_obj.with_context({'wubook_action': False}).create(vals) - except ValidationError: - self.create_issue('room', _("Can't import rooms from WuBook"), results) - - return count - - @api.model - def fetch_rooms_values(self, dfrom, dto, rooms=False, - set_max_avail=False): - # Sanitize Dates - now_dt = date_utils.now() - dfrom_dt = date_utils.get_datetime(dfrom) - dto_dt = date_utils.get_datetime(dto) - if dto_dt < now_dt: - return True - if dfrom_dt < now_dt: - dfrom_dt = now_dt - if dfrom_dt > dto_dt: - dtemp_dt = dfrom_dt - dfrom_dt = dto_dt - dto_dt = dtemp_dt - - try: - results = self.backend_adapter.fetch_rooms_values( - dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - rooms) - self._generate_room_values(dfrom, dto, results, - set_max_avail=set_max_avail) - except ValidationError: - self.create_issue('room', _("Can't fetch rooms values from WuBook"), - results, dfrom=dfrom, dto=dto) - return False - return True - @api.model def fetch_booking(self, channel_reservation_id): try: @@ -746,11 +677,11 @@ class HotelChannelConnectorImporter(AbstractComponent): self.backend_adapter.fetch_rooms_values( checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) - except ValidationError: - self.create_channel_connector_issue( + except ChannelConnectorError as err: + self.create_issue( 'reservation', _("Can't process reservations from wubook"), - results, channel_object_id=channel_reservation_id) + err.data['message'], channel_object_id=channel_reservation_id) return False return True @@ -759,11 +690,11 @@ class HotelChannelConnectorImporter(AbstractComponent): try: results = self.backend_adapter.get_pricing_plans() count = self._generate_pricelists(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', _("Can't get pricing plans from wubook"), - results) + err.data['message']) return 0 return count @@ -776,11 +707,11 @@ class HotelChannelConnectorImporter(AbstractComponent): date_to, rooms) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', _("Can't fetch plan prices from wubook"), - results) + err.data['message']) return False return True @@ -799,11 +730,12 @@ class HotelChannelConnectorImporter(AbstractComponent): date_to, rooms) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'plan', "Can't fetch all plan prices from wubook!", - results, wid=channel_plan_id, dfrom=date_from, dto=date_to) + err.data['message'], + channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to) return False return no_errors @@ -812,11 +744,11 @@ class HotelChannelConnectorImporter(AbstractComponent): try: results = self.backend_adapter.rplan_rplans() count = self._generate_restrictions(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'rplan', _("Can't fetch restriction plans from wubook"), - results) + err.data['message']) return 0 return count @@ -829,25 +761,39 @@ class HotelChannelConnectorImporter(AbstractComponent): int(channel_restriction_plan_id)) if any(results): self._generate_restriction_items(results) - except ValidationError: + except ChannelConnectorError as err: self.create_issue( 'rplan', _("Can't fetch plan restrictions from wubook"), - results, - wid=channel_restriction_plan_id, + err.data['message'], + channel_object_id=channel_restriction_plan_id, dfrom=date_from, dto=date_to) return False return True - @api.model - def import_channels_info(self): - try: - results = self.backend_adapter.get_channels_info() - count = self._generate_wubook_channel_info(results) - except ValidationError: - self.create_issue( - 'channel', - _("Can't import channels info from wubook"), - results) - return 0 - return count +class BatchImporter(AbstractComponent): + """ The role of a BatchImporter is to search for a list of + items to import, then it can either import them directly or delay + the import of each item separately. + """ + + _name = 'channel.batch.importer' + _inherit = ['base.importer', 'base.hotel.channel.connector'] + _usage = 'batch.importer' + + def _import_record(self, external_id): + """ Import a record directly or delay the import of the record. + + Method to implement in sub-classes. + """ + raise NotImplementedError + +class DirectBatchImporter(AbstractComponent): + """ Import the records directly, without delaying the jobs. """ + + _name = 'channel.direct.batch.importer' + _inherit = 'channel.batch.importer' + + def _import_record(self, external_id): + """ Import the record directly """ + self.model.import_record(self.backend_record, external_id) diff --git a/hotel_channel_connector/components/mapper.py b/hotel_channel_connector/components/mapper.py new file mode 100644 index 000000000..ad2ed03b9 --- /dev/null +++ b/hotel_channel_connector/components/mapper.py @@ -0,0 +1,16 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import AbstractComponent + + +class ChannelImportMapper(AbstractComponent): + _name = 'channel.import.mapper' + _inherit = ['base.hotel.channel.connector', 'base.import.mapper'] + _usage = 'import.mapper' + + +class ChannelExportMapper(AbstractComponent): + _name = 'channel.export.mapper' + _inherit = ['base.hotel.channel.connector', 'base.export.mapper'] + _usage = 'export.mapper' diff --git a/hotel_channel_connector/data/menus.xml b/hotel_channel_connector/data/menus.xml index 4f0fdc237..06b13e837 100644 --- a/hotel_channel_connector/data/menus.xml +++ b/hotel_channel_connector/data/menus.xml @@ -1,28 +1,28 @@ - - Hotel Channel Connector OTA's Info - hotel.channel.connector.ota.info - form - tree,form - + - - Hotel Channel Connector Issues - hotel.channel.connector.issue - form - tree,form - - {"search_default_to_read":True} - + - + - - + diff --git a/hotel_channel_connector/models/__init__.py b/hotel_channel_connector/models/__init__.py index 96726f45d..622a159e4 100644 --- a/hotel_channel_connector/models/__init__.py +++ b/hotel_channel_connector/models/__init__.py @@ -1,17 +1,17 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import channel_backend from . import channel_binding -from . import res_config +from . import channel_backend from . import hotel_room_type from . import product_pricelist from . import inherited_product_pricelist_item from . import hotel_room_type_restriction -from . import inherited_reservation_restriction_item +from . import hotel_room_type_restriction_item from . import hotel_room_type_availability from . import hotel_reservation from . import inherited_hotel_folio from . import inherited_res_partner -from . import hotel_channel_connector_ota_info +from . import channel_ota_info from . import hotel_channel_connector_issue +from . import res_config diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 776bf5d04..88b898522 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -1,6 +1,8 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os +import binascii from contextlib import contextmanager from odoo import models, api, fields from ...components.backend_adapter import WuBookLogin, WuBookServer @@ -27,12 +29,32 @@ class ChannelBackend(models.Model): server = fields.Char('Channel Service Server', default='https://wired.wubook.net/xrws/') pkey = fields.Char('Channel Service PKey') + security_token = fields.Char('Channel Service Security Token') + + @api.multi + def generate_key(self): + for record in self: + record.security_token = binascii.hexlify(os.urandom(32)).decode() @api.multi def import_reservations(self): - channel_hotel_reservation = self.env['channel.hotel.reservation'] + channel_hotel_reservation_obj = self.env['channel.hotel.reservation'] for backend in self: - channel_hotel_reservation.import_reservations(backend) + channel_hotel_reservation_obj.import_reservations(backend) + return True + + @api.multi + def import_rooms(self): + channel_hotel_room_type_obj = self.env['channel.hotel.room.type'] + for backend in self: + channel_hotel_room_type_obj.import_rooms(backend) + return True + + @api.multi + def import_otas_info(self): + channel_ota_info_obj = self.env['channel.ota.info'] + for backend in self: + channel_ota_info_obj.import_otas_info(backend) return True @contextmanager @@ -47,6 +69,165 @@ class ChannelBackend(models.Model): self.pkey) with WuBookServer(wubook_login) as channel_api: _super = super(ChannelBackend, self) - # from the components we'll be able to do: self.work.channel_api with _super.work_on(model_name, channel_api=channel_api, **kwargs) as work: yield work + + # Dangerus method: Usefull for cloned instances with new wubook account + @api.multi + def resync(self): + self.ensure_one() + + now_utc_dt = fields.Date.now() + now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + + # Reset Issues + issue_ids = self.env['wubook.issue'].search([]) + issue_ids.write({ + 'to_read': False + }) + + # Push Virtual Rooms + wubook_obj = self.env['wubook'].with_context({ + 'init_connection': False + }) + if wubook_obj.init_connection(): + ir_seq_obj = self.env['ir.sequence'] + room_types = self.env['hotel.room.type'].search([]) + for room_type in room_types: + shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4] + channel_room_id = wubook_obj.create_room( + shortcode, + room_type.name, + room_type.wcapacity, + room_type.list_price, + room_type.total_rooms_count + ) + if channel_room_id: + room_type.with_context(wubook_action=False).write({ + 'channel_room_id': channel_room_id, + 'wscode': shortcode, + }) + else: + room_type.with_context(wubook_action=False).write({ + 'channel_room_id': '', + 'wscode': '', + }) + # Create Restrictions + room_type_rest_obj = self.env['hotel.room.type.restriction'] + restriction_ids = room_type_rest_obj.search([]) + for restriction in restriction_ids: + if restriction.wpid != '0': + channel_plan_id = wubook_obj.create_rplan(restriction.name) + restriction.write({ + 'channel_plan_id': channel_plan_id or '' + }) + # Create Pricelist + pricelist_ids = self.env['product.pricelist'].search([]) + for pricelist in pricelist_ids: + channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.is_daily_plan) + pricelist.write({ + 'channel_plan_id': channel_plan_id or '' + }) + wubook_obj.close_connection() + + # Reset Folios + folio_ids = self.env['hotel.folio'].search([]) + folio_ids.with_context(wubook_action=False).write({ + 'wseed': '', + }) + + # Reset Reservations + reservation_ids = self.env['hotel.reservation'].search([ + ('channel_reservation_id', '!=', ''), + ('channel_reservation_id', '!=', False) + ]) + reservation_ids.with_context(wubook_action=False).write({ + 'channel_reservation_id': '', + 'ota_id': False, + 'ota_reservation_id': '', + 'is_from_ota': False, + 'wstatus': 0 + }) + + # Get Parity Models + pricelist_id = int(self.env['ir.default'].sudo().get( + 'res.config.settings', 'parity_pricelist_id')) + restriction_id = int(self.env['ir.default'].sudo().get( + 'res.config.settings', 'parity_restrictions_id')) + + room_type_restr_it_obj = self.env['hotel.room.type.restriction.item'] + # Secure Wubook Input + restriction_item_ids = room_type_restr_it_obj.search([ + ('applied_on', '=', '0_room_type'), + ('date_start', '<', now_utc_str), + ]) + if any(restriction_item_ids): + restriction_item_ids.with_context(wubook_action=False).write({ + 'wpushed': True + }) + # Put to push restrictions + restriction_item_ids = room_type_restr_it_obj.search([ + ('restriction_id', '=', restriction_id), + ('applied_on', '=', '0_room_type'), + ('wpushed', '=', True), + ('date_start', '>=', now_utc_str), + ]) + if any(restriction_item_ids): + restriction_item_ids.with_context(wubook_action=False).write({ + 'wpushed': False + }) + + # Secure Wubook Input + pricelist_item_ids = self.env['product.pricelist.item'].search([ + ('applied_on', '=', '1_product'), + ('compute_price', '=', 'fixed'), + ('date_start', '<', now_utc_str), + ]) + if any(pricelist_item_ids): + pricelist_item_ids.with_context(wubook_action=False).write({ + 'wpushed': True + }) + # Put to push pricelists + pricelist_item_ids = self.env['product.pricelist.item'].search([ + ('pricelist_id', '=', pricelist_id), + ('applied_on', '=', '1_product'), + ('compute_price', '=', 'fixed'), + ('wpushed', '=', True), + ('date_start', '>=', now_utc_str), + ]) + if any(pricelist_item_ids): + pricelist_item_ids.with_context(wubook_action=False).write({ + 'wpushed': False + }) + + # Secure Wubook Input + availabity_ids = self.env['hotel.room.type.availability'].search([ + ('date', '<', now_utc_str), + ]) + if any(availabity_ids): + availabity_ids.with_context(wubook_action=False).write({ + 'wpushed': True + }) + # Put to push availability + availabity_ids = self.env['hotel.room.type.availability'].search([ + ('wpushed', '=', True), + ('date', '>=', now_utc_str), + ]) + if any(availabity_ids): + availabity_ids.with_context(wubook_action=False).write({ + 'wpushed': False + }) + + # Generate Security Token + self.env['ir.default'].sudo().set( + 'wubook.config.settings', + 'wubook_push_security_token', + binascii.hexlify(os.urandom(16)).decode()) + self.env.cr.commit() # FIXME: Need do this + + # Push Changes + if wubook_obj.init_connection(): + wubook_obj.push_activation() + wubook_obj.import_channels_info() + wubook_obj.push_changes() + wubook_obj.close_connection() diff --git a/hotel_channel_connector/models/channel_binding/common.py b/hotel_channel_connector/models/channel_binding/common.py index dc8b8616f..0db43c593 100644 --- a/hotel_channel_connector/models/channel_binding/common.py +++ b/hotel_channel_connector/models/channel_binding/common.py @@ -15,3 +15,8 @@ class ChannelBinding(models.AbstractModel): string='Hotel Channel Connector Backend', required=True, ondelete='restrict') + + _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/__init__.py b/hotel_channel_connector/models/channel_ota_info/__init__.py new file mode 100644 index 000000000..06e54858b --- /dev/null +++ b/hotel_channel_connector/models/channel_ota_info/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import common +from . import importer diff --git a/hotel_channel_connector/models/channel_ota_info/common.py b/hotel_channel_connector/models/channel_ota_info/common.py new file mode 100644 index 000000000..8f1ed528e --- /dev/null +++ b/hotel_channel_connector/models/channel_ota_info/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.addons.queue_job.job import job +from odoo.addons.component.core import Component + +class ChannelOtaInfo(models.Model): + _name = 'channel.ota.info' + _inherit = 'channel.binding' + _description = 'Channel OTA Info' + + ota_id = fields.Char("Channel OTA ID", required=True) + name = fields.Char("OTA Name", required=True) + ical = fields.Boolean("ical", default=False) + + @job(default_channel='root.channel') + @api.model + def import_otas_info(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='ota.info.importer') + return importer.import_otas_info() + +class HotelRoomTypeAdapter(Component): + _name = 'channel.ota.info.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.ota.info' + + def fetch_rooms(self): + return super(HotelRoomTypeAdapter, self).fetch_rooms() diff --git a/hotel_channel_connector/models/channel_ota_info/importer.py b/hotel_channel_connector/models/channel_ota_info/importer.py new file mode 100644 index 000000000..6fed02a7d --- /dev/null +++ b/hotel_channel_connector/models/channel_ota_info/importer.py @@ -0,0 +1,62 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.connector.components.mapper import mapping +from odoo import fields, api, _ +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) + + +class ChannelOtaInfoImporter(Component): + _name = 'channel.ota.info.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.ota.info'] + _usage = 'ota.info.importer' + + @api.model + def import_otas_info(self): + count = 0 + try: + results = self.backend_adapter.get_channels_info() + + channel_ota_info_obj = self.env['channel.ota.info'] + ota_info_mapper = self.component(usage='import.mapper', + model_name='channel.ota.info') + for ota_id in results.keys(): + vals = { + 'id': ota_id, + 'name': results[ota_id]['name'], + 'ical': results[ota_id]['ical'] == 1, + } + map_record = ota_info_mapper.map_record(vals) + ota_info_bind = channel_ota_info_obj.search([ + ('ota_id', '=', ota_id) + ], limit=1) + if ota_info_bind: + ota_info_bind.write(map_record.values()) + else: + 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']) + return count + + +class ChannelOtaInfoImportMapper(Component): + _name = 'channel.ota.info.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.ota.info' + + direct = [ + ('id', 'ota_id'), + ('name', 'name'), + ('ical', 'ical'), + ] + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/models/hotel_channel_connector_ota_info.py b/hotel_channel_connector/models/hotel_channel_connector_ota_info.py deleted file mode 100644 index 3ed70d301..000000000 --- a/hotel_channel_connector/models/hotel_channel_connector_ota_info.py +++ /dev/null @@ -1,20 +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 odoo.addons.queue_job.job import job - -class HotelChannelConnectorOTAInfo(models.Model): - _name = 'hotel.channel.connector.ota.info' - - ota_id = fields.Char("Channel OTA ID", required=True) - name = fields.Char("OTA Name", required=True) - ical = fields.Boolean("ical", default=False) - - @job(default_channel='root.channel') - @api.multi - def import_channels_info(self): - self.ensure_one() - with self.backend_id.work_on(self._name) as work: - importer = work.component(usage='channel.importer') - return importer.import_channels_info() diff --git a/hotel_channel_connector/models/hotel_reservation/common.py b/hotel_channel_connector/models/hotel_reservation/common.py index bf92a9bca..87ed888d2 100644 --- a/hotel_channel_connector/models/hotel_reservation/common.py +++ b/hotel_channel_connector/models/hotel_reservation/common.py @@ -53,7 +53,7 @@ class ChannelHotelReservation(models.Model): @api.model def import_reservations(self, backend): with backend.work_on(self._name) as work: - importer = work.component(usage='channel.hotel.reservation.importer') + importer = work.component(usage='hotel.reservation.importer') return importer.fetch_new_bookings() @api.depends('channel_reservation_id', 'ota_id') @@ -271,7 +271,15 @@ class HotelReservation(models.Model): if not self.is_from_ota: return super().on_change_checkin_checkout_product_id() -class ChannelBindingProductListener(Component): +class HotelReservationAdapter(Component): + _name = 'channel.hotel.reservation.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.reservation' + + def fetch_new_bookings(self): + return super(HotelReservationAdapter, self).fetch_new_bookings() + +class ChannelBindingHotelReservationListener(Component): _name = 'channel.binding.hotel.reservation.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.reservation'] diff --git a/hotel_channel_connector/models/hotel_reservation/importer.py b/hotel_channel_connector/models/hotel_reservation/importer.py index e4e673b1d..565b24ac9 100644 --- a/hotel_channel_connector/models/hotel_reservation/importer.py +++ b/hotel_channel_connector/models/hotel_reservation/importer.py @@ -3,6 +3,7 @@ from odoo.exceptions import ValidationError from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo import fields, api, _ from odoo.tools import ( DEFAULT_SERVER_DATE_FORMAT, @@ -14,3 +15,30 @@ class HotelReservationImporter(Component): _inherit = 'hotel.channel.importer' _apply_on = ['channel.hotel.reservation'] _usage = 'hotel.reservation.importer' + + def fetch_new_bookings(self): + try: + results = self.backend_adapter.fetch_new_bookings() + processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \ + self._generate_reservations(results) + if any(processed_rids): + uniq_rids = list(set(processed_rids)) + rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids) + if rcodeb != 0: + self.create_issue( + 'wubook', + _("Problem trying mark bookings (%s)") % str(processed_rids), + '') + # Update Odoo availability (don't wait for wubook) + # This cause abuse service in first import!! + if checkin_utc_dt and checkout_utc_dt: + self.backend_adapter.fetch_rooms_values( + checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + except ChannelConnectorError as err: + self.create_issue( + 'reservation', + _("Can't process reservations from wubook"), + err.data['message']) + return False + return True diff --git a/hotel_channel_connector/models/hotel_room_type/__init__.py b/hotel_channel_connector/models/hotel_room_type/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type/__init__.py @@ -2,3 +2,5 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index b967543cb..5af7a14b3 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -1,11 +1,13 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging from odoo import api, models, fields, _ from odoo.exceptions import ValidationError from odoo.addons.queue_job.job import job, related_action from odoo.addons.component.core import Component from odoo.addons.component_event import skip_if +_logger = logging.getLogger(__name__) class ChannelHotelRoomType(models.Model): _name = 'channel.hotel.room.type' @@ -21,6 +23,22 @@ class ChannelHotelRoomType(models.Model): channel_short_code = fields.Char("Channel Short Code", readonly=True, old_name='wscode') ota_capacity = fields.Integer("OTA's Capacity", default=1, old_name='wcapacity') + @api.onchange('room_ids') + def _get_capacity(self): + for rec in self: + rec.ota_capacity = rec.odoo_id.get_capacity() + + def _check_self_unlink(self): + if not self.odoo_id: + self.sudo().unlink() + + @job(default_channel='root.channel') + @api.model + def import_rooms(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='hotel.room.type.importer') + return importer.get_rooms() + @api.constrains('ota_capacity') def _check_ota_capacity(self): for record in self: @@ -39,65 +57,30 @@ class ChannelHotelRoomType(models.Model): @api.multi def create_room(self): self.ensure_one() - if self._context.get('channel_action', True): - seq_obj = self.env['ir.sequence'] - shortcode = seq_obj.next_by_code('hotel.room.type')[:4] + if not self.channel_room_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - channel_room_id = adapter.create_room( - shortcode, - self.name, - self.ota_capacity, - self.list_price, - self.total_rooms_count) - if channel_room_id: - self.write({ - 'channel_room_id': channel_room_id, - 'channel_short_code': shortcode, - }) - except ValidationError as e: - self.create_issue('room', "Can't create room on channel", "sss") + exporter = work.component(usage='hotel.room.type.exporter') + exporter.create_room(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def modify_room(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.channel_room_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.modify_room( - self.channel_room_id, - self.name, - self.ota_capacity, - self.list_price, - self.total_rooms_count, - self.channel_short_code) - except ValidationError as e: - self.create_issue('room', "Can't modify room on channel", "sss") + exporter = work.component(usage='hotel.room.type.exporter') + exporter.modify_room(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def delete_room(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.channel_room_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.delete_room(self.channel_room_id) - except ValidationError as e: - self.create_issue('room', "Can't delete room on channel", "sss") - - @job(default_channel='root.channel') - @api.multi - def import_rooms(self): - if self._context.get('channel_action', True): - with self.backend_id.work_on(self._name) as work: - importer = work.component(usage='channel.importer') - return importer.import_rooms() + exporter = work.component(usage='hotel.room.type.exporter') + exporter.delete_room(self) class HotelRoomType(models.Model): _inherit = 'hotel.room.type' @@ -107,10 +90,16 @@ class HotelRoomType(models.Model): inverse_name='odoo_id', string='Hotel Channel Connector Bindings') + capacity = fields.Integer("Capacity", compute="_compute_capacity") + + @api.multi + def _compute_capacity(self): + for record in self: + record.capacity = record.get_capacity() + @api.onchange('room_ids') - def _get_capacity(self): - for rec in self: - rec.channel_bind_ids.ota_capacity = rec.get_capacity() + def _onchange_room_ids(self): + self._compute_capacity() @api.multi def get_restrictions(self, date): @@ -118,26 +107,61 @@ class HotelRoomType(models.Model): 'res.config.settings', 'parity_restrictions_id')) self.ensure_one() restriction = self.env['hotel.room.type.restriction.item'].search([ - ('date_start', '=', date), - ('date_end', '=', date), + ('date', '=', date), ('room_type_id', '=', self.id), ('restriction_id', '=', restriction_plan_id) ], limit=1) return restriction + @api.multi + def create_bindings(self): + backends = self.env['channel.backend'].search([]) + binding_obj = self.env['channel.hotel.room.type'] + for backend in backends: + binding = binding_obj.search([ + ('odoo_id', '=', self.id), + ('backend_id', '=', backend.id)], limit=1) + if not binding: + binding_obj.sudo().create({ + 'odoo_id': self.id, + 'backend_id': backend.id, + }) + +class HotelRoomTypeAdapter(Component): + _name = 'channel.hotel.room.type.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.hotel.room.type' + + def fetch_rooms(self): + return super(HotelRoomTypeAdapter, self).fetch_rooms() + +class BindingHotelRoomTypeListener(Component): + _name = 'binding.hotel.room.type.listener' + _inherit = 'base.connector.listener' + _apply_on = ['hotel.room.type'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if 'name' in fields or 'list_price' in fields: + record.channel_bind_ids[0].modify_room() + + # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + # def on_record_create(self, record, fields=None): + # record.create_bindings() + class ChannelBindingRoomTypeListener(Component): _name = 'channel.binding.room.type.listener' _inherit = 'base.connector.listener' - _apply_on = ['channel.room.type'] + _apply_on = ['channel.hotel.room.type'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_write(self, record, fields=None): - record.with_delay(priority=20).create_room() + def on_record_create(self, record, fields=None): + record.create_room() @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_room() + record.delete_room() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - record.with_delay(priority=20).modidy_room() + record.modify_room() diff --git a/hotel_channel_connector/models/hotel_room_type/exporter.py b/hotel_channel_connector/models/hotel_room_type/exporter.py new file mode 100644 index 000000000..5ee014734 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type/exporter.py @@ -0,0 +1,55 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo import api, _ +_logger = logging.getLogger(__name__) + +class HotelRoomTypeExporter(Component): + _name = 'channel.hotel.room.type.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type'] + _usage = 'hotel.room.type.exporter' + + @api.model + def modify_room(self, binding): + try: + return self.backend_adapter.modify_room( + binding.channel_room_id, + binding.name, + binding.ota_capacity, + binding.list_price, + binding.total_rooms_count, + binding.channel_short_code) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't modify rooms in WuBook"), err.data['message']) + + @api.model + def delete_room(self, binding): + try: + return self.backend_adapter.delete_room(binding.channel_room_id) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't delete room in WuBook"), err.data['message']) + + @api.model + def create_room(self, binding): + try: + seq_obj = self.env['ir.sequence'] + short_code = seq_obj.next_by_code('hotel.room.type')[:4] + external_id = self.backend_adapter.create_room( + short_code, + binding.name, + binding.ota_capacity, + binding.list_price, + binding.total_rooms_count + ) + binding.write({ + 'channel_room_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']) + 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 new file mode 100644 index 000000000..d1728e3f6 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -0,0 +1,170 @@ +# 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.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, _ +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) +from odoo.addons.hotel_channel_connector.components.backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT +_logger = logging.getLogger(__name__) + + +class HotelRoomTypeImporter(Component): + _name = 'channel.hotel.room.type.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.hotel.room.type'] + _usage = 'hotel.room.type.importer' + + @api.model + def get_rooms(self): + count = 0 + try: + results = self.backend_adapter.fetch_rooms() + + channel_room_type_obj = self.env['channel.hotel.room.type'] + room_mapper = self.component(usage='import.mapper', + model_name='channel.hotel.room.type') + count = len(results) + for room in results: + map_record = room_mapper.map_record(room) + room_bind = channel_room_type_obj.search([ + ('channel_room_id', '=', room['id']) + ], limit=1) + if room_bind: + room_bind.with_context({'wubook_action': False}).write(map_record.values()) + else: + room_bind = channel_room_type_obj.with_context({'wubook_action': False}).create( + map_record.values(for_create=True)) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message']) + + return count + + @api.model + def fetch_rooms_values(self, dfrom, dto, rooms=False, + set_max_avail=False): + # Sanitize Dates + now_dt = date_utils.now() + dfrom_dt = date_utils.get_datetime(dfrom) + dto_dt = date_utils.get_datetime(dto) + if dto_dt < now_dt: + return True + if dfrom_dt < now_dt: + dfrom_dt = now_dt + if dfrom_dt > dto_dt: + dfrom_dt, dto_dt = dto_dt, dfrom_dt + + try: + results = self.backend_adapter.fetch_rooms_values( + dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + rooms) + self._generate_room_values(dfrom, dto, results, + set_max_avail=set_max_avail) + except ChannelConnectorError as err: + self.create_issue('room', _("Can't fetch rooms values from WuBook"), + err.data['message'], dfrom=dfrom, dto=dto) + return False + return True + + @api.model + def _map_room_values_availability(self, day_vals, set_max_avail): + channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability'] + room_avail_mapper = self.component(usage='import.mapper', + model_name='channel.hotel.room.type.availability') + map_record = room_avail_mapper.map_record(day_vals) + map_record.update(channel_pushed=True) + if set_max_avail: + map_record.update(max_avail=day_vals.get('avail', 0)) + + channel_room_type_avail = channel_room_type_avail_obj.search([ + ('room_type_id', '=', day_vals['room_type_id']), + ('date', '=', day_vals['date']) + ], limit=1) + if channel_room_type_avail: + channel_room_type_avail.with_context({ + 'wubook_action': False, + }).write(map_record.values()) + else: + channel_room_type_avail_obj.with_context({ + 'wubook_action': False, + 'mail_create_nosubscribe': True, + }).create(map_record.values(for_create=True)) + + @api.model + def _map_room_values_restrictions(self, day_vals): + channel_room_type_restr_item_obj = self.env['channel.hotel.room.type.restriction.item'] + room_restriction_mapper = self.component( + usage='import.mapper', + model_name='channel.hotel.room.type.restriction.item') + map_record = room_restriction_mapper.map_record(day_vals) + map_record.update(channel_pushed=True) + + room_type_restr = channel_room_type_restr_item_obj.search([ + ('room_type_id', '=', day_vals['room_type_id']), + ('applied_on', '=', '0_room_type'), + ('date', '=', day_vals['date']), + ('restriction_id', '=', day_vals['restriction_plan_id']), + ]) + if room_type_restr: + room_type_restr.with_context({ + 'wubook_action': False, + }).write(map_record.values()) + else: + channel_room_type_restr_item_obj.with_context({ + 'wubook_action': False, + }).create(map_record.values(for_create=True)) + + @api.model + def _generate_room_values(self, dfrom, dto, values, set_max_avail=False): + channel_room_type_restr_obj = self.env['channel.hotel.room.type.restriction'] + channel_hotel_room_type_obj = self.env['channel.hotel.room.type'] + def_restr_plan = channel_room_type_restr_obj.search([('channel_plan_id', '=', '0')]) + _logger.info("==== ROOM VALUES (%s -- %s)", dfrom, dto) + _logger.info(values) + for k_rid, v_rid in values.iteritems(): + room_type = channel_hotel_room_type_obj.search([ + ('channel_plan_id', '=', k_rid) + ], limit=1) + if room_type: + date_dt = date_utils.get_datetime( + dfrom, + dtformat=DEFAULT_WUBOOK_DATE_FORMAT) + for day_vals in v_rid: + date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + day_vals.update({ + 'room_type_id': room_type.odoo_id.id, + 'date': date_str, + }) + self._map_room_values_availability(day_vals, set_max_avail) + if def_restr_plan: + day_vals.update({ + 'restriction_plan_id': def_restr_plan.odoo_id.id + }) + self._map_room_values_restrictions(day_vals) + date_dt = date_dt + timedelta(days=1) + return True + +class HotelRoomTypeImportMapper(Component): + _name = 'channel.hotel.room.type.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.hotel.room.type' + + direct = [ + ('id', 'channel_room_id'), + ('shortname', 'channel_short_code'), + ('occupancy', 'ota_capacity'), + ('price', 'list_price'), + ('name', 'name'), + ] + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} 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 257ab04fc..06e54858b 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer 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 0f78f6ae2..1235db837 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -38,7 +38,7 @@ class ChannelHotelRoomTypeAvailability(models.Model): for record in self: if record.channel_max_avail > record.odoo_id.room_type_id.total_rooms_count: raise ValidationError(_("max avail for channel can't be high \ - than toal rooms \ + than total rooms \ count: %d") % record.odoo_id.room_type_id.total_rooms_count) @job(default_channel='root.channel') diff --git a/hotel_channel_connector/models/hotel_room_type_availability/importer.py b/hotel_channel_connector/models/hotel_room_type_availability/importer.py new file mode 100644 index 000000000..4970a308b --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_availability/importer.py @@ -0,0 +1,37 @@ +# 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.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, _ +_logger = logging.getLogger(__name__) + + +class HotelRoomTypeAvailabilityImporter(Component): + _name = 'channel.hotel.room.type.availability.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.hotel.room.type.availability'] + _usage = 'hotel.room.type.availability.importer' + + +class HotelRoomTypeAvailabilityImportMapper(Component): + _name = 'channel.hotel.room.type.availability.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.hotel.room.type.availability' + + direct = [ + ('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} 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 257ab04fc..06e54858b 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py new file mode 100644 index 000000000..33b15b3a4 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -0,0 +1,37 @@ +# 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.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, _ +_logger = logging.getLogger(__name__) + + +class HotelRoomTypeRestrictionImporter(Component): + _name = 'channel.hotel.room.type.restriction.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.hotel.room.type.restriction'] + _usage = 'hotel.room.type.restriction.importer' + + +class HotelRoomTypeRestrictionImportMapper(Component): + _name = 'channel.hotel.room.type.restriction.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.hotel.room.type.restriction' + + direct = [ + ('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} 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 new file mode 100644 index 000000000..06e54858b --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import common +from . import importer 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 new file mode 100644 index 000000000..2f79bfc38 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -0,0 +1,48 @@ +# 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 ChannelHotelRoomTypeRestrictionItem(models.Model): + _name = 'channel.hotel.room.type.restriction.item' + _inherit = 'channel.binding' + _inherits = {'hotel.room.type.restriction.item': 'odoo_id'} + _description = 'Channel Hotel Room Type Restriction Item' + + odoo_id = fields.Many2one(comodel_name='hotel.room.type.restriction.item', + string='Hotel Virtual Room Restriction', + required=True, + ondelete='cascade') + 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 + +class HotelRoomTypeRestrictionItem(models.Model): + _inherit = 'hotel.room.type.restriction.item' + + channel_bind_ids = fields.One2many( + comodel_name='channel.hotel.room.type.restriction.item', + inverse_name='odoo_id', + string='Hotel Channel Connector Bindings') + +class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'channel.binding.hotel.room.type.restriction.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) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + record.update_channel_pushed(False) 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 new file mode 100644 index 000000000..a50a8868d --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/importer.py @@ -0,0 +1,44 @@ +# 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.connector.components.mapper import mapping, only_create +from odoo.addons.hotel import date_utils +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + + +class HotelRoomTypeRestrictionImporter(Component): + _name = 'channel.hotel.room.type.restriction.item.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.hotel.room.type.restriction.item'] + _usage = 'hotel.room.type.restriction.item.importer' + +class HotelRoomTypeRestrictionItemImportMapper(Component): + _name = 'channel.hotel.room.type.restriction.item.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.hotel.room.type.restriction.item' + + direct = [ + ('min_stay', 'min_stay'), + ('min_stay_arrival', 'min_stay_arrival'), + ('max_stay', 'max_stay'), + ('max_stay_arrival', 'max_stay_arrival'), + ('closed', 'closed'), + ('closed_departure', 'closed_departure'), + ('closed_arrival', 'closed_arrival'), + ('room_type_id', 'room_type_id'), + ('date', 'date'), + ] + + @only_create + @mapping + def applied_on(self, record): + return {'applied_on': '0_room_type'} + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/models/inherited_res_partner.py b/hotel_channel_connector/models/inherited_res_partner.py index 483a7ee05..f71988ca9 100644 --- a/hotel_channel_connector/models/inherited_res_partner.py +++ b/hotel_channel_connector/models/inherited_res_partner.py @@ -1,7 +1,7 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api +from odoo import models, fields, api class ResPartner(models.Model): diff --git a/hotel_channel_connector/models/inherited_reservation_restriction_item.py b/hotel_channel_connector/models/inherited_reservation_restriction_item.py deleted file mode 100644 index 440bdd63b..000000000 --- a/hotel_channel_connector/models/inherited_reservation_restriction_item.py +++ /dev/null @@ -1,29 +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 ReservationRestrictionItem(models.Model): - _inherit = 'hotel.room.type.restriction.item' - - channel_pushed = fields.Boolean("Channel Pushed", default=False, readonly=True, - old_name='wpushed') - - @api.onchange('date_start') - def _onchange_date_start(self): - self.date_end = self.date_start - - @api.model - def create(self, vals): - if vals.get('date_start'): - vals['date_end'] = vals.get('date_start') - return super(ReservationRestrictionItem, self).create(vals) - - @api.multi - def write(self, vals): - if vals.get('date_start'): - vals['date_end'] = vals.get('date_start') - if self._context.get('channel_action', True): - vals.update({'channel_pushed': False}) - return super(ReservationRestrictionItem, self).write(vals) diff --git a/hotel_channel_connector/models/res_config.py b/hotel_channel_connector/models/res_config.py index 22da5eed9..25de1266d 100644 --- a/hotel_channel_connector/models/res_config.py +++ b/hotel_channel_connector/models/res_config.py @@ -1,196 +1,32 @@ -# Copyright 2018 Alexandre Díaz +# Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import os -import binascii -import logging -from datetime import datetime, timedelta -from odoo import models, fields, api, _ -from odoo.tools import DEFAULT_SERVER_DATE_FORMAT -_logger = logging.getLogger(__name__) +from openerp import models, fields, api -class HotelChannelConnectorConfiguration(models.TransientModel): +class HotelConfiguration(models.TransientModel): _inherit = 'res.config.settings' - channel_push_security_token = fields.Char('WuBook Push Notification Security Token') + default_channel_connector = fields.Many2one( + 'channel.backend', + 'Default Channel Connector Backend') @api.multi def set_values(self): - super(HotelChannelConnectorConfiguration, self).set_values() + super(HotelConfiguration, self).set_values() self.env['ir.default'].sudo().set( - 'res.config.settings', 'channel_push_security_token', - self.channel_push_security_token) + 'res.config.settings', 'default_channel_connector', + self.default_channel_connector.id) @api.model def get_values(self): - res = super(HotelChannelConnectorConfiguration, self).get_values() + res = super(HotelConfiguration, self).get_values() # ONLY FOR v11. DO NOT FORWARD-PORT - channel_push_security_token = self.env['ir.default'].sudo().get( - 'res.config.settings', 'channel_push_security_token') + default_channel_connector = self.env['ir.default'].sudo().get( + 'res.config.settings', 'default_channel_connector') + res.update( - channel_push_security_token=channel_push_security_token, + default_channel_connector=default_channel_connector, ) return res - - # Dangerus method: Usefull for cloned instances with new wubook account - @api.multi - def resync(self): - self.ensure_one() - - now_utc_dt = fields.Date.now() - now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) - - # Reset Issues - issue_ids = self.env['wubook.issue'].search([]) - issue_ids.write({ - 'to_read': False - }) - - # Push Virtual Rooms - wubook_obj = self.env['wubook'].with_context({ - 'init_connection': False - }) - if wubook_obj.init_connection(): - ir_seq_obj = self.env['ir.sequence'] - room_types = self.env['hotel.room.type'].search([]) - for room_type in room_types: - shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4] - channel_room_id = wubook_obj.create_room( - shortcode, - room_type.name, - room_type.wcapacity, - room_type.list_price, - room_type.total_rooms_count - ) - if channel_room_id: - room_type.with_context(wubook_action=False).write({ - 'channel_room_id': channel_room_id, - 'wscode': shortcode, - }) - else: - room_type.with_context(wubook_action=False).write({ - 'channel_room_id': '', - 'wscode': '', - }) - # Create Restrictions - room_type_rest_obj = self.env['hotel.room.type.restriction'] - restriction_ids = room_type_rest_obj.search([]) - for restriction in restriction_ids: - if restriction.wpid != '0': - channel_plan_id = wubook_obj.create_rplan(restriction.name) - restriction.write({ - 'channel_plan_id': channel_plan_id or '' - }) - # Create Pricelist - pricelist_ids = self.env['product.pricelist'].search([]) - for pricelist in pricelist_ids: - channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.is_daily_plan) - pricelist.write({ - 'channel_plan_id': channel_plan_id or '' - }) - wubook_obj.close_connection() - - # Reset Folios - folio_ids = self.env['hotel.folio'].search([]) - folio_ids.with_context(wubook_action=False).write({ - 'wseed': '', - }) - - # Reset Reservations - reservation_ids = self.env['hotel.reservation'].search([ - ('channel_reservation_id', '!=', ''), - ('channel_reservation_id', '!=', False) - ]) - reservation_ids.with_context(wubook_action=False).write({ - 'channel_reservation_id': '', - 'ota_id': False, - 'ota_reservation_id': '', - 'is_from_ota': False, - 'wstatus': 0 - }) - - # Get Parity Models - pricelist_id = int(self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_pricelist_id')) - restriction_id = int(self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_restrictions_id')) - - room_type_restr_it_obj = self.env['hotel.room.type.restriction.item'] - # Secure Wubook Input - restriction_item_ids = room_type_restr_it_obj.search([ - ('applied_on', '=', '0_room_type'), - ('date_start', '<', now_utc_str), - ]) - if any(restriction_item_ids): - restriction_item_ids.with_context(wubook_action=False).write({ - 'wpushed': True - }) - # Put to push restrictions - restriction_item_ids = room_type_restr_it_obj.search([ - ('restriction_id', '=', restriction_id), - ('applied_on', '=', '0_room_type'), - ('wpushed', '=', True), - ('date_start', '>=', now_utc_str), - ]) - if any(restriction_item_ids): - restriction_item_ids.with_context(wubook_action=False).write({ - 'wpushed': False - }) - - # Secure Wubook Input - pricelist_item_ids = self.env['product.pricelist.item'].search([ - ('applied_on', '=', '1_product'), - ('compute_price', '=', 'fixed'), - ('date_start', '<', now_utc_str), - ]) - if any(pricelist_item_ids): - pricelist_item_ids.with_context(wubook_action=False).write({ - 'wpushed': True - }) - # Put to push pricelists - pricelist_item_ids = self.env['product.pricelist.item'].search([ - ('pricelist_id', '=', pricelist_id), - ('applied_on', '=', '1_product'), - ('compute_price', '=', 'fixed'), - ('wpushed', '=', True), - ('date_start', '>=', now_utc_str), - ]) - if any(pricelist_item_ids): - pricelist_item_ids.with_context(wubook_action=False).write({ - 'wpushed': False - }) - - # Secure Wubook Input - availabity_ids = self.env['hotel.room.type.availability'].search([ - ('date', '<', now_utc_str), - ]) - if any(availabity_ids): - availabity_ids.with_context(wubook_action=False).write({ - 'wpushed': True - }) - # Put to push availability - availabity_ids = self.env['hotel.room.type.availability'].search([ - ('wpushed', '=', True), - ('date', '>=', now_utc_str), - ]) - if any(availabity_ids): - availabity_ids.with_context(wubook_action=False).write({ - 'wpushed': False - }) - - # Generate Security Token - self.env['ir.default'].sudo().set( - 'wubook.config.settings', - 'wubook_push_security_token', - binascii.hexlify(os.urandom(16)).decode()) - self.env.cr.commit() # FIXME: Need do this - - # Push Changes - if wubook_obj.init_connection(): - wubook_obj.push_activation() - wubook_obj.import_channels_info() - wubook_obj.push_changes() - wubook_obj.close_connection() diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 3724afa0a..0728b2bf1 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -5,12 +5,12 @@ channel.backend.form channel.backend - + - + string="Synchronize Push URL's"/> @@ -21,12 +21,19 @@ - + + + + + + @@ -59,6 +66,24 @@ string="Import in background"/> + + + + + + + + + + + + @@ -78,7 +103,7 @@ - Channel Backends + Hotel Channel Backends channel.backend form tree,form diff --git a/hotel_channel_connector/views/channel_connector_menu.xml b/hotel_channel_connector/views/channel_connector_menu.xml deleted file mode 100644 index 3ffd1d7c9..000000000 --- a/hotel_channel_connector/views/channel_connector_menu.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/hotel_channel_connector/views/channel_hotel_room_type_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_views.xml index 7ccbbddb1..846964b59 100644 --- a/hotel_channel_connector/views/channel_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/channel_hotel_room_type_views.xml @@ -6,6 +6,9 @@ channel.hotel.room.type + + + diff --git a/hotel_channel_connector/views/channel_ota_info_views.xml b/hotel_channel_connector/views/channel_ota_info_views.xml new file mode 100644 index 000000000..8dcc4dff8 --- /dev/null +++ b/hotel_channel_connector/views/channel_ota_info_views.xml @@ -0,0 +1,40 @@ + + + + + + channel.ota.info.form + channel.ota.info + + + + + + + + + + + + + + + + channel.ota.info.tree + channel.ota.info + + + + + + + + + + Hotel Channel Connector OTA's Info + channel.ota.info + form + tree,form + + + 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 4638222b6..5732014df 100644 --- a/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml +++ b/hotel_channel_connector/views/hotel_channel_connector_issue_views.xml @@ -69,4 +69,13 @@ + + Hotel Channel Connector Issues + hotel.channel.connector.issue + form + tree,form + + {"search_default_to_read":True} + + diff --git a/hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml b/hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml deleted file mode 100644 index 07f559d22..000000000 --- a/hotel_channel_connector/views/hotel_channel_connector_ota_info_views.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - hotel.channel.connector.ota.info.form - hotel.channel.connector.ota.info - - - - - - - - - - - - - - - - hotel.channel.connector.ota.info.tree - hotel.channel.connector.ota.info - - - - - - - - - diff --git a/hotel_channel_connector/views/inherited_reservation_restriction_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml similarity index 75% rename from hotel_channel_connector/views/inherited_reservation_restriction_views.xml rename to hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml index ca93004bf..86ef97d11 100644 --- a/hotel_channel_connector/views/inherited_reservation_restriction_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_restriction_views.xml @@ -1,9 +1,9 @@ - + hotel.room.type.restriction - + diff --git a/hotel_channel_connector/views/inherited_hotel_room_type_views.xml b/hotel_channel_connector/views/inherited_hotel_room_type_views.xml index 12a51319c..e75a7a7c0 100644 --- a/hotel_channel_connector/views/inherited_hotel_room_type_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_room_type_views.xml @@ -9,9 +9,10 @@ - + + - + diff --git a/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml b/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml deleted file mode 100644 index 4cb391c94..000000000 --- a/hotel_channel_connector/views/inherited_reservation_restriction_item_views.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - hotel.room.type.restriction.item - - - - True - - - - - diff --git a/hotel_channel_connector/wizard/__init__.py b/hotel_channel_connector/wizard/__init__.py index 689ca52b8..937912893 100644 --- a/hotel_channel_connector/wizard/__init__.py +++ b/hotel_channel_connector/wizard/__init__.py @@ -19,7 +19,6 @@ # along with this program. If not, see . # ############################################################################## -from . import wubook_installer from . import wubook_import_plan_prices from . import wubook_import_plan_restrictions from . import wubook_import_availability diff --git a/hotel_channel_connector/wizard/wubook_installer.py b/hotel_channel_connector/wizard/wubook_installer.py deleted file mode 100644 index ceba052b4..000000000 --- a/hotel_channel_connector/wizard/wubook_installer.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# Alexandre Díaz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -############################################################################## -import os -import binascii -from openerp import models, fields, api, _ -from openerp.exceptions import ValidationError -from ..components.backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT -from odoo.addons.hotel import date_utils - - -class WuBookInstaller(models.TransientModel): - _name = 'wubook.installer' - _inherit = 'res.config.installer' - - wubook_user = fields.Char('User', required=True) - wubook_passwd = fields.Char('Password', required=True) - wubook_lcode = fields.Char('LCode', required=True) - wubook_server = fields.Char(string='Server', - default='https://wired.wubook.net/xrws/', - required=True) - wubook_pkey = fields.Char('PKey', required=True) - activate_push = fields.Boolean('Active Push Notifications', default=True) - - @api.multi - def execute(self): - super(WuBookInstaller, self).execute() - return self.execute_simple() - - @api.multi - def execute_simple(self): - activate_push = True - for rec in self: - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_user', - rec.wubook_user) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_passwd', - rec.wubook_passwd) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_lcode', - rec.wubook_lcode) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_server', - rec.wubook_server) - self.env['ir.default'].sudo().set('wubook.config.settings', - 'wubook_pkey', - rec.wubook_pkey) - activate_push = rec.activate_push - self.env['ir.default'].sudo().set( - 'wubook.config.settings', - 'wubook_push_security_token', - binascii.hexlify(os.urandom(16)).decode()) - self.env.cr.commit() # FIXME: Need do this - - # Create Wubook Base Restrictions - restr_obj = self.env['hotel.room.type.restriction'].with_context({ - 'wubook_action': False - }) - base_rest = restr_obj.search([('wpid', '=', '0')], limit=1) - if not base_rest: - nrest = restr_obj.create({ - 'name': 'Base WuBook Restrictions', - 'wpid': '0', - }) - if not nrest: - raise ValidationError(_("Can't create base wubook restrictions")) - - # Initialize WuBook - wres = self.env['wubook'].initialize(activate_push) - if not wres: - raise ValidationError("Can't finish installation!") - - # Open Next Step - v_id = 'hotel_wubook_proto.view_wubook_configuration_installer_parity' - return { - 'name': _("Configure Hotel Parity"), - 'type': 'ir.actions.act_window', - 'res_model': 'wubook.installer.parity', - 'view_id': self.env.ref(v_id).id, - 'view_type': 'form', - 'view_mode': 'form', - 'target': 'new' - } - - -class WuBookInstallerParity(models.TransientModel): - _name = 'wubook.installer.parity' - _inherit = 'res.config.installer' - - parity_pricelist_id = fields.Many2one('product.pricelist', - 'Product Pricelist') - parity_restrictions_id = fields.Many2one('hotel.room.type.restriction', - 'Restrictions') - import_data = fields.Boolean('Import Data From WuBook', default=False) - date_start = fields.Date('Date Start') - date_end = fields.Date('Date End') - - @api.multi - def execute(self): - self.execute_simple() - return super(WuBookInstallerParity, self).execute() - - @api.multi - def execute_simple(self): - wubookObj = self.env['wubook'] - irValuesObj = self.env['ir.values'] - for rec in self: - irValuesObj.sudo().set_default('res.config.settings', - 'parity_pricelist_id', - rec.parity_pricelist_id.id) - irValuesObj.sudo().set_default('res.config.settings', - 'parity_restrictions_id', - rec.parity_restrictions_id.id) - import_data = rec.import_data - if rec.import_data: - date_start_dt = date_utils.get_datetime(rec.date_start) - date_end_dt = date_utils.get_datetime(rec.date_end) - # Availability - wresAvail = wubookObj.fetch_rooms_values( - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT)) - # Pricelist - wresPrices = wubookObj.fetch_plan_prices( - rec.parity_pricelist_id.wpid, - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT)) - # Restrictions - wresRestr = wubookObj.fetch_rplan_restrictions( - date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - rec.parity_restrictions_id.wpid) - - if not wresAvail or not wresPrices or not wresRestr: - raise ValidationError(_("Errors importing data from WuBook")) - - # Reservations - wubookObj.fetch_new_bookings() diff --git a/hotel_channel_connector/wizard/wubook_installer.xml b/hotel_channel_connector/wizard/wubook_installer.xml deleted file mode 100644 index 57dbac21f..000000000 --- a/hotel_channel_connector/wizard/wubook_installer.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - wubook.installer.form - wubook.installer - - - - WuBook Configuration - - - - - - - WuBook API Configuration. This wizard will activate push request and synchronize rooms & reservations with Odoo. - - - - - - - - - - - - - - - - - - - - - wubook.installer.parity.form - wubook.installer.parity - - - - WuBook Configuration Parity - - - - - - - These models are used as masters - - - - - - - - - - - - - - - - - - - - - - Configure WuBook Data - ir.actions.act_window - wubook.installer - - form - form - new - - - - -
- WuBook API Configuration. This wizard will activate push request and synchronize rooms & reservations with Odoo. -
- These models are used as masters -