From 52ff989dc4d33aace3f7232c8afbc46165c6f683 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 6 Nov 2018 00:56:22 +0100 Subject: [PATCH] [WIP] Channel Connector: Import Pricelist --- hotel_calendar/models/inherited_ir_default.py | 12 +- hotel_calendar/views/res_config_views.xml | 34 +++--- hotel_channel_connector/__manifest__.py | 3 +- .../components/backend_adapter.py | 4 +- .../models/channel_backend/common.py | 19 ++++ .../models/hotel_channel_connector_issue.py | 8 +- .../models/hotel_room_type/common.py | 8 +- .../models/hotel_room_type/importer.py | 2 +- .../hotel_room_type_availability/common.py | 14 ++- .../hotel_room_type_availability/exporter.py | 2 +- .../hotel_room_type_restriction/importer.py | 6 +- .../__init__.py | 1 + .../common.py | 48 +++++--- .../exporter.py | 107 ++++++++++++++++++ .../models/product_pricelist/__init__.py | 2 + .../models/product_pricelist/common.py | 93 +++++++-------- .../models/product_pricelist/exporter.py | 52 +++++++++ .../models/product_pricelist/importer.py | 65 +++++++++++ .../views/channel_connector_backend_views.xml | 18 +++ ...hotel_room_type_restriction_item_views.xml | 30 +++++ 20 files changed, 426 insertions(+), 102 deletions(-) create mode 100644 hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py create mode 100644 hotel_channel_connector/models/product_pricelist/exporter.py create mode 100644 hotel_channel_connector/models/product_pricelist/importer.py create mode 100644 hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml diff --git a/hotel_calendar/models/inherited_ir_default.py b/hotel_calendar/models/inherited_ir_default.py index 60bc254a0..ce5cf8f98 100644 --- a/hotel_calendar/models/inherited_ir_default.py +++ b/hotel_calendar/models/inherited_ir_default.py @@ -25,10 +25,10 @@ class IrDefault(models.Model): fixed_price = pitem.fixed_price room_type = room_type_obj.search([ ('product_id.product_tmpl_id', '=', product_tmpl_id), - ('date_start', '>=', fields.Date.today()) ], limit=1) - room_pr_cached_obj.create({ - 'room_type_id': room_type.id, - 'date': date_start, - 'price': fixed_price, - }) + if room_type: + room_pr_cached_obj.create({ + 'room_id': room_type.id, + 'date': date_start, + 'price': fixed_price, + }) diff --git a/hotel_calendar/views/res_config_views.xml b/hotel_calendar/views/res_config_views.xml index a3549f08b..0047838ad 100644 --- a/hotel_calendar/views/res_config_views.xml +++ b/hotel_calendar/views/res_config_views.xml @@ -8,24 +8,22 @@ - -
-

Calendar colors

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

Calendar colors

+
+
+ + + + + +
+
+ + + + +
diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 77b4d51f9..b478791b1 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -22,6 +22,7 @@ 'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_availability.xml', 'views/general.xml', + 'views/hotel_channel_connector_issue_views.xml', 'views/inherited_hotel_reservation_views.xml', 'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_type_availability_views.xml', @@ -32,11 +33,11 @@ 'views/inherited_hotel_room_type_restriction_item_views.xml', 'views/inherited_res_partner_views.xml', 'views/channel_ota_info_views.xml', - 'views/hotel_channel_connector_issue_views.xml', 'views/channel_hotel_reservation_views.xml', 'views/channel_hotel_room_type_views.xml', 'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_restriction_views.xml', + 'views/channel_hotel_room_type_restriction_item_views.xml', 'views/channel_product_pricelist_views.xml', 'views/channel_product_pricelist_item_views.xml', 'views/channel_connector_backend_views.xml', diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index 930b1dd92..07d2058ba 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -441,7 +441,7 @@ class WuBookAdapter(AbstractComponent): return results def update_plan_periods(self, channel_plan_id, periods): - rcode, results = self.SERVER.update_plan_periods( + rcode, results = self._server.update_plan_periods( self._session_info[0], self._session_info[1], channel_plan_id, @@ -454,7 +454,7 @@ class WuBookAdapter(AbstractComponent): return results def get_pricing_plans(self): - rcode, results = self.SERVER.get_pricing_plans( + rcode, results = self._server.get_pricing_plans( self._session_info[0], self._session_info[1]) if rcode != 0: diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 1901bfb36..32feb4c9a 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -39,6 +39,11 @@ class ChannelBackend(models.Model): restriction_id = fields.Many2one('channel.hotel.room.type.restriction', 'Channel Restriction') + pricelist_from = fields.Date('Pricelist From') + pricelist_to = fields.Date('Pricelist To') + pricelist_id = fields.Many2one('channel.product.pricelist', + 'Channel Product Pricelist') + issue_ids = fields.One2many('hotel.channel.connector.issue', 'backend_id', string='Issues', @@ -98,6 +103,20 @@ class ChannelBackend(models.Model): channel_hotel_restr_item_obj.import_restriction_values(backend) return True + @api.multi + def push_restriction(self): + channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item'] + for backend in self: + channel_hotel_restr_item_obj.push_restriction(backend) + return True + + @api.multi + def import_pricelist_plans(self): + channel_product_pricelist_obj = self.env['channel.product.pricelist'] + for backend in self: + channel_product_pricelist_obj.import_price_plans(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): diff --git a/hotel_channel_connector/models/hotel_channel_connector_issue.py b/hotel_channel_connector/models/hotel_channel_connector_issue.py index fba387289..e9cba78b0 100644 --- a/hotel_channel_connector/models/hotel_channel_connector_issue.py +++ b/hotel_channel_connector/models/hotel_channel_connector_issue.py @@ -11,6 +11,7 @@ class HotelChannelConnectorIssue(models.Model): backend_id = fields.Many2one('channel.backend', 'Restriction Plan', + required=True, ondelete='cascade', index=True) @@ -46,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model): reserv_ids.append(record.channel_object_id) record.to_read = False if any(reserv_ids): - res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids) - if not res: - raise ValidationError( - ("Can't mark reservation as readed in Channel!")) + with self.backend_id.work_on('channel.hotel.reservation') as work: + exporter = work.component(usage='hotel.reservation.exporter') + return exporter.mark_bookings(reserv_ids) @api.model def _needaction_domain_get(self): diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index ca490e8dd..fb2a84ec7 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -101,9 +101,7 @@ class HotelRoomType(models.Model): self._compute_capacity() @api.multi - def get_restrictions(self, date): - restriction_plan_id = int(self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_restrictions_id')) + def get_restrictions(self, date, restriction_plan_id): self.ensure_one() restriction = self.env['hotel.room.type.restriction.item'].search([ ('date', '=', date), @@ -141,7 +139,9 @@ class BindingHotelRoomTypeListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields: + if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \ + 'room_ids' in fields: + # FIXME: Supossed that only exists one channel connector per record record.channel_bind_ids[0].modify_room() # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) diff --git a/hotel_channel_connector/models/hotel_room_type/importer.py b/hotel_channel_connector/models/hotel_room_type/importer.py index 9a471dcd3..185ea9acf 100644 --- a/hotel_channel_connector/models/hotel_room_type/importer.py +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -165,7 +165,7 @@ class HotelRoomTypeImportMapper(Component): _apply_on = 'channel.hotel.room.type' direct = [ - ('id', 'externa_id'), + ('id', 'external_id'), ('shortname', 'channel_short_code'), ('occupancy', 'ota_capacity'), ('price', 'list_price'), diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 7210008e6..9a4f78e87 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -164,14 +164,22 @@ class BindingHotelRoomTypeAvailabilityListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'avail' in fields: - for binding in record.channel_bind_ids: - binding.channel_pushed = False + record.channel_bind_ids.write({'channel_pushed': False}) class ChannelBindingHotelRoomTypeAvailabilityListener(Component): _name = 'channel.binding.hotel.room.type.availability.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.availability'] + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if 'avail' in fields: + record.channel_pushed = False + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_fix_channel_availability(self, record, fields=None): - record.with_delay(priority=20).update_availability() + record.update_availability() diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py index 8eb17c2fc..589d7d27a 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -20,7 +20,7 @@ class HotelRoomTypeAvailabilityExporter(Component): if any(binding.room_type_id.channel_bind_ids): try: sday_dt = fields.Date.from_string(binding.date) - # Supossed that only exists one channel connector per record + # FIXME: Supossed that only exists one channel connector per record binding.channel_pushed = True return self.backend_adapter.update_availability({ 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py index a328904bf..ac687dbca 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -35,15 +35,15 @@ class HotelRoomTypeRestrictionImporter(Component): channel_restriction_obj.with_context({ 'wubook_action': False, 'rules': plan.get('rules'), - }).create(plan_record.values()) + }).create(plan_record.values(for_create=True)) else: plan_bind.with_context({'wubook_action': False}).write( - plan_record.values(for_create=True)) + plan_record.values()) count = count + 1 except ChannelConnectorError as err: self.create_issue( backend=self.backend_adapter.id, - section='rplan', + section='restriction', internal_message=_("Can't fetch restriction plans from wubook"), channel_message=err.data['message']) return count diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py index 06e54858b..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py @@ -3,3 +3,4 @@ from . import common from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py index c01c39197..5377b8cef 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -20,12 +20,6 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, old_name='wpushed') - @job(default_channel='root.channel') - @api.multi - def update_channel_pushed(self, status): - self.ensure_one() - self.channel_pushed = status - @job(default_channel='root.channel') @api.model def import_restriction_values(self, backend): @@ -36,6 +30,13 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): backend.restriction_to, channel_restr_id=backend.restriction_id) + @job(default_channel='root.channel') + @api.model + def push_restriction(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='hotel.room.type.restriction.item.exporter') + return exporter.push_restriction() + class HotelRoomTypeRestrictionItem(models.Model): _inherit = 'hotel.room.type.restriction.item' @@ -55,15 +56,34 @@ class HotelRoomTypeRestrictionItemAdapter(Component): date_to, channel_restriction_plan_id) -class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): - _name = 'channel.binding.hotel.room.type.restriction.item.listener' +class BindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'binding.hotel.room.type.restriction.item.listener' _inherit = 'base.connector.listener' - _apply_on = ['channel.hotel.room.type.restriction'] - - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_create(self, record, fields=None): - return True + _apply_on = ['hotel.room.type.restriction.item'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - return True + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_bind_ids.write({'channel_pushed': False}) + +class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'channel.binding.hotel.room.type.restriction.item.listener' + _inherit = 'base.connector.listener' + _apply_on = ['channel.hotel.room.type.restriction.item'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_pushed = False diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py new file mode 100644 index 000000000..b8788aaf1 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py @@ -0,0 +1,107 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + +class HotelRoomTypeRestrictionItemExporter(Component): + _name = 'channel.hotel.room.type.restriction.item.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type.restriction.item'] + _usage = 'hotel.room.type.restriction.item.exporter' + + @api.model + def update_restriction(self, binding): + if any(binding.restriction_id.channel_bind_ids): + try: + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_rplan_values( + binding.restriction_id.channel_bind_ids[0].external_id, + binding.date, + { + 'min_stay': binding.min_stay or 0, + 'min_stay_arrival': binding.min_stay_arrival or 0, + 'max_stay': binding.max_stay or 0, + 'max_stay_arrival': binding.max_stay_arrival or 0, + 'closed': binding.closed and 1 or 0, + 'closed_arrival': binding.closed_arrival and 1 or 0, + 'closed_departure': binding.closed_departure and 1 or 0, + }) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restriction in WuBook"), + channel_message=err.data['message']) + + @api.model + def push_restriction(self): + channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction'] + channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item'] + unpushed = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('date', '>=', fields.Date.today()) + ], order="date ASC") + if any(unpushed): + date_start = fields.Date.from_string(unpushed[0].date) + date_end = fields.Date.from_string(unpushed[-1].date) + days_diff = (date_end-date_start).days + 1 + restrictions = {} + channel_restr_plan_ids = channel_room_type_rest_obj.search([]) + for rp in channel_restr_plan_ids: + restrictions.update({rp.external_id: {}}) + unpushed_rp = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('restriction_id', '=', rp.odoo_id.id) + ]) + room_type_ids = unpushed_rp.mapped('room_type_id') + for room_type in room_type_ids: + if any(room_type.channel_bind_ids): + # FIXME: Supossed that only exists one channel connector per record + room_type_external_id = room_type.channel_bind_ids[0].external_id + restrictions[rp.external_id].update({ + room_type_external_id: [], + }) + for i in range(0, days_diff): + ndate_dt = date_start + timedelta(days=i) + restr = room_type.get_restrictions( + ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + rp.odoo_id.id) + if restr: + restrictions[rp.external_id][room_type_external_id].append({ + 'min_stay': restr.min_stay or 0, + 'min_stay_arrival': restr.min_stay_arrival or 0, + 'max_stay': restr.max_stay or 0, + 'max_stay_arrival': restr.max_stay_arrival or 0, + 'closed': restr.closed and 1 or 0, + 'closed_arrival': restr.closed_arrival and 1 or 0, + 'closed_departure': restr.closed_departure and 1 or 0, + }) + else: + restrictions[rp.external_id][room_type_external_id].append({}) + _logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==") + _logger.info(restrictions) + for k_res, v_res in restrictions.items(): + if any(v_res): + try: + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restrictions in WuBook"), + channel_message=err.data['message']) + unpushed.with_context({ + 'wubook_action': False}).write({'channel_pushed': True}) + return True diff --git a/hotel_channel_connector/models/product_pricelist/__init__.py b/hotel_channel_connector/models/product_pricelist/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/product_pricelist/__init__.py +++ b/hotel_channel_connector/models/product_pricelist/__init__.py @@ -2,3 +2,5 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/product_pricelist/common.py b/hotel_channel_connector/models/product_pricelist/common.py index 3430fcc87..775bd698a 100644 --- a/hotel_channel_connector/models/product_pricelist/common.py +++ b/hotel_channel_connector/models/product_pricelist/common.py @@ -17,7 +17,6 @@ class ChannelProductPricelist(models.Model): string='Pricelist', required=True, ondelete='cascade') - channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid') is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan') @job(default_channel='root.channel') @@ -25,61 +24,37 @@ class ChannelProductPricelist(models.Model): @api.multi def create_plan(self): self.ensure_one() - if self._context.get('channel_action', True): + if not self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - channel_plan_id = adapter.create_plan(self.name, - self.is_daily_plan and 1 or 0) - if channel_plan_id: - self.channel_plan_id = channel_plan_id - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't create plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.create_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def update_plan_name(self): self.ensure_one() - if self._context.get('channel_action', True): + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.update_plan_name( - self.channel_plan_id, - self.name) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't update plan name on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.rename_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def delete_plan(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.delete_plan(self.channel_plan_id) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't delete plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.delete_plan(self) @job(default_channel='root.channel') - @api.multi - def import_price_plans(self): - if self._context.get('channel_action', True): - with self.backend_id.work_on(self._name) as work: - importer = work.component(usage='channel.importer') - return importer.import_pricing_plans() + @api.model + def import_price_plans(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='product.pricelist.importer') + return importer.import_pricing_plans() class ProductPricelist(models.Model): _inherit = 'product.pricelist' @@ -92,19 +67,47 @@ class ProductPricelist(models.Model): @api.multi @api.depends('name') def name_get(self): - self.ensure_one() pricelist_obj = self.env['product.pricelist'] org_names = super(ProductPricelist, self).name_get() names = [] for name in org_names: priclist_id = pricelist_obj.browse(name[0]) if any(priclist_id.channel_bind_ids) and \ - priclist_id.channel_bind_ids[0].channel_plan_id: - names.append((name[0], '%s (Channel)' % name[1])) + priclist_id.channel_bind_ids[0].external_id: + names.append((name[0], '%s (%s Backend)' % ( + name[1], + priclist_id.channel_bind_ids[0].backend_id.name))) else: names.append((name[0], name[1])) return names +class ProductPricelistAdapter(Component): + _name = 'channel.product.pricelist.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.product.pricelist' + + def get_pricing_plans(self): + return super(ProductPricelistAdapter, self).get_pricing_plans() + + def create_plan(self, name): + return super(ProductPricelistAdapter, self).create_plan(name) + + def delete_plan(self, external_id): + return super(ProductPricelistAdapter, self).delete_plan(external_id) + + def rename_plan(self, external_id, new_name): + return super(ProductPricelistAdapter, self).rename_plan(external_id, new_name) + +class BindingProductPricelistListener(Component): + _name = 'binding.product.pricelist.listener' + _inherit = 'base.connector.listener' + _apply_on = ['product.pricelist'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if any(record.channel_bind_ids) and 'name' in fields: + record.channel_bind_ids[0].update_plan_name() + class ChannelBindingProductPricelistListener(Component): _name = 'channel.binding.product.pricelist.listener' _inherit = 'base.connector.listener' @@ -112,13 +115,13 @@ class ChannelBindingProductPricelistListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_create(self, record, fields=None): - record.with_delay(priority=20).create_plan() + record.create_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_unlink(self, record, fields=None): - record.with_delay(priority=20).delete_plan() + record.delete_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'name' in fields: - record.with_delay(priority=20).update_plan_name() + record.update_plan_name() diff --git a/hotel_channel_connector/models/product_pricelist/exporter.py b/hotel_channel_connector/models/product_pricelist/exporter.py new file mode 100644 index 000000000..45b028d84 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/exporter.py @@ -0,0 +1,52 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo import api, _ +_logger = logging.getLogger(__name__) + +class ProductPricelistExporter(Component): + _name = 'channel.product.pricelist.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.exporter' + + @api.model + def rename_plan(self, binding): + try: + return self.backend_adapter.rename_plan( + binding.external_id, + binding.name) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't modify pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def delete_plan(self, binding): + try: + return self.backend_adapter.delete_plan(binding.external_id) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't delete pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def create_plan(self, binding): + try: + external_id = self.backend_adapter.create_plan(binding.name) + binding.external_id = external_id + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't create pricelist plan in WuBook"), + channel_message=err.data['message']) + else: + self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/product_pricelist/importer.py b/hotel_channel_connector/models/product_pricelist/importer.py new file mode 100644 index 000000000..74dabe6e8 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/importer.py @@ -0,0 +1,65 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.connector.components.mapper import mapping, only_create +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + + +class ProductPricelistImporter(Component): + _name = 'channel.product.pricelist.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.importer' + + @api.model + def import_pricing_plans(self): + channel_product_listprice_obj = self.env['channel.product.pricelist'] + pricelist_mapper = self.component(usage='import.mapper', + model_name='channel.product.pricelist') + count = 0 + try: + results = self.backend_adapter.get_pricing_plans() + for plan in results: + if 'vpid' in plan: + continue # FIXME: Ignore Virtual Plans + plan_record = pricelist_mapper.map_record(plan) + plan_bind = channel_product_listprice_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_product_listprice_obj.with_context({ + 'wubook_action': False}).create(plan_record.values(for_create=True)) + else: + channel_product_listprice_obj.write(plan_record.values()) + count = count + 1 + except ChannelConnectorError as err: + self.create_issue( + section='pricelist', + internal_message=_("Can't get pricing plans from wubook"), + channel_message=err.data['message']) + return 0 + return count + + +class ProductPricelistMapper(Component): + _name = 'channel.product.pricelist.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.product.pricelist' + + direct = [ + ('id', 'external_id'), + ('name', 'name'), + ('daily', 'is_daily_plan'), + ] + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 3e2dcb8c5..2b947a5d0 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -116,6 +116,15 @@ string="Import in background"/>
+ + @@ -127,6 +136,15 @@ string="Export in background"/>
+ + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml new file mode 100644 index 000000000..da7933657 --- /dev/null +++ b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml @@ -0,0 +1,30 @@ + + + + + channel.hotel.room.type.restriction.item.form + channel.hotel.room.type.restriction.item + +
+ + + + + + + +
+
+
+ + + channel.hotel.room.type.restriction.item.tree + channel.hotel.room.type.restriction.item + + + + + + + +