From 05586eafcf52ddf9346107d6693b6d6c8903850e Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Wed, 8 Aug 2018 02:32:41 +0200 Subject: [PATCH] [WIP] Hotel Channel Connector --- .../models/hotel_calendar_management.py | 14 +- .../models/inherited_hotel_reservation.py | 6 +- .../models/inherited_hotel_virtual_room.py | 2 +- hotel_calendar/models/inherited_ir_default.py | 2 +- .../models/inherited_product_pricelist.py | 2 +- .../inherited_product_pricelist_item.py | 6 +- .../models/virtual_room_pricelist_cached.py | 2 +- .../js/views/calendar/hotel_calendar_model.js | 2 +- .../inherited_hotel_virtual_room_views.xml | 2 +- hotel_calendar/wizard/wizard_reservation.py | 6 +- hotel_calendar_channel_connector/__init__.py | 23 +- .../models/__init__.py | 25 +- .../models/inherited_bus_hotel_calendar.py | 23 +- .../inherited_hotel_calendar_management.py | 23 +- ...inherited_hotel_channel_connector_issue.py | 18 + .../models/inherited_hotel_reservation.py | 25 +- .../src/js/views/hotel_calendar_model.js | 2 +- .../tests/__init__.py | 24 +- .../tests/test_folio.py | 24 +- hotel_channel_connector/__init__.py | 26 +- hotel_channel_connector/__manifest__.py | 33 +- .../components/__init__.py | 7 + .../components/backend_adapter.py | 552 +++++++++++ hotel_channel_connector/components/binder.py | 8 + hotel_channel_connector/components/core.py | 19 + .../components/exporter.py | 169 ++++ .../components/importer.py | 871 ++++++++++++++++++ .../controllers/__init__.py | 23 +- hotel_channel_connector/controllers/main.py | 23 +- hotel_channel_connector/data/menus.xml | 6 +- hotel_channel_connector/data/sequences.xml | 10 +- hotel_channel_connector/models/__init__.py | 25 +- .../models/hotel_channel_connector_issue.py | 24 +- .../models/inherited_hotel_folio.py | 23 +- .../models/inherited_hotel_reservation.py | 23 +- .../models/inherited_hotel_virtual_room.py | 37 +- .../models/inherited_product_pricelist.py | 27 +- .../inherited_product_pricelist_item.py | 41 +- .../models/inherited_res_partner.py | 22 +- .../inherited_reservation_restriction.py | 25 +- .../inherited_reservation_restriction_item.py | 23 +- .../inherited_virtual_room_availability.py | 92 +- hotel_channel_connector/models/res_config.py | 27 +- .../models/wubook_channel_info.py | 23 +- .../src/js/wubook_listview_import_buttons.js | 47 +- hotel_channel_connector/tests/common.py | 10 +- .../tests/test_hotel_virtual_room_model.py | 2 +- .../inherited_hotel_virtual_room_views.xml | 2 +- 48 files changed, 1820 insertions(+), 631 deletions(-) create mode 100644 hotel_calendar_channel_connector/models/inherited_hotel_channel_connector_issue.py create mode 100644 hotel_channel_connector/components/__init__.py create mode 100644 hotel_channel_connector/components/backend_adapter.py create mode 100644 hotel_channel_connector/components/binder.py create mode 100644 hotel_channel_connector/components/core.py create mode 100644 hotel_channel_connector/components/exporter.py create mode 100644 hotel_channel_connector/components/importer.py diff --git a/hotel_calendar/models/hotel_calendar_management.py b/hotel_calendar/models/hotel_calendar_management.py index 4727c428e..33d9c2204 100644 --- a/hotel_calendar/models/hotel_calendar_management.py +++ b/hotel_calendar/models/hotel_calendar_management.py @@ -36,7 +36,7 @@ class HotelCalendarManagement(models.TransientModel): @api.model def _get_availability_values(self, avail, vroom): - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] cavail = len(vroom_obj.check_availability_virtual_room( avail['date'], avail['date'], virtual_room_id=vroom.id)) ravail = min(cavail, vroom.total_rooms_count, int(avail['avail'])) @@ -49,7 +49,7 @@ class HotelCalendarManagement(models.TransientModel): @api.multi def save_changes(self, pricelist_id, restriction_id, pricelist, restrictions, availability): - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] product_pricelist_item_obj = self.env['product.pricelist.item'] vroom_rest_item_obj = self.env['hotel.virtual.room.restriction.item'] vroom_avail_obj = self.env['hotel.virtual.room.availability'] @@ -140,7 +140,7 @@ class HotelCalendarManagement(models.TransientModel): @api.model def _hcalendar_pricelist_json_data(self, prices): json_data = {} - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] for rec in prices: virtual_room_id = vroom_obj.search([ ('product_id.product_tmpl_id', '=', rec.product_tmpl_id.id) @@ -178,7 +178,7 @@ class HotelCalendarManagement(models.TransientModel): def _hcalendar_availability_json_data(self, dfrom, dto): date_start = date_utils.get_datetime(dfrom, hours=False) date_diff = date_utils.date_diff(dfrom, dto, hours=False) + 1 - vrooms = self.env['hotel.virtual.room'].search([]) + vrooms = self.env['hotel.room.type'].search([]) json_data = {} for vroom in vrooms: @@ -239,10 +239,10 @@ class HotelCalendarManagement(models.TransientModel): @api.model def _hcalendar_get_count_reservations_json_data(self, dfrom, dto): - vrooms = self.env['hotel.virtual.room'].search([]) + vrooms = self.env['hotel.room.type'].search([]) date_start = date_utils.get_datetime(dfrom, hours=False) date_diff = date_utils.date_diff(dfrom, dto, hours=False) + 1 - hotel_vroom_obj = self.env['hotel.virtual.room'] + hotel_vroom_obj = self.env['hotel.room.type'] vrooms = hotel_vroom_obj.search([]) json_data = {} @@ -308,7 +308,7 @@ class HotelCalendarManagement(models.TransientModel): }) if withRooms: - room_ids = self.env['hotel.virtual.room'].search( + room_ids = self.env['hotel.room.type'].search( [], order='hcal_sequence ASC') json_rooms = self._hcalendar_room_json_data(room_ids) diff --git a/hotel_calendar/models/inherited_hotel_reservation.py b/hotel_calendar/models/inherited_hotel_reservation.py index 8e654df19..9dcd7bd47 100644 --- a/hotel_calendar/models/inherited_hotel_reservation.py +++ b/hotel_calendar/models/inherited_hotel_reservation.py @@ -65,7 +65,7 @@ class HotelReservation(models.Model): pricelist_id = int(pricelist_id) json_rooms = [] room_type_obj = self.env['hotel.room.type'] - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] for room in rooms: room_type = room_type_obj.search([ ('cat_id', '=', room.categ_id.id) @@ -138,7 +138,7 @@ class HotelReservation(models.Model): date_diff = date_utils.date_diff(date_start, dto, hours=False) + 1 # Get Prices json_rooms_prices = {pricelist_id: []} - vrooms = self.env['hotel.virtual.room'].search( + vrooms = self.env['hotel.room.type'].search( [], order='hcal_sequence ASC') vroom_pr_cached_obj = self.env['virtual.room.pricelist.cached'] @@ -178,7 +178,7 @@ class HotelReservation(models.Model): date_diff = date_utils.date_diff(dfrom, dto, hours=False) + 1 # Get Prices json_rooms_rests = {} - vrooms = self.env['hotel.virtual.room'].search( + vrooms = self.env['hotel.room.type'].search( [], order='hcal_sequence ASC') vroom_rest_obj = self.env['hotel.virtual.room.restriction.item'] diff --git a/hotel_calendar/models/inherited_hotel_virtual_room.py b/hotel_calendar/models/inherited_hotel_virtual_room.py index 59508b000..815d6225d 100644 --- a/hotel_calendar/models/inherited_hotel_virtual_room.py +++ b/hotel_calendar/models/inherited_hotel_virtual_room.py @@ -4,7 +4,7 @@ from odoo import models, fields, api class HotelVirtualRoom(models.Model): - _inherit = 'hotel.virtual.room' + _inherit = 'hotel.room.type' hcal_sequence = fields.Integer('Calendar Sequence', default=0) diff --git a/hotel_calendar/models/inherited_ir_default.py b/hotel_calendar/models/inherited_ir_default.py index 9ae372b97..f254607a3 100644 --- a/hotel_calendar/models/inherited_ir_default.py +++ b/hotel_calendar/models/inherited_ir_default.py @@ -18,7 +18,7 @@ class IrDefault(models.Model): pricelist_items = self.env['product.pricelist.item'].search([ ('pricelist_id', '=', pricelist_id) ]) - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] vroom_pr_cached_obj = self.env['virtual.room.pricelist.cached'] for pitem in pricelist_items: date_start = pitem.date_start diff --git a/hotel_calendar/models/inherited_product_pricelist.py b/hotel_calendar/models/inherited_product_pricelist.py index 2a321cdb7..651d4d8cf 100644 --- a/hotel_calendar/models/inherited_product_pricelist.py +++ b/hotel_calendar/models/inherited_product_pricelist.py @@ -8,7 +8,7 @@ class ProductPricelist(models.Model): @api.multi def update_price(self, virtual_room_id, date, price): - vroom = self.env['hotel.virtual.room'].browse(virtual_room_id) + vroom = self.env['hotel.room.type'].browse(virtual_room_id) pritem_obj = self.env['product.pricelist.item'] for record in self: plitem = pritem_obj.search([ diff --git a/hotel_calendar/models/inherited_product_pricelist_item.py b/hotel_calendar/models/inherited_product_pricelist_item.py index 81f567b7d..309c6d55b 100644 --- a/hotel_calendar/models/inherited_product_pricelist_item.py +++ b/hotel_calendar/models/inherited_product_pricelist_item.py @@ -16,7 +16,7 @@ class ProductPricelistItem(models.Model): pricelist_id = res.pricelist_id.id product_tmpl_id = res.product_tmpl_id.id date_start = res.date_start - vroom = self.env['hotel.virtual.room'].search([ + vroom = self.env['hotel.room.type'].search([ ('product_id.product_tmpl_id', '=', product_tmpl_id) ], limit=1) if pricelist_id == pricelist_parity_id and vroom: @@ -59,7 +59,7 @@ class ProductPricelistItem(models.Model): vroom_pr_cached_obj = self.env['virtual.room.pricelist.cached'] bus_calendar_obj = self.env['bus.hotel.calendar'] - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] if vals.get('fixed_price'): for record in self: pricelist_id = vals.get('pricelist_id') or \ @@ -113,7 +113,7 @@ class ProductPricelistItem(models.Model): for record in self: if record.pricelist_id.id != pricelist_parity_id: continue - vroom = self.env['hotel.virtual.room'].search([ + vroom = self.env['hotel.room.type'].search([ ('product_id.product_tmpl_id', '=', record.product_tmpl_id.id) ], limit=1) unlink_vals.append({ diff --git a/hotel_calendar/models/virtual_room_pricelist_cached.py b/hotel_calendar/models/virtual_room_pricelist_cached.py index 5d1395fd7..5ec8fd32d 100644 --- a/hotel_calendar/models/virtual_room_pricelist_cached.py +++ b/hotel_calendar/models/virtual_room_pricelist_cached.py @@ -11,7 +11,7 @@ class VirtualRoomPricelistCached(models.Model): _name = 'virtual.room.pricelist.cached' - virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room', + virtual_room_id = fields.Many2one('hotel.room.type', 'Virtual Room', required=True, track_visibility='always') price = fields.Float('Price', default=0.0) date = fields.Date('Date', required=True, track_visibility='always') diff --git a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js index b1542c5e3..5f6297d73 100644 --- a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js +++ b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js @@ -82,7 +82,7 @@ return AbstractModel.extend({ }, get_vrooms: function() { return this._rpc({ - model: 'hotel.virtual.room', + model: 'hotel.room.type', method: 'search_read', args: [false, ['id','name']], context: Session.user_context, diff --git a/hotel_calendar/views/inherited_hotel_virtual_room_views.xml b/hotel_calendar/views/inherited_hotel_virtual_room_views.xml index 57505f6a0..2d12dc572 100644 --- a/hotel_calendar/views/inherited_hotel_virtual_room_views.xml +++ b/hotel_calendar/views/inherited_hotel_virtual_room_views.xml @@ -2,7 +2,7 @@ - hotel.virtual.room + hotel.room.type diff --git a/hotel_calendar/wizard/wizard_reservation.py b/hotel_calendar/wizard/wizard_reservation.py index 35c0ca159..08d791e00 100644 --- a/hotel_calendar/wizard/wizard_reservation.py +++ b/hotel_calendar/wizard/wizard_reservation.py @@ -226,7 +226,7 @@ class FolioWizard(models.TransientModel): checkout_dt = date_utils.get_datetime(self.checkout, stz=tz) # Reservation end day count as free day. Not check it checkout_dt -= timedelta(days=1) - virtual_room_ids = self.env['hotel.virtual.room'].search([]) + virtual_room_ids = self.env['hotel.room.type'].search([]) virtual_rooms = [] for virtual in virtual_room_ids: @@ -313,7 +313,7 @@ class VirtualRoomWizars(models.TransientModel): def _get_default_checkout(self): return self.folio_wizard_id.checkout - virtual_room_id = fields.Many2one('hotel.virtual.room', + virtual_room_id = fields.Many2one('hotel.room.type', string="Virtual Rooms") rooms_num = fields.Integer('Number of Rooms') max_rooms = fields.Integer('Max', compute="_compute_max") @@ -459,7 +459,7 @@ class ReservationWizard(models.TransientModel): help='Number of children there in guest list.') checkin = fields.Datetime('Check In', required=True) checkout = fields.Datetime('Check Out', required=True) - virtual_room_id = fields.Many2one('hotel.virtual.room', + virtual_room_id = fields.Many2one('hotel.room.type', string='Virtual Room Type', required=True) nights = fields.Integer('Nights', readonly=True) diff --git a/hotel_calendar_channel_connector/__init__.py b/hotel_calendar_channel_connector/__init__.py index 8f10572dd..572903d95 100644 --- a/hotel_calendar_channel_connector/__init__.py +++ b/hotel_calendar_channel_connector/__init__.py @@ -1,22 +1,3 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models diff --git a/hotel_calendar_channel_connector/models/__init__.py b/hotel_calendar_channel_connector/models/__init__.py index 32e496b97..4cb563145 100644 --- a/hotel_calendar_channel_connector/models/__init__.py +++ b/hotel_calendar_channel_connector/models/__init__.py @@ -1,25 +1,6 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import inherited_hotel_reservation from . import inherited_bus_hotel_calendar -from . import inherited_wubook_issue +from . import inherited_hotel_channel_connector_issue from . import inherited_hotel_calendar_management diff --git a/hotel_calendar_channel_connector/models/inherited_bus_hotel_calendar.py b/hotel_calendar_channel_connector/models/inherited_bus_hotel_calendar.py index 1897d5ca2..f3bc09eac 100644 --- a/hotel_calendar_channel_connector/models/inherited_bus_hotel_calendar.py +++ b/hotel_calendar_channel_connector/models/inherited_bus_hotel_calendar.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime from openerp.tools import DEFAULT_SERVER_DATE_FORMAT from openerp import models, api diff --git a/hotel_calendar_channel_connector/models/inherited_hotel_calendar_management.py b/hotel_calendar_channel_connector/models/inherited_hotel_calendar_management.py index 6b576872f..f20f26270 100644 --- a/hotel_calendar_channel_connector/models/inherited_hotel_calendar_management.py +++ b/hotel_calendar_channel_connector/models/inherited_hotel_calendar_management.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, api diff --git a/hotel_calendar_channel_connector/models/inherited_hotel_channel_connector_issue.py b/hotel_calendar_channel_connector/models/inherited_hotel_channel_connector_issue.py new file mode 100644 index 000000000..bbaa6d821 --- /dev/null +++ b/hotel_calendar_channel_connector/models/inherited_hotel_channel_connector_issue.py @@ -0,0 +1,18 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import models, fields, api, _ + + +class WuBookIssue(models.Model): + _inherit = 'hotel.channel.connector.issue' + + @api.model + def create(self, vals): + issue_id = super(WuBookIssue, self).create(vals) + self.env['bus.hotel.calendar'].send_issue_notification( + 'warn', + _("Oops! Issue Reported!!"), + issue_id.id, + issue_id.section, + issue_id.message) + return issue_id diff --git a/hotel_calendar_channel_connector/models/inherited_hotel_reservation.py b/hotel_calendar_channel_connector/models/inherited_hotel_reservation.py index 0ae6e46dd..94856adb4 100644 --- a/hotel_calendar_channel_connector/models/inherited_hotel_reservation.py +++ b/hotel_calendar_channel_connector/models/inherited_hotel_reservation.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging from datetime import datetime, timedelta from openerp import models, fields, api, _ @@ -118,5 +99,5 @@ class HotelReservation(models.Model): def confirm(self): for record in self: if record.to_assign == True: - record.write({'to_read': False, 'to_assign': False}) + record.write({'to_read': False, 'to_assign': False}) return super(HotelReservation, self).confirm() diff --git a/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_model.js b/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_model.js index b1542c5e3..5f6297d73 100644 --- a/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_model.js +++ b/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_model.js @@ -82,7 +82,7 @@ return AbstractModel.extend({ }, get_vrooms: function() { return this._rpc({ - model: 'hotel.virtual.room', + model: 'hotel.room.type', method: 'search_read', args: [false, ['id','name']], context: Session.user_context, diff --git a/hotel_calendar_channel_connector/tests/__init__.py b/hotel_calendar_channel_connector/tests/__init__.py index 327f7e426..5c2388c95 100644 --- a/hotel_calendar_channel_connector/tests/__init__.py +++ b/hotel_calendar_channel_connector/tests/__init__.py @@ -1,23 +1,3 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import test_folio diff --git a/hotel_calendar_channel_connector/tests/test_folio.py b/hotel_calendar_channel_connector/tests/test_folio.py index 718141d45..824d9a05f 100644 --- a/hotel_calendar_channel_connector/tests/test_folio.py +++ b/hotel_calendar_channel_connector/tests/test_folio.py @@ -1,25 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta from odoo.addons.hotel_calendar.tests.common import TestHotelCalendar from odoo.addons.hotel import date_utils diff --git a/hotel_channel_connector/__init__.py b/hotel_channel_connector/__init__.py index f33b38cae..88bc9d53e 100644 --- a/hotel_channel_connector/__init__.py +++ b/hotel_channel_connector/__init__.py @@ -1,25 +1,7 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import models, fields, api, _ from . import controllers from . import models -from . import wubook from . import wizard +from . import components diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index d686144f7..816b607e8 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -1,33 +1,14 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'Hotel WuBook Prototype', + 'name': 'Hotel Channel Connector', 'version': '1.0', - 'author': "Alexandre Díaz (Aloxa Solucións S.L.) ", + 'author': "Alexandre Díaz ", 'website': 'https://www.eiqui.com', - 'category': 'eiqui/hotel', - 'summary': "Hotel WuBook", - 'description': "Hotel WuBook Prototype", + 'category': 'hotel/connector', + 'summary': "Hotel Channel Connector Base", + 'description': "Hotel Channel Connector Base", 'depends': [ 'hotel', ], diff --git a/hotel_channel_connector/components/__init__.py b/hotel_channel_connector/components/__init__.py new file mode 100644 index 000000000..8c217199f --- /dev/null +++ b/hotel_channel_connector/components/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import backend_adapter +from . import binder +from . import core +from . import exporter +from . import importer diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py new file mode 100644 index 000000000..a644a071d --- /dev/null +++ b/hotel_channel_connector/components/backend_adapter.py @@ -0,0 +1,552 @@ +from odoo.addons.component.core import AbstractComponent +from odoo.addons.queue_job.exception import RetryableJobError +from odoo.addons.queue_job.exception import RetryableJobError +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) +from odoo.addons.payment.models.payment_acquirer import _partner_split_name +from odoo.addons.hotel import date_utils + +# GLOBAL VARS +DEFAULT_WUBOOK_DATE_FORMAT = "%d/%m/%Y" +DEFAULT_WUBOOK_TIME_FORMAT = "%H:%M" +DEFAULT_WUBOOK_DATETIME_FORMAT = "%s %s" % (DEFAULT_WUBOOK_DATE_FORMAT, + DEFAULT_WUBOOK_TIME_FORMAT) +WUBOOK_STATUS_CONFIRMED = 1 +WUBOOK_STATUS_WAITING = 2 +WUBOOK_STATUS_REFUSED = 3 +WUBOOK_STATUS_ACCEPTED = 4 +WUBOOK_STATUS_CANCELLED = 5 +WUBOOK_STATUS_CANCELLED_PENALTY = 6 + +WUBOOK_STATUS_GOOD = ( + WUBOOK_STATUS_CONFIRMED, + WUBOOK_STATUS_WAITING, + WUBOOK_STATUS_ACCEPTED, +) +WUBOOK_STATUS_BAD = ( + WUBOOK_STATUS_REFUSED, + WUBOOK_STATUS_CANCELLED, + WUBOOK_STATUS_CANCELLED_PENALTY, +) + +class WuBookLogin(object): + def __init__(self, address, user, passwd, lcode, pkey): + self.address = address + self.user = user + self.passwd = passwd + self.lcode = lcode + self.pkey = pkey + + def is_valid(self): + return self.address and self.user and self.passwd and self.lcode and self.pkey + +class WuBookServer(object): + def __init__(self, login_data): + self._server = None + self._token = None + self._login_data = login_data + + @property + def server(self): + if self._server is None and self._login_data.is_valid(): + try: + self._server = xmlrpclib.Server(self._login_data.address) + res, tok = self._server.acquire_token( + self._login_data.user, + self._login_data.passwd, + self._login_data.pkey) + if res == 0: + self._token = tok + else: + self._server = None + except Exception: + self._server = None + raise RetryableJobError("Can't connect with channel!") + return self._server + + @property + def session_token(self): + return self._token + + @property + def lcode(self): + return self._login_data.lcode + + def close(self): + self._server.release_token(self._token) + self._token = None + self._server = None + +class HotelChannelInterfaceAdapter(AbstractComponent): + _name = 'hotel.channel.interface.adapter' + _inherit = ['base.backend.adapter', 'base.hotel.channel.connector'] + _usage = 'backend.adapter' + + def create_room(self, shortcode, name, capacity, price, availability): + raise NotImplementedError + + def modify_room(self, channel_room_id, name, capacity, price, availability, scode): + raise NotImplementedError + + def delete_room(self, channel_room_id): + raise NotImplementedError + + def fetch_rooms(self, channel_room_id=0): + raise NotImplementedError + + def fetch_rooms_values(self, date_from, date_to, rooms=False): + raise NotImplementedError + + def update_availability(self, rooms_avail): + raise NotImplementedError + + def corporate_fetch(self): + raise NotImplementedError + + def create_reservation(self, channel_room_id, customer_name, email, city, + phone, address, country_code, checkin, checkout, + adults, children, notes=''): + raise NotImplementedError + + def cancel_reservation(self, channel_reservation_id, reason=""): + raise NotImplementedError + + def fetch_new_bookings(self): + raise NotImplementedError + + def fetch_booking(self, channel_reservation_id): + raise NotImplementedError + + def mark_bookings(self, channel_reservation_ids): + raise NotImplementedError + + def create_plan(self, name, daily=1): + raise NotImplementedError + + def delete_plan(self, channel_plan_id): + raise NotImplementedError + + def update_plan_name(self, channel_plan_id, new_name): + raise NotImplementedError + + def update_plan_prices(self, channel_plan_id, date_from, prices): + raise NotImplementedError + + def update_plan_periods(self, channel_plan_id, periods): + raise NotImplementedError + + def get_pricing_plans(self): + raise NotImplementedError + + def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms): + raise NotImplementedError + + def rplan_rplans(self): + raise NotImplementedError + + def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id): + raise NotImplementedError + + def update_rplan_values(self, channel_restriction_plan_id, date_from, values): + raise NotImplementedError + + def create_rplan(self, name, compact=False): + raise NotImplementedError + + def rename_rplan(self, channel_restriction_plan_id, new_name): + raise NotImplementedError + + def delete_rplan(self, channel_restriction_plan_id): + raise NotImplementedError + + def get_channels_info(self): + raise NotImplementedError + + @property + def _server(self): + try: + channel_server = getattr(self.work, 'hotel_channel_server') + except AttributeError: + raise AttributeError( + 'You must provide a hotel_channel_server attribute with a ' + 'WuBookServer instance to be able to use the ' + 'Backend Adapter.' + ) + return channel_server.server + + @property + def _session_info(self): + try: + channel_server = getattr(self.work, 'hotel_channel_server') + except AttributeError: + raise AttributeError( + 'You must provide a hotel_channel_server attribute with a ' + 'WuBookServer instance to be able to use the ' + 'Backend Adapter.' + ) + return (channel_server.session_token, channel_server.lcode) + +class WuBookAdapter(AbstractComponent): + _name = 'wubook.adapter' + _inherit = 'hotel.channel.interface.adapter' + + # === ROOMS + def create_room(self, shortcode, name, capacity, price, availability): + rcode, results = self._server.new_room( + self._session_info[0], + self._session_info[1], + 0, + name, + capacity, + price, + availability, + shortcode[:4], + 'nb' # TODO: Complete this part + # rtype=('name' in vals and vals['name'] and 3) or 1 + ) + if rcode != 0: + raise ValidationError(_("Can't create room in WuBook"), { + 'message': results, + }) + return results + + def modify_room(self, channel_room_id, name, capacity, price, availability, scode): + rcode, results = self._server.mod_room( + self._session_info[0], + self._session_info[1], + channel_room_id, + name, + capacity, + price, + availability, + scode, + 'nb' + # rtype=('name' in vals and vals['name'] and 3) or 1 + ) + if rcode != 0: + raise ValidationError(_("Can't modify room in WuBook"), { + 'message': results, + 'channel_id': channel_room_id, + }) + return results + + def delete_room(self, channel_room_id): + rcode, results = self._server.del_room( + self._session_info[0], + self._session_info[1], + channel_room_id) + if rcode != 0: + raise ValidationError(_("Can't delete room in WuBook"), { + 'message': results, + 'channel_id': channel_room_id, + }) + return results + + def fetch_rooms(self, channel_room_id=0): + rcode, results = self._server.fetch_rooms( + self._session_info[0], + self._session_info[1], + channel_room_id) + if rcode != 0: + raise ValidationError(_("Can't fetch room values from WuBook"), { + 'message': results, + 'channel_id': channel_room_id, + }) + return results + + def fetch_rooms_values(self, date_from, date_to, rooms=False): + rcode, results = self._server.fetch_rooms_values( + self._session_info[0], + self._session_info[1], + date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + rooms) + if rcode != 0: + raise ValidationError(_("Can't fetch rooms values from WuBook"), { + 'message': results, + }) + return results + + def update_availability(self, rooms_avail): + rcode, results = self._server.update_sparse_avail( + self._session_info[0], + self._session_info[1], + rooms_avail) + if rcode != 0: + raise ValidationError(_("Can't update rooms availability in WuBook"), { + 'message': results, + }) + return results + + 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"), { + 'message': results, + }) + return results + + # === RESERVATIONS + def create_reservation(self, channel_room_id, customer_name, email, city, + phone, address, country_code, checkin, checkout, + adults, children, notes=''): + customer_name = _partner_split_name(customer_name) + customer = { + 'lname': customer_name[0], + 'fname': customer_name[1], + 'email': email, + 'city': city, + 'phone': phone, + 'street': address, + 'country': country_code, + 'arrival_hour': date_utils.get_datetime(checkin).strftime("%H:%M"), + 'notes': notes + } + rcode, results = self._server.new_reservation( + self._session_info[0], + self._session_info[1], + date_utils.get_datetime(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + date_utils.get_datetime(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + {channel_room_id: [adults+children, 'nb']}, + customer, + adults+children) + if rcode != 0: + raise ValidationError(_("Can't create reservations in wubook"), { + 'message': results, + 'date_from': checkin, + 'date_to': checkout, + }) + return results + + def cancel_reservation(self, channel_reservation_id, reason=""): + rcode, results = self._server.cancel_reservation( + self._session_info[0], + self._session_info[1], + channel_reservation_id, + reason) + if rcode != 0: + raise ValidationError(_("Can't cancel reservation in WuBook"), { + 'message': results, + 'channel_reservation_id': channel_reservation_id, + }) + return results + + def fetch_new_bookings(self): + rcode, results = self._server.fetch_new_bookings( + self._session_info[0], + self._session_info[1], + 1, + 0) + if rcode != 0: + raise ValidationError(_("Can't process reservations from wubook"), { + 'message': results, + }) + return results + + def fetch_booking(self, channel_reservation_id): + rcode, results = self.backend_adapter.fetch_booking( + self._session_info[0], + self._session_info[1], + channel_reservation_id) + if rcode != 0: + raise ValidationError(_("Can't process reservation from wubook"), { + 'message': results, + }) + return results + + def mark_bookings(self, channel_reservation_ids): + init_connection = self._context.get('init_connection', True) + if init_connection: + if not self.init_connection(): + return False + rcode, results = self._server.mark_bookings( + self._session_info[0], + self._session_info[1], + channel_reservation_ids) + if rcode != 0: + raise ValidationError(_("Can't mark as readed a reservation in wubook"), { + 'message': results, + 'channel_reservation_ids': str(channel_reservation_ids), + }) + return results + + # === PRICE PLANS + def create_plan(self, name, daily=1): + rcode, results = self._server.add_pricing_plan( + self._session_info[0], + self._session_info[1], + name, + daily) + if rcode != 0: + raise ValidationError(_("Can't add pricing plan to wubook"), { + 'message': results, + }) + return results + + def delete_plan(self, channel_plan_id): + rcode, results = self._server.del_plan( + self._session_info[0], + self._session_info[1], + channel_plan_id) + if rcode != 0: + raise ValidationError(_("Can't delete pricing plan from wubook"), { + 'message': results, + 'channel_plan_id': channel_plan_id, + }) + return results + + def update_plan_name(self, channel_plan_id, new_name): + rcode, results = self._server.update_plan_name( + self._session_info[0], + self._session_info[1], + channel_plan_id, + new_name) + if rcode != 0: + raise ValidationError(_("Can't update pricing plan name in wubook"), { + 'message': results, + 'channel_plan_id': channel_plan_id, + }) + return results + + def update_plan_prices(self, channel_plan_id, date_from, prices): + rcode, results = self._server.update_plan_prices( + self._session_info[0], + self._session_info[1], + channel_plan_id, + date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + prices) + if rcode != 0: + raise ValidationError(_("Can't update pricing plan in wubook"), { + 'message': results, + 'channel_plan_id': channel_plan_id, + 'date_from': date_from, + }) + return results + + def update_plan_periods(self, channel_plan_id, periods): + rcode, results = self.SERVER.update_plan_periods( + self._session_info[0], + self._session_info[1], + channel_plan_id, + periods) + if rcode != 0: + raise ValidationError(_("Can't update pricing plan period in wubook"), { + 'message': results, + 'channel_plan_id': channel_plan_id, + }) + return results + + def get_pricing_plans(self): + rcode, results = self.SERVER.get_pricing_plans( + self._session_info[0], + self._session_info[1]) + if rcode != 0: + raise ValidationError(_("Can't get pricing plans from wubook"), { + 'message': results, + }) + return results + + def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms): + rcode, results = self._server.fetch_plan_prices( + self._session_info[0], + self._session_info[1], + channel_plan_id, + date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + rooms or []) + if rcode != 0: + raise ValidationError(_("Can't get pricing plans from wubook"), { + 'message': results, + 'channel_plan_id': channel_plan_id, + 'date_from': date_from, + 'date_to': date_to + }) + return results + + # === RESTRICTIONS + def rplan_rplans(self): + rcode, results = self._server.rplan_rplans( + self._session_info[0], + self._session_info[1]) + if rcode != 0: + raise ValidationError(_("Can't fetch restriction plans from wubook"), { + 'message': results, + }) + return results + + def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id): + rcode, results = self._server.wired_rplan_get_rplan_values( + self._session_info[0], + self._session_info[1], + date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + channel_restriction_plan_id) + if rcode != 0: + raise ValidationError(_("Can't fetch restriction plans from wubook"), { + 'message': results, + 'channel_restriction_plan_id': channel_restriction_plan_id, + 'date_from': date_from, + 'date_to': date_to, + }) + return results + + def update_rplan_values(self, channel_restriction_plan_id, date_from, values): + rcode, results = self._server.rplan_update_rplan_values( + self._session_info[0], + self._session_info[1], + channel_restriction_plan_id, + date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), + values) + if rcode != 0: + raise ValidationError(_("Can't update plan restrictions on wubook"), { + 'message': results, + 'channel_restriction_plan_id': channel_restriction_plan_id, + 'date_from': date_from, + }) + return results + + def create_rplan(self, name, compact=False): + rcode, results = self._server.rplan_add_rplan( + self._session_info[0], + self._session_info[1], + name, + compact and 1 or 0) + if rcode != 0: + raise ValidationError(_("Can't create plan restriction in wubook"), { + 'message': results, + }) + return results + + def rename_rplan(self, channel_restriction_plan_id, new_name): + rcode, results = self._server.rplan_rename_rplan( + self._session_info[0], + self._session_info[1], + channel_restriction_plan_id, + new_name) + if rcode != 0: + raise ValidationError(_("Can't rename plan restriction in wubook"), { + 'message': results, + 'channel_restriction_plan_id': channel_restriction_plan_id, + }) + return results + + def delete_rplan(self, channel_restriction_plan_id): + rcode, results = self._server.rplan_del_rplan( + self._session_info[0], + self._session_info[1], + channel_restriction_plan_id) + if rcode != 0: + raise ValidationError(_("Can't delete plan restriction on wubook"), { + 'message': results, + 'channel_restriction_plan_id': channel_restriction_plan_id, + }) + return results + + 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"), { + 'message': results, + }) + return results diff --git a/hotel_channel_connector/components/binder.py b/hotel_channel_connector/components/binder.py new file mode 100644 index 000000000..45dd234cd --- /dev/null +++ b/hotel_channel_connector/components/binder.py @@ -0,0 +1,8 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.addons.component.core import Component + +class HotelConnectorModelBinder(Component): + _name = 'hotel.channel.connector.binder' + _inherit = ['base.binder', 'base.hotel.channel.connector'] + _apply_on = [] diff --git a/hotel_channel_connector/components/core.py b/hotel_channel_connector/components/core.py new file mode 100644 index 000000000..7cdea7605 --- /dev/null +++ b/hotel_channel_connector/components/core.py @@ -0,0 +1,19 @@ +from odoo.addons.component.core import AbstractComponent + + +class BaseHotelChannelConnectorComponent(AbstractComponent): + _name = 'base.hotel.channel.connector' + _inherit = 'base.connector' + _collection = 'hotel.channel.backend' + + @api.model + def create_issue(self, section, message, wmessage, wid=False, + dfrom=False, dto=False): + self.env['hotel.channel.connector.issue'].sudo().create({ + 'section': section, + 'message': message, + 'wid': wid, + 'wmessage': wmessage, + 'date_start': dfrom and dfrom.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'date_end': dto and dto.strftime(DEFAULT_SERVER_DATE_FORMAT), + }) diff --git a/hotel_channel_connector/components/exporter.py b/hotel_channel_connector/components/exporter.py new file mode 100644 index 000000000..16080ec73 --- /dev/null +++ b/hotel_channel_connector/components/exporter.py @@ -0,0 +1,169 @@ +# 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 AbstractComponent +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) +from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT +from odoo.addons.hotel import date_utils +_logger = logging.getLogger(__name__) + +class HotelChannelConnectorExporter(AbstractComponent): + _name = 'hotel.channel.exporter' + _inherit = ['base.exporter', 'base.hotel.channel.connector'] + _usage = 'channel.exporter' + + @api.model + def push_changes(self): + return self.push_availability() and self.push_priceplans() and \ + self.push_restrictions() + + @api.model + def push_availability(self): + vroom_avail_ids = self.env['hotel.virtual.room.availability'].search([ + ('wpushed', '=', False), + ('date', '>=', date_utils.now(hours=False).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + ]) + + vrooms = vroom_avail_ids.mapped('virtual_room_id') + avails = [] + for vroom in vrooms: + vroom_avails = vroom_avail_ids.filtered( + lambda x: x.virtual_room_id.id == vroom.id) + days = [] + for vroom_avail in vroom_avails: + vroom_avail.with_context({ + 'wubook_action': False}).write({'wpushed': True}) + wavail = vroom_avail.avail + if wavail > vroom_avail.wmax_avail: + wavail = vroom_avail.wmax_avail + date_dt = date_utils.get_datetime( + vroom_avail.date, + dtformat=DEFAULT_SERVER_DATE_FORMAT) + days.append({ + 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 'avail': wavail, + 'no_ota': vroom_avail.no_ota and 1 or 0, + # 'booked': vroom_avail.booked and 1 or 0, + }) + avails.append({'id': vroom.wrid, 'days': days}) + _logger.info("UPDATING AVAILABILITY IN WUBOOK...") + _logger.info(avails) + if any(avails): + self.backend_adapter.update_availability(avails) + return True + + @api.model + def push_priceplans(self): + unpushed = self.env['product.pricelist.item'].search([ + ('wpushed', '=', False), + ('date_start', '>=', date_utils.now(hours=False).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + ], order="date_start ASC") + if any(unpushed): + date_start = date_utils.get_datetime( + unpushed[0].date_start, + dtformat=DEFAULT_SERVER_DATE_FORMAT) + date_end = date_utils.get_datetime( + unpushed[-1].date_start, + dtformat=DEFAULT_SERVER_DATE_FORMAT) + days_diff = date_utils.date_diff(date_start, date_end, hours=False) + 1 + + prices = {} + pricelist_ids = self.env['product.pricelist'].search([ + ('wpid', '!=', False), + ('active', '=', True) + ]) + for pr in pricelist_ids: + prices.update({pr.wpid: {}}) + unpushed_pl = self.env['product.pricelist.item'].search( + [('wpushed', '=', False), ('pricelist_id', '=', pr.id)]) + product_tmpl_ids = unpushed_pl.mapped('product_tmpl_id') + for pt_id in product_tmpl_ids: + vroom = self.env['hotel.room.type'].search([ + ('product_id.product_tmpl_id', '=', pt_id.id) + ], limit=1) + if vroom: + prices[pr.wpid].update({vroom.wrid: []}) + for i in range(0, days_diff): + prod = vroom.product_id.with_context({ + 'quantity': 1, + 'pricelist': pr.id, + 'date': (date_start + timedelta(days=i)). + strftime(DEFAULT_SERVER_DATE_FORMAT), + }) + prices[pr.wpid][vroom.wrid].append(prod.price) + _logger.info("UPDATING PRICES IN WUBOOK...") + _logger.info(prices) + for k_pk, v_pk in prices.iteritems(): + if any(v_pk): + self.backend_adapter.update_plan_prices(k_pk, date_start.strftime( + DEFAULT_SERVER_DATE_FORMAT), v_pk) + + unpushed.with_context({ + 'wubook_action': False}).write({'wpushed': True}) + return True + + @api.model + def push_restrictions(self): + vroom_rest_obj = self.env['hotel.virtual.room.restriction'] + rest_item_obj = self.env['hotel.virtual.room.restriction.item'] + unpushed = rest_item_obj.search([ + ('wpushed', '=', False), + ('date_start', '>=', date_utils.now(hours=False).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + ], order="date_start ASC") + if any(unpushed): + date_start = date_utils.get_datetime( + unpushed[0].date_start, + dtformat=DEFAULT_SERVER_DATE_FORMAT) + date_end = date_utils.get_datetime( + unpushed[-1].date_start, + dtformat=DEFAULT_SERVER_DATE_FORMAT) + days_diff = date_utils.date_diff( + date_start, + date_end, + hours=False) + 1 + restrictions = {} + restriction_plan_ids = vroom_rest_obj.search([ + ('wpid', '!=', False), + ('active', '=', True) + ]) + for rp in restriction_plan_ids: + restrictions.update({rp.wpid: {}}) + unpushed_rp = rest_item_obj.search([ + ('wpushed', '=', False), + ('restriction_id', '=', rp.id) + ]) + virtual_room_ids = unpushed_rp.mapped('virtual_room_id') + for vroom in virtual_room_ids: + restrictions[rp.wpid].update({vroom.wrid: []}) + for i in range(0, days_diff): + ndate_dt = date_start + timedelta(days=i) + restr = vroom.get_restrictions( + ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + if restr: + restrictions[rp.wpid][vroom.wrid].append({ + 'min_stay': restr.min_stay or 0, + 'min_stay_arrival': restr.min_stay_arrival or 0, + 'max_stay': restr.max_stay or 0, + 'max_stay_arrival': restr.max_stay_arrival or 0, + 'closed': restr.closed and 1 or 0, + 'closed_arrival': restr.closed_arrival and 1 or 0, + 'closed_departure': restr.closed_departure and 1 or 0, + }) + else: + restrictions[rp.wpid][vroom.wrid].append({}) + _logger.info("UPDATING RESTRICTIONS IN WUBOOK...") + _logger.info(restrictions) + for k_res, v_res in restrictions.iteritems(): + if any(v_res): + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) + unpushed.with_context({ + 'wubook_action': False}).write({'wpushed': True}) + return True diff --git a/hotel_channel_connector/components/importer.py b/hotel_channel_connector/components/importer.py new file mode 100644 index 000000000..1e7832046 --- /dev/null +++ b/hotel_channel_connector/components/importer.py @@ -0,0 +1,871 @@ +# 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 AbstractComponent +from odoo.tools import ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_DATETIME_FORMAT) +from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT +_logger = logging.getLogger(__name__) + +class HotelChannelConnectorImporter(AbstractComponent): + _name = 'hotel.channel.importer' + _inherit = ['base.importer', 'base.hotel.channel.connector'] + _usage = 'channel.importer' + + @api.model + def _get_room_values_availability(self, vroom_id, date_str, day_vals, set_wmax_avail): + virtual_room_avail_obj = self.env['hotel.virtual.room.availability'] + vroom_avail = virtual_room_avail_obj.search([ + ('virtual_room_id', '=', vroom_id), + ('date', '=', date_str) + ], limit=1) + vals = { + 'no_ota': day_vals.get('no_ota'), + 'booked': day_vals.get('booked'), + 'avail': day_vals.get('avail', 0), + 'wpushed': True, + } + if set_wmax_avail: + vals.update({'wmax_avail': day_vals.get('avail', 0)}) + if vroom_avail: + vroom_avail.with_context({ + 'wubook_action': False, + }).write(vals) + else: + vals.update({ + 'virtual_room_id': vroom_id, + 'date': date_str, + }) + virtual_room_avail_obj.with_context({ + 'wubook_action': False, + 'mail_create_nosubscribe': True, + }).create(vals) + + @api.model + def _get_room_values_restrictions(self, restriction_plan_id, vroom_id, date_str, day_vals): + vroom_restr_item_obj = self.env['hotel.virtual.room.restriction.item'] + vroom_restr = vroom_restr_item_obj.search([ + ('virtual_room_id', '=', vroom_id), + ('applied_on', '=', '0_virtual_room'), + ('date_start', '=', date_str), + ('date_end', '=', date_str), + ('restriction_id', '=', restriction_plan_id), + ]) + vals = { + 'min_stay': int(day_vals.get('min_stay', 0)), + 'min_stay_arrival': int(day_vals.get( + 'min_stay_arrival', + 0)), + 'max_stay': int(day_vals.get('max_stay', 0)), + 'max_stay_arrival': int(day_vals.get( + 'max_stay_arrival', + 0)), + 'closed': int(day_vals.get('closed', False)), + 'closed_departure': int(day_vals.get( + 'closed_departure', + False)), + 'closed_arrival': int(day_vals.get( + 'closed_arrival', + False)), + 'wpushed': True, + } + if vroom_restr: + vroom_restr.with_context({ + 'wubook_action': False, + }).write(vals) + else: + vals.update({ + 'restriction_id': restriction_plan_id, + 'virtual_room_id': vroom_id, + 'date_start': date_str, + 'date_end': date_str, + 'applied_on': '0_virtual_room', + }) + vroom_restr_item_obj.with_context({ + 'wubook_action': False, + }).create(vals) + + @api.model + def _generate_room_values(self, dfrom, dto, values, set_wmax_avail=False): + virtual_room_restr_obj = self.env['hotel.virtual.room.restriction'] + hotel_virtual_room_obj = self.env['hotel.room.type'] + def_restr_plan = virtual_room_restr_obj.search([('wpid', '=', '0')]) + _logger.info("==== ROOM VALUES (%s -- %s)", dfrom, dto) + _logger.info(values) + for k_rid, v_rid in values.iteritems(): + vroom = hotel_virtual_room_obj.search([ + ('wrid', '=', k_rid) + ], limit=1) + if vroom: + 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) + self._get_room_values_availability( + vroom.id, + date_str, + day_vals, + set_wmax_avail) + if def_restr_plan: + self._get_room_values_restrictions( + def_restr_plan.id, + vroom.id, + date_str, + day_vals) + date_dt = date_dt + timedelta(days=1) + return True + + @api.model + def _generate_booking_vals(self, broom, checkin_str, checkout_str, + is_cancellation, wchannel_info, wstatus, crcode, + rcode, vroom, split_booking, dates_checkin, + dates_checkout, book): + # Generate Reservation Day Lines + reservation_line_ids = [] + tprice = 0.0 + for brday in broom['roomdays']: + wndate = date_utils.get_datetime( + brday['day'], + dtformat=DEFAULT_WUBOOK_DATE_FORMAT + ).replace(tzinfo=pytz.utc) + if date_utils.date_in(wndate, + dates_checkin[0], + dates_checkout[0] - timedelta(days=1), + hours=False) == 0: + reservation_line_ids.append((0, False, { + 'date': wndate.strftime( + DEFAULT_SERVER_DATE_FORMAT), + 'price': brday['price'] + })) + tprice += brday['price'] + persons = vroom.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, + 'wstatus': wstatus, + 'to_read': True, + 'state': is_cancellation and 'cancelled' or 'draft', + 'virtual_room_id': vroom.id, + 'splitted': split_booking, + 'wbook_json': json.dumps(book), + 'wmodified': book['was_modified'] + } + _logger.info("===== CONTRUCT RESERV") + _logger.info(vals) + return vals + + @api.model + def _generate_partner_vals(self, book): + country_id = self.env['res.country'].search([ + ('code', '=', str(book['customer_country'])) + ], limit=1) + # lang = self.env['res.lang'].search([('code', '=', book['customer_language_iso'])], limit=1) + return { + 'name': "%s, %s" % + (book['customer_surname'], book['customer_name']), + 'country_id': country_id and country_id.id, + 'city': book['customer_city'], + 'phone': book['customer_phone'], + 'zip': book['customer_zip'], + 'street': book['customer_address'], + 'email': book['customer_mail'], + 'unconfirmed': True, + # 'lang': lang and lang.id, + } + + # FIXME: Super big method!!! O_o + @api.model + def _generate_reservations(self, bookings): + _logger.info("=== BOOKINGS FROM WUBOOK") + _logger.info(bookings) + default_arrival_hour = self.env['ir.default'].sudo().get( + 'hotel.config.settings', 'default_arrival_hour') + default_departure_hour = self.env['ir.default'].sudo().get( + 'hotel.config.settings', 'default_departure_hour') + + # Get user timezone + tz_hotel = self.env['ir.default'].sudo().get( + 'hotel.config.settings', 'tz_hotel') + res_partner_obj = self.env['res.partner'] + hotel_reserv_obj = self.env['hotel.reservation'] + hotel_folio_obj = self.env['hotel.folio'] + hotel_vroom_obj = self.env['hotel.room.type'] + # Space for store some data for construct folios + processed_rids = [] + failed_reservations = [] + checkin_utc_dt = False + checkout_utc_dt = False + split_booking = False + for book in bookings: # This create a new folio + splitted_map = {} + is_cancellation = book['status'] in WUBOOK_STATUS_BAD + bstatus = str(book['status']) + rcode = str(book['reservation_code']) + crcode = str(book['channel_reservation_code']) \ + if book['channel_reservation_code'] else 'undefined' + + # Can't process failed reservations + # (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( + 'reservation', + "Can't process a reservation that previusly failed!", + '', wid=book['reservation_code']) + continue + + # Get dates for the reservation (GMT->UTC) + arr_hour = default_arrival_hour if book['arrival_hour'] == "--" \ + else book['arrival_hour'] + checkin = "%s %s" % (book['date_arrival'], arr_hour) + checkin_dt = date_utils.get_datetime( + checkin, + dtformat=DEFAULT_WUBOOK_DATETIME_FORMAT, + stz=tz_hotel) + checkin_utc_dt = date_utils.dt_as_timezone(checkin_dt, 'UTC') + checkin = checkin_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + + checkout = "%s %s" % (book['date_departure'], + default_departure_hour) + checkout_dt = date_utils.get_datetime( + checkout, + dtformat=DEFAULT_WUBOOK_DATETIME_FORMAT, + stz=tz_hotel) + checkout_utc_dt = date_utils.dt_as_timezone(checkout_dt, 'UTC') + checkout = checkout_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + + # Search Folio. If exists. + folio_id = False + if crcode != 'undefined': + reserv_folio = hotel_reserv_obj.search([ + ('wchannel_reservation_code', '=', crcode) + ], limit=1) + if reserv_folio: + folio_id = reserv_folio.folio_id + else: + reserv_folio = hotel_reserv_obj.search([ + ('wrid', '=', rcode) + ], limit=1) + if reserv_folio: + folio_id = reserv_folio.folio_id + + # Need update reservations? + sreservs = hotel_reserv_obj.search([('wrid', '=', rcode)]) + reservs = folio_id.room_lines if folio_id else sreservs + 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({ + 'wstatus': str(book['status']), + 'wstatus_reason': book.get('status_reason', ''), + 'to_read': True, + 'to_assign': True, + 'price_unit': book['amount'], + 'wcustomer_notes': book['customer_notes'], + 'wbook_json': json.dumps(book), + }) + if reserv.partner_id.unconfirmed: + reserv.partner_id.write( + self._generate_partner_vals(book) + ) + reservs_processed = True + if is_cancellation: + reserv.with_context({ + 'wubook_action': False}).action_cancel() + elif reserv.state == 'cancelled': + reserv.with_context({ + 'wubook_action': False, + }).write({ + 'discount': 0.0, + 'state': 'confirm', + }) + + # Do Nothing if already processed 'wrid' + if reservs_processed: + processed_rids.append(rcode) + continue + + # Search Customer + customer_mail = book.get('customer_mail', False) + partner_id = False + if customer_mail: + partner_id = res_partner_obj.search([ + ('email', '=', customer_mail) + ], limit=1) + if not partner_id: + 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) + + reservations = [] + used_rooms = [] + # Iterate booked rooms + for broom in book['booked_rooms']: + vroom = hotel_vroom_obj.search([ + ('wrid', '=', broom['room_id']) + ], limit=1) + if not vroom: + self.create_channel_connector_issue( + 'reservation', + "Can't found any virtual room associated to '%s' \ + in this hotel" % book['rooms'], + '', wid=book['reservation_code']) + failed_reservations.append(crcode) + continue + + dates_checkin = [checkin_utc_dt, False] + dates_checkout = [checkout_utc_dt, False] + split_booking = False + split_booking_parent = False + # This perhaps create splitted reservation + while dates_checkin[0]: + checkin_str = dates_checkin[0].strftime( + DEFAULT_SERVER_DATETIME_FORMAT) + checkout_str = dates_checkout[0].strftime( + DEFAULT_SERVER_DATETIME_FORMAT) + vals = self._generate_booking_vals( + broom, + checkin_str, + checkout_str, + is_cancellation, + wchannel_info, + bstatus, + crcode, + rcode, + vroom, + split_booking, + dates_checkin, + dates_checkout, + book, + ) + if vals['price_unit'] != book['amount']: + self.create_channel_connector_issue( + 'reservation', + "Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), + '', wid=book['reservation_code']) + + free_rooms = hotel_vroom_obj.check_availability_virtual_room( + checkin_str, + checkout_str, + virtual_room_id=vroom.id, + notthis=used_rooms) + if any(free_rooms): + vals.update({ + 'product_id': free_rooms[0].product_id.id, + 'name': free_rooms[0].name, + }) + reservations.append((0, False, vals)) + used_rooms.append(free_rooms[0].id) + + if split_booking: + if not split_booking_parent: + split_booking_parent = len(reservations) + else: + splitted_map.setdefault( + split_booking_parent, + []).append(len(reservations)) + dates_checkin = [dates_checkin[1], False] + dates_checkout = [dates_checkout[1], False] + else: + date_diff = (dates_checkout[0].replace( + hour=0, minute=0, second=0, + microsecond=0) - + dates_checkin[0].replace( + hour=0, minute=0, second=0, + microsecond=0)).days + if date_diff <= 0: + if split_booking: + if split_booking_parent: + del reservations[split_booking_parent-1:] + if split_booking_parent in splitted_map: + del splitted_map[split_booking_parent] + # Can't found space for reservation + vals = self._generate_booking_vals( + broom, + checkin_utc_dt, + checkout_utc_dt, + is_cancellation, + wchannel_info, + bstatus, + crcode, + rcode, + vroom, + False, + (checkin_utc_dt, False), + (checkout_utc_dt, False), + book, + ) + vals.update({ + 'product_id': + vroom.room_ids[0].product_id.id, + 'name': vroom.name, + 'overbooking': True, + }) + reservations.append((0, False, vals)) + self.create_channel_connector_issue( + 'reservation', + "Reservation imported with overbooking state", + '', wid=rcode) + dates_checkin = [False, False] + dates_checkout = [False, False] + split_booking = False + else: + split_booking = True + dates_checkin = [ + dates_checkin[0], + dates_checkin[0] + timedelta(days=date_diff-1) + ] + dates_checkout = [ + dates_checkout[0] - timedelta(days=1), + checkout_utc_dt + ] + + if split_booking: + self.create_channel_connector_issue( + 'reservation', + "Reservation Splitted", + '', wid=rcode) + + # Create Folio + if not any(failed_reservations) and any(reservations): + try: + # TODO: Improve 'addons_list' & discounts + addons = str(book['addons_list']) if any(book['addons_list']) else '' + discounts = book.get('discount', '') + vals = { + 'room_lines': reservations, + 'wcustomer_notes': "%s\nADDONS:\n%s\nDISCOUNT:\n%s" % ( + book['customer_notes'], addons, discounts), + 'channel_type': 'web', + } + _logger.info("=== FOLIO CREATE") + _logger.info(reservations) + if folio_id: + folio_id.with_context({ + 'wubook_action': False}).write(vals) + else: + vals.update({ + 'partner_id': partner_id.id, + 'wseed': book['sessionSeed'] + }) + folio_id = hotel_folio_obj.with_context({ + 'wubook_action': False}).create(vals) + + # Update Reservation Spitted Parents + sorted_rlines = folio_id.room_lines.sorted(key='id') + for k_pid, v_pid in splitted_map.iteritems(): + preserv = sorted_rlines[k_pid-1] + for pid in v_pid: + creserv = sorted_rlines[pid-1] + creserv.parent_reservation = preserv.id + + processed_rids.append(rcode) + except Exception as e_msg: + self.create_channel_connector_issue( + 'reservation', + e_msg[0], + '', wid=rcode) + failed_reservations.append(crcode) + return (processed_rids, any(failed_reservations), + checkin_utc_dt, checkout_utc_dt) + + @api.model + def _generate_pricelists(self, price_plans): + product_listprice_obj = self.env['product.pricelist'] + count = 0 + for plan in price_plans: + if 'vpid' in plan: + continue # Ignore Virtual Plans + + vals = { + 'name': plan['name'], + 'wdaily': plan['daily'] == 1, + } + plan_id = product_listprice_obj.search([ + ('wpid', '=', str(plan['id'])) + ], limit=1) + if not plan_id: + vals.update({ + 'wpid': str(plan['id']), + }) + product_listprice_obj.with_context({ + 'wubook_action': False}).create(vals) + else: + plan_id.with_context({'wubook_action': False}).write(vals) + count = count + 1 + return count + + @api.model + def _generate_pricelist_items(self, channel_plan_id, date_from, date_to, plan_prices): + hotel_virtual_room_obj = self.env['hotel.room.type'] + pricelist = self.env['product.pricelist'].search([ + ('wpid', '=', channel_plan_id) + ], limit=1) + if pricelist: + pricelist_item_obj = self.env['product.pricelist.item'] + dfrom_dt = date_utils.get_datetime(date_from) + dto_dt = date_utils.get_datetime(date_to) + days_diff = date_utils.date_diff(dfrom_dt, dto_dt, hours=False) + 1 + for i in range(0, days_diff): + ndate_dt = dfrom_dt + timedelta(days=i) + for k_rid, v_rid in plan_prices.iteritems(): + vroom = hotel_virtual_room_obj.search([ + ('wrid', '=', k_rid) + ], limit=1) + if vroom: + pricelist_item = pricelist_item_obj.search([ + ('pricelist_id', '=', pricelist.id), + ('date_start', '=', ndate_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT)), + ('date_end', '=', ndate_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT)), + ('compute_price', '=', 'fixed'), + ('applied_on', '=', '1_product'), + ('product_tmpl_id', '=', vroom.product_id.product_tmpl_id.id) + ], limit=1) + vals = { + 'fixed_price': plan_prices[k_rid][i], + 'wpushed': True, + } + if pricelist_item: + pricelist_item.with_context({ + 'wubook_action': False}).write(vals) + else: + vals.update({ + 'pricelist_id': pricelist.id, + 'date_start': ndate_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT), + 'date_end': ndate_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT), + 'compute_price': 'fixed', + 'applied_on': '1_product', + 'product_tmpl_id': vroom.product_id.product_tmpl_id.id + }) + pricelist_item_obj.with_context({ + 'wubook_action': False}).create(vals) + return True + + @api.model + def _generate_restrictions(self, restriction_plans): + restriction_obj = self.env['hotel.virtual.room.restriction'] + count = 0 + for plan in restriction_plans: + vals = { + 'name': plan['name'], + } + plan_id = restriction_obj.search([ + ('wpid', '=', str(plan['id'])) + ], limit=1) + if not plan_id: + vals.update({ + 'wpid': str(plan['id']), + }) + restriction_obj.with_context({ + 'wubook_action': False, + 'rules': plan.get('rules'), + }).create(vals) + else: + plan_id.with_context({'wubook_action': False}).write(vals) + count = count + 1 + return count + + @api.model + def _generate_restriction_items(self, plan_restrictions): + hotel_virtual_room_obj = self.env['hotel.room.type'] + reserv_restriction_obj = self.env['hotel.virtual.room.restriction'] + restriction_item_obj = self.env['hotel.virtual.room.restriction.item'] + _logger.info("===== RESTRICTIONS") + _logger.info(plan_restrictions) + for k_rpid, v_rpid in plan_restrictions.iteritems(): + restriction_id = reserv_restriction_obj.search([ + ('wpid', '=', k_rpid) + ], limit=1) + if restriction_id: + for k_rid, v_rid in v_rpid.iteritems(): + vroom = hotel_virtual_room_obj.search([ + ('wrid', '=', k_rid) + ], limit=1) + if vroom: + for item in v_rid: + date_dt = date_utils.get_datetime( + item['date'], + dtformat=DEFAULT_WUBOOK_DATE_FORMAT) + restriction_item = restriction_item_obj.search([ + ('restriction_id', '=', restriction_id.id), + ('date_start', '=', date_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT)), + ('date_end', '=', date_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT)), + ('applied_on', '=', '0_virtual_room'), + ('virtual_room_id', '=', vroom.id) + ], limit=1) + vals = { + 'closed_arrival': item['closed_arrival'], + 'closed': item['closed'], + 'min_stay': item['min_stay'], + 'closed_departure': item['closed_departure'], + 'max_stay': item['max_stay'], + 'max_stay_arrival': item['max_stay_arrival'], + 'min_stay_arrival': item['min_stay_arrival'], + 'wpushed': True, + } + if restriction_item: + restriction_item.with_context({ + 'wubook_action': False}).write(vals) + else: + vals.update({ + 'restriction_id': restriction_id.id, + 'date_start': date_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT), + 'date_end': date_dt.strftime( + DEFAULT_SERVER_DATE_FORMAT), + 'applied_on': '0_virtual_room', + 'virtual_room_id': vroom.id + }) + restriction_item_obj.with_context({ + 'wubook_action': False}).create(vals) + + 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() + + vroom_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'], + } + vroom = vroom_obj.search([('wrid', '=', room['id'])], limit=1) + if vroom: + vroom.with_context({'wubook_action': False}).write(vals) + else: + vroom_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_wmax_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_wmax_avail=set_wmax_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_new_bookings(self): + try: + results = self.backend_adapter.fetch_new_bookings() + processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \ + self._generate_reservations(results) + if any(processed_rids): + uniq_rids = list(set(processed_rids)) + rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids) + if rcodeb != 0: + self.create_issue( + 'wubook', + _("Problem trying mark bookings (%s)") % + str(processed_rids), + '') + # Update Odoo availability (don't wait for wubook) + # This cause abuse service in first import!! + if checkin_utc_dt and checkout_utc_dt: + self.backend_adapter.fetch_rooms_values( + checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + except ValidationError: + self.create_issue( + 'reservation', + _("Can't process reservations from wubook"), + results) + return False + return True + + @api.model + def fetch_booking(self, channel_reservation_id): + try: + results = self.backend_adapter.fetch_booking(channel_reservation_id) + processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \ + self.generate_reservations(results) + if any(processed_rids): + self.backend_adapter.mark_bookings(list(set(processed_rids))) + + # Update Odoo availability (don't wait for wubook) + if checkin_utc_dt and checkout_utc_dt: + self.backend_adapter.fetch_rooms_values( + checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + except ValidationError: + self.create_channel_connector_issue( + 'reservation', + _("Can't process reservations from wubook"), + results, wid=wrid) + return False + return True + + @api.model + def import_pricing_plans(self): + try: + results = self.backend_adapter.get_pricing_plans() + count = self._generate_pricelists(results) + except ValidationError: + self.create_issue( + 'plan', + _("Can't get pricing plans from wubook"), + results) + return 0 + return count + + @api.model + def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms=None): + try: + results = self.backend_adapter.fetch_plan_prices( + channel_plan_id, + date_from, + date_to, + rooms) + self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) + except ValidationError: + self.create_issue( + 'plan', + _("Can't fetch plan prices from wubook"), + results) + return False + return True + + @api.model + def fetch_all_plan_prices(self, date_from, date_to, rooms=None): + no_errors = True + channel_plan_ids = self.env['product.pricelist'].search([ + ('wpid', '!=', False), ('wpid', '!=', '') + ]).mapped('wpid') + if any(channel_plan_ids): + try: + for channel_plan_id in channel_plan_ids: + results = self.backend_adapter.fetch_plan_prices( + channel_plan_id, + date_from, + date_to, + rooms) + self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) + except ValidationError: + self.create_issue( + 'plan', + "Can't fetch all plan prices from wubook!", + results, wid=channel_plan_id, dfrom=date_from, dto=date_to) + return False + return no_errors + + @api.model + def import_restriction_plans(self): + try: + results = self.backend_adapter.rplan_rplans() + count = self._generate_restrictions(results) + except ValidationError: + self.create_issue( + 'rplan', + _("Can't fetch restriction plans from wubook"), + results) + return 0 + return count + + @api.model + def fetch_rplan_restrictions(self, date_from, date_to, channel_restriction_plan_id=False): + try: + results = self.backend_adapter.wired_rplan_get_rplan_values( + date_from, + date_to, + int(channel_restriction_plan_id)) + if any(results): + self._generate_restriction_items(results) + except ValidationError: + self.create_issue( + 'rplan', + _("Can't fetch plan restrictions from wubook"), + results, + wid=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 diff --git a/hotel_channel_connector/controllers/__init__.py b/hotel_channel_connector/controllers/__init__.py index ccdb3ead6..4f27ee78d 100644 --- a/hotel_channel_connector/controllers/__init__.py +++ b/hotel_channel_connector/controllers/__init__.py @@ -1,22 +1,3 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import main diff --git a/hotel_channel_connector/controllers/main.py b/hotel_channel_connector/controllers/main.py index 50efa0d55..5e06b04f1 100644 --- a/hotel_channel_connector/controllers/main.py +++ b/hotel_channel_connector/controllers/main.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging from openerp import http, _ from openerp.http import request diff --git a/hotel_channel_connector/data/menus.xml b/hotel_channel_connector/data/menus.xml index f6259ceb1..17dabeeb7 100644 --- a/hotel_channel_connector/data/menus.xml +++ b/hotel_channel_connector/data/menus.xml @@ -1,6 +1,5 @@ - - + WuBook Channel Info @@ -38,5 +37,4 @@ - - + diff --git a/hotel_channel_connector/data/sequences.xml b/hotel_channel_connector/data/sequences.xml index cb58030f8..4143cc28e 100644 --- a/hotel_channel_connector/data/sequences.xml +++ b/hotel_channel_connector/data/sequences.xml @@ -1,13 +1,11 @@ - - - + + Virtual Room Short Code - hotel.virtual.room + hotel.room.type 4 - - \ No newline at end of file + diff --git a/hotel_channel_connector/models/__init__.py b/hotel_channel_connector/models/__init__.py index 16670f20c..df7e001e3 100644 --- a/hotel_channel_connector/models/__init__.py +++ b/hotel_channel_connector/models/__init__.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import res_config from . import inherited_hotel_virtual_room from . import inherited_product_pricelist @@ -30,4 +11,4 @@ from . import inherited_hotel_reservation from . import inherited_hotel_folio from . import inherited_res_partner from . import wubook_channel_info -from . import wubook_issue +from . import hotel_channel_connector_issue diff --git a/hotel_channel_connector/models/hotel_channel_connector_issue.py b/hotel_channel_connector/models/hotel_channel_connector_issue.py index 511812f1c..2d68b4404 100644 --- a/hotel_channel_connector/models/hotel_channel_connector_issue.py +++ b/hotel_channel_connector/models/hotel_channel_connector_issue.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api, _ from openerp.exceptions import ValidationError @@ -34,7 +15,6 @@ class HotelChannelConnectorIssue(models.Model): ('plan', 'Price Plan'), ('room', 'Room'), ('avail', 'Availability')], required=True) - channel_name = fields.Char("Channel Name") to_read = fields.Boolean("To Read", default=True) internal_message = fields.Char("Internal Message", old_name='message') date_start = fields.Date("From", readonly=True) diff --git a/hotel_channel_connector/models/inherited_hotel_folio.py b/hotel_channel_connector/models/inherited_hotel_folio.py index e98a04f86..2fb23e6b2 100644 --- a/hotel_channel_connector/models/inherited_hotel_folio.py +++ b/hotel_channel_connector/models/inherited_hotel_folio.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api diff --git a/hotel_channel_connector/models/inherited_hotel_reservation.py b/hotel_channel_connector/models/inherited_hotel_reservation.py index 9369b10f2..1ded7f79d 100644 --- a/hotel_channel_connector/models/inherited_hotel_reservation.py +++ b/hotel_channel_connector/models/inherited_hotel_reservation.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging from openerp import models, fields, api, _ from openerp.exceptions import UserError, ValidationError diff --git a/hotel_channel_connector/models/inherited_hotel_virtual_room.py b/hotel_channel_connector/models/inherited_hotel_virtual_room.py index 298bc2d1d..8f5c3f40e 100644 --- a/hotel_channel_connector/models/inherited_hotel_virtual_room.py +++ b/hotel_channel_connector/models/inherited_hotel_virtual_room.py @@ -1,30 +1,11 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api, _ from openerp.exceptions import ValidationError class HotelVirtualRoom(models.Model): - _inherit = 'hotel.virtual.room' + _inherit = 'hotel.room.type' @api.depends('wcapacity') @api.onchange('room_ids', 'room_type_ids') @@ -39,14 +20,16 @@ class HotelVirtualRoom(models.Model): @api.constrains('wcapacity') def _check_wcapacity(self): - if self.wcapacity < 1: - raise ValidationError(_("wcapacity can't be less than one")) + for record in self: + if record.wcapacity < 1: + raise ValidationError(_("wcapacity can't be less than one")) @api.multi @api.constrains('wscode') def _check_wscode(self): - if len(self.wscode) > 4: # Wubook scode max. length - raise ValidationError(_("SCODE Can't be longer than 4 characters")) + for record in self: + if len(record.wscode) > 4: # Wubook scode max. length + raise ValidationError(_("SCODE Can't be longer than 4 characters")) @api.multi def get_restrictions(self, date): @@ -78,7 +61,7 @@ class HotelVirtualRoom(models.Model): if self._context.get('wubook_action', True) and \ self.env['wubook'].is_valid_account(): seq_obj = self.env['ir.sequence'] - shortcode = seq_obj.next_by_code('hotel.virtual.room')[:4] + shortcode = seq_obj.next_by_code('hotel.room.type')[:4] wrid = self.env['wubook'].create_room( shortcode, vroom.name, diff --git a/hotel_channel_connector/models/inherited_product_pricelist.py b/hotel_channel_connector/models/inherited_product_pricelist.py index 1fe9c646e..7681f0070 100644 --- a/hotel_channel_connector/models/inherited_product_pricelist.py +++ b/hotel_channel_connector/models/inherited_product_pricelist.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime, timedelta from openerp import models, fields, api, _ from openerp.exceptions import ValidationError @@ -50,7 +31,7 @@ class ProductPricelist(models.Model): if not min_date or not max_date: return prices days_diff = abs((max_date - min_date).days) - vrooms = self.env['hotel.virtual.room'].search([ + vrooms = self.env['hotel.room.type'].search([ ('wrid', '!=', ''), ('wrid', '!=', False) ]) @@ -65,7 +46,7 @@ class ProductPricelist(models.Model): uom=vroom.product_id.product_tmpl_id.uom_id.id) prices[vroom.wrid].append(product_id.price) else: - vrooms = self.env['hotel.virtual.room'].search([ + vrooms = self.env['hotel.room.type'].search([ ('wrid', '!=', ''), ('wrid', '!=', False) ]) diff --git a/hotel_channel_connector/models/inherited_product_pricelist_item.py b/hotel_channel_connector/models/inherited_product_pricelist_item.py index aedc234a1..c1c3a58f7 100644 --- a/hotel_channel_connector/models/inherited_product_pricelist_item.py +++ b/hotel_channel_connector/models/inherited_product_pricelist_item.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api from openerp.exceptions import ValidationError @@ -31,20 +12,22 @@ class ProductPricelistItem(models.Model): @api.constrains('fixed_price') def _check_fixed_price(self): - vroom = self.env['hotel.virtual.room'].search([ - ('product_id.product_tmpl_id', '=', self.product_tmpl_id.id) + vroom_obj = self.env['hotel.room.type'] + for record in self: + vroom = vroom_obj.search([ + ('product_id.product_tmpl_id', '=', record.product_tmpl_id.id) ], limit=1) - if vroom and vroom.wrid and self.compute_price == 'fixed' \ - and self.fixed_price <= 0.0: - raise ValidationError(_("Price need be greater than zero")) + if vroom and vroom.wrid and record.compute_price == 'fixed' \ + and record.fixed_price <= 0.0: + raise ValidationError(_("Price need be greater than zero")) @api.model def create(self, vals): if self._context.get('wubook_action', True) and \ self.env['wubook'].is_valid_account(): pricelist_id = self.env['product.pricelist'].browse( - vals.get('pricelist_id')) - vroom = self.env['hotel.virtual.room'].search([ + vals.get('pricelist_id')) + vroom = self.env['hotel.room.type'].search([ ('product_id.product_tmpl_id', '=', vals.get('product_tmpl_id')), ('wrid', '!=', False) @@ -64,7 +47,7 @@ class ProductPricelistItem(models.Model): record.pricelist_id product_tmpl_id = vals.get('product_tmpl_id') or \ record.product_tmpl_id.id - vroom = self.env['hotel.virtual.room'].search([ + vroom = self.env['hotel.room.type'].search([ ('product_id.product_tmpl_id', '=', product_tmpl_id), ('wrid', '!=', False) ]) diff --git a/hotel_channel_connector/models/inherited_res_partner.py b/hotel_channel_connector/models/inherited_res_partner.py index 123e626b2..75afaecb9 100644 --- a/hotel_channel_connector/models/inherited_res_partner.py +++ b/hotel_channel_connector/models/inherited_res_partner.py @@ -1,23 +1,5 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# -# 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api diff --git a/hotel_channel_connector/models/inherited_reservation_restriction.py b/hotel_channel_connector/models/inherited_reservation_restriction.py index 40243f0aa..5462500e3 100644 --- a/hotel_channel_connector/models/inherited_reservation_restriction.py +++ b/hotel_channel_connector/models/inherited_reservation_restriction.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime, timedelta from openerp import models, fields, api, _ from openerp.exceptions import ValidationError @@ -49,7 +30,7 @@ class ReservationRestriction(models.Model): if not min_date or not max_date: return prices days_diff = abs((max_date - min_date).days) - vrooms = self.env['hotel.virtual.room'].search([ + vrooms = self.env['hotel.room.type'].search([ ('wrid', '!=', ''), ('wrid', '!=', False) ]) diff --git a/hotel_channel_connector/models/inherited_reservation_restriction_item.py b/hotel_channel_connector/models/inherited_reservation_restriction_item.py index 95710accf..f771907da 100644 --- a/hotel_channel_connector/models/inherited_reservation_restriction_item.py +++ b/hotel_channel_connector/models/inherited_reservation_restriction_item.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api diff --git a/hotel_channel_connector/models/inherited_virtual_room_availability.py b/hotel_channel_connector/models/inherited_virtual_room_availability.py index 036d0def3..576bef901 100644 --- a/hotel_channel_connector/models/inherited_virtual_room_availability.py +++ b/hotel_channel_connector/models/inherited_virtual_room_availability.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta from openerp import models, fields, api, _ from openerp.exceptions import ValidationError @@ -42,42 +23,45 @@ class VirtualRoomAvailability(models.Model): @api.constrains('avail') def _check_avail(self): - vroom_obj = self.env['hotel.virtual.room'] - cavail = len(vroom_obj.check_availability_virtual_room( - self.date, - self.date, - virtual_room_id=self.virtual_room_id.id)) - max_avail = min(cavail, - self.virtual_room_id.total_rooms_count) - if self.avail > max_avail: - self.env['wubook.issue'].sudo().create({ - 'section': 'avail', - 'message': _("The new availability can't be greater than \ - the actual availability \ - \n[%s]\nInput: %d\Limit: %d") % (self.virtual_room_id.name, - self.avail, - max_avail), - 'wid': self.virtual_room_id.wrid, - 'date_start': self.date, - 'date_end': self.date, - }) - # Auto-Fix wubook availability - date_dt = date_utils.get_datetime(self.date) - self.env['wubook'].update_availability([{ - 'id': self.virtual_room_id.wrid, - 'days': [{ - 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), - 'avail': max_avail, - }], - }]) + vroom_obj = self.env['hotel.room.type'] + issue_obj = self.env['wubook.issue'] + wubook_obj = self.env['wubook'] + for record in self: + cavail = len(vroom_obj.check_availability_virtual_room( + record.date, + record.date, + virtual_room_id=record.virtual_room_id.id)) + max_avail = min(cavail, record.virtual_room_id.total_rooms_count) + if record.avail > max_avail: + issue_obj.sudo().create({ + 'section': 'avail', + 'message': _("The new availability can't be greater than \ + the actual availability \ + \n[%s]\nInput: %d\Limit: %d") % (record.virtual_room_id.name, + record.avail, + record), + 'wid': record.virtual_room_id.wrid, + 'date_start': record.date, + 'date_end': record.date, + }) + # Auto-Fix wubook availability + date_dt = date_utils.get_datetime(record.date) + wubook_obj.update_availability([{ + 'id': record.virtual_room_id.wrid, + 'days': [{ + 'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), + 'avail': max_avail, + }], + }]) return super(VirtualRoomAvailability, self)._check_avail() @api.constrains('wmax_avail') def _check_wmax_avail(self): - if self.wmax_avail > self.virtual_room_id.total_rooms_count: - raise ValidationError(_("max avail for wubook can't be high \ - than toal rooms \ - count: %d") % self.virtual_room_id.total_rooms_count) + for record in self: + if record.wmax_avail > record.virtual_room_id.total_rooms_count: + raise ValidationError(_("max avail for wubook can't be high \ + than toal rooms \ + count: %d") % record.virtual_room_id.total_rooms_count) @api.onchange('virtual_room_id') def onchange_virtual_room_id(self): @@ -97,7 +81,7 @@ class VirtualRoomAvailability(models.Model): # Not count end day of the reservation date_diff = date_utils.date_diff(checkin, checkout, hours=False) - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] virtual_room_avail_obj = self.env['hotel.virtual.room.availability'] vrooms = vroom_obj.search([ diff --git a/hotel_channel_connector/models/res_config.py b/hotel_channel_connector/models/res_config.py index 6cda8dfff..479249564 100644 --- a/hotel_channel_connector/models/res_config.py +++ b/hotel_channel_connector/models/res_config.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import os import binascii import logging @@ -93,9 +74,9 @@ class WubookConfiguration(models.TransientModel): }) if wubook_obj.init_connection(): ir_seq_obj = self.env['ir.sequence'] - vrooms = self.env['hotel.virtual.room'].search([]) + vrooms = self.env['hotel.room.type'].search([]) for vroom in vrooms: - shortcode = ir_seq_obj.next_by_code('hotel.virtual.room')[:4] + shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4] wrid = wubook_obj.create_room( shortcode, vroom.name, diff --git a/hotel_channel_connector/models/wubook_channel_info.py b/hotel_channel_connector/models/wubook_channel_info.py index 9a6fe35ae..72830aa89 100644 --- a/hotel_channel_connector/models/wubook_channel_info.py +++ b/hotel_channel_connector/models/wubook_channel_info.py @@ -1,24 +1,5 @@ -# -*- 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 . -# -############################################################################## +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api diff --git a/hotel_channel_connector/static/src/js/wubook_listview_import_buttons.js b/hotel_channel_connector/static/src/js/wubook_listview_import_buttons.js index fbc78a968..bd27cead7 100644 --- a/hotel_channel_connector/static/src/js/wubook_listview_import_buttons.js +++ b/hotel_channel_connector/static/src/js/wubook_listview_import_buttons.js @@ -1,10 +1,9 @@ -odoo.define('wubook.listview_button_import_rooms', function(require) { +odoo.define('hotel_channel_connector.listview_button_import_rooms', function(require) { 'use strict'; /* - * Hotel WuBook + * Hotel Channel Connector * GNU Public License - * Aloxa Solucions S.L. - * Alexandre Díaz + * Alexandre Díaz */ var ListView = require('web.ListView'); @@ -148,7 +147,7 @@ function push_availability(){ new Model('wubook').call('push_availability', [false]).then(function(results){ self.do_notify(_t('Operation Success'), _t('Availability successfully pushed'), false); }).fail(function(){ - self.do_warn(_t('Operation Errors'), _t('Errors while pushing availability to WuBook. See issues log.'), true); + self.do_warn(_t('Operation Errors'), _t('Errors while pushing availability to Channel. See issues log.'), true); }); return false; @@ -158,30 +157,30 @@ ListView.include({ render_buttons: function () { this._super.apply(this, arguments); // Sets this.$buttons - if (this.dataset.model === 'hotel.virtual.room') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_rooms').on('click', import_rooms.bind(this)); + if (this.dataset.model === 'hotel.room.type') { + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_rooms').on('click', import_rooms.bind(this)); } else if (this.dataset.model === 'hotel.folio') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_reservations').on('click', import_reservations.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_reservations').on('click', import_reservations.bind(this)); } else if (this.dataset.model === 'product.pricelist') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_price_plans').on('click', import_price_plans.bind(this)); - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_push_price_plans').on('click', push_price_plans.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_price_plans').on('click', import_price_plans.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_push_price_plans').on('click', push_price_plans.bind(this)); } else if (this.dataset.model === 'wubook.channel.info') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_channels_info').on('click', import_channels_info.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_channels_info').on('click', import_channels_info.bind(this)); } else if (this.dataset.model === 'hotel.virtual.room.restriction') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_restriction_plans').on('click', import_restriction_plans.bind(this)); - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_push_restriction_plans').on('click', push_restriction_plans.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_restriction_plans').on('click', import_restriction_plans.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_push_restriction_plans').on('click', push_restriction_plans.bind(this)); } else if (this.dataset.model === 'hotel.virtual.room.availability') { - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_import_availability').on('click', import_availability.bind(this)); - this.$buttons.append(""); - this.$buttons.find('.oe_wubook_push_availability').on('click', push_availability.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_import_availability').on('click', import_availability.bind(this)); + this.$buttons.append(""); + this.$buttons.find('.oe_channel_connector_push_availability').on('click', push_availability.bind(this)); } } }); diff --git a/hotel_channel_connector/tests/common.py b/hotel_channel_connector/tests/common.py index 00859b10d..90d29b73b 100644 --- a/hotel_channel_connector/tests/common.py +++ b/hotel_channel_connector/tests/common.py @@ -45,12 +45,12 @@ class TestHotelWubook(TestHotel): return True @api.model - def wubook_create_wubook_issue(self, section, message, wmessage, + def wubook_create_channel_connector_issue(self, section, message, wmessage, wid=False, dfrom=False, dto=False): _logger.info("ISSUE CREATED:\n\t- %s\n\t--- %s", section, message) - cls.env['wubook']._patch_method('create_wubook_issue', - wubook_create_wubook_issue) + cls.env['wubook']._patch_method('create_channel_connector_issue', + wubook_create_channel_connector_issue) cls.env['wubook']._patch_method('is_valid_account', wubook_ommit) cls.env['wubook']._patch_method('initialize', wubook_ommit) cls.env['wubook']._patch_method('push_activation', wubook_ommit) @@ -123,7 +123,7 @@ class TestHotelWubook(TestHotel): rooms = [] rooms_occu = [] booked_rooms = [] - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] max_persons = 0 for k_room, v_room in rinfo.iteritems(): vroom = vroom_obj.search([ @@ -273,7 +273,7 @@ class TestHotelWubook(TestHotel): @classmethod def tearDownClass(cls): # Remove mocks - cls.env['wubook']._revert_method('create_wubook_issue') + cls.env['wubook']._revert_method('create_channel_connector_issue') cls.env['wubook']._revert_method('is_valid_account') cls.env['wubook']._revert_method('initialize') cls.env['wubook']._revert_method('push_activation') diff --git a/hotel_channel_connector/tests/test_hotel_virtual_room_model.py b/hotel_channel_connector/tests/test_hotel_virtual_room_model.py index 91eb66363..691ffeaac 100644 --- a/hotel_channel_connector/tests/test_hotel_virtual_room_model.py +++ b/hotel_channel_connector/tests/test_hotel_virtual_room_model.py @@ -60,7 +60,7 @@ class TestHotelVirtualRoom(TestHotelWubook): self.hotel_vroom_budget.sudo(self.user_hotel_manager).import_rooms() def test_create(self): - vroom_obj = self.env['hotel.virtual.room'] + vroom_obj = self.env['hotel.room.type'] vroom = vroom_obj.sudo(self.user_hotel_manager).create({ 'name': 'Budget Room', 'virtual_code': '001', diff --git a/hotel_channel_connector/views/inherited_hotel_virtual_room_views.xml b/hotel_channel_connector/views/inherited_hotel_virtual_room_views.xml index 64a54b3f0..0e8193b74 100644 --- a/hotel_channel_connector/views/inherited_hotel_virtual_room_views.xml +++ b/hotel_channel_connector/views/inherited_hotel_virtual_room_views.xml @@ -2,7 +2,7 @@ - hotel.virtual.room + hotel.room.type