From 44d3665a7b098352f199a5ce5519ac16cf14e291 Mon Sep 17 00:00:00 2001 From: QS5ELkMu Date: Tue, 9 Oct 2018 09:39:07 +0200 Subject: [PATCH 01/30] [IMP][MIG][11.0] hotel_calendar --- hotel_calendar/models/bus_hotel_calendar.py | 2 +- .../models/inherited_hotel_room_type_availability.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hotel_calendar/models/bus_hotel_calendar.py b/hotel_calendar/models/bus_hotel_calendar.py index 2cef55759..9314f95ed 100644 --- a/hotel_calendar/models/bus_hotel_calendar.py +++ b/hotel_calendar/models/bus_hotel_calendar.py @@ -110,7 +110,7 @@ class BusHotelCalendar(models.TransientModel): return { 'type': 'availability', 'availability': { - vals['room_id']: { + vals['room_type_id']: { date_dt.strftime("%d/%m/%Y"): [ vals['avail'], vals['no_ota'], diff --git a/hotel_calendar/models/inherited_hotel_room_type_availability.py b/hotel_calendar/models/inherited_hotel_room_type_availability.py index fa81bf137..4a001c4a9 100644 --- a/hotel_calendar/models/inherited_hotel_room_type_availability.py +++ b/hotel_calendar/models/inherited_hotel_room_type_availability.py @@ -8,7 +8,7 @@ class HotelRoomTypeAvailability(models.Model): @api.model def create(self, vals): - res = super(HotelVirtualRoomAvailability, self).create(vals) + res = super(HotelRoomTypeAvailability, self).create(vals) self.env['bus.hotel.calendar'].send_availability_notification({ 'date': res.date, 'avail': res.avail, @@ -20,7 +20,7 @@ class HotelRoomTypeAvailability(models.Model): @api.multi def write(self, vals): - ret_vals = super(HotelVirtualRoomAvailability, self).write(vals) + ret_vals = super(HotelRoomTypeAvailability, self).write(vals) bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for record in self: bus_hotel_calendar_obj.send_availability_notification({ @@ -44,7 +44,7 @@ class HotelRoomTypeAvailability(models.Model): 'no_ota': False, 'id': record.id, }) - res = super(HotelVirtualRoomAvailability, self).unlink() + res = super(HotelRoomTypeAvailability, self).unlink() bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] for uval in unlink_vals: bus_hotel_calendar_obj.send_availability_notification(uval) From 8d8defe92d2aef8bcad633f6fd1552fd7327c466 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Thu, 25 Oct 2018 13:40:11 +0200 Subject: [PATCH 02/30] [ADD] Hotel Room Type Class basic structure: model, views and relations --- hotel/__manifest__.py | 1 + hotel/models/__init__.py | 1 + hotel/models/hotel_room_type.py | 3 +- hotel/models/hotel_room_type_class.py | 24 +++++++++++ hotel/views/hotel_room_type.xml | 1 + hotel/views/hotel_room_type_class.xml | 59 +++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 hotel/models/hotel_room_type_class.py create mode 100644 hotel/views/hotel_room_type_class.xml diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 06a070f6b..f5e6d59fb 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -43,6 +43,7 @@ # 'views/hotel_service_line.xml', 'views/hotel_room_type.xml', 'views/hotel_room.xml', + 'views/hotel_room_type_class.xml', # 'views/hotel_service.xml', 'views/inherit_product_product.xml', 'views/hotel_room_amenities_type.xml', diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index 6b4eea4f4..9afd2028c 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -30,4 +30,5 @@ from . import inherit_product_pricelist from . import res_config from . import inherit_res_partner from . import inherited_mail_compose_message +from . import hotel_room_type_class #~ from . import hotel_dashboard diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 7dcd4abe0..5a2cdb122 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -5,7 +5,7 @@ from odoo import models, fields, api class HotelRoomType(models.Model): """ Before creating a 'room type', you need to consider the following: - With the term 'room type' is meant a type of residential accommodation: for + With the term 'room type' is meant a sales type of residential accommodation: for example, a Double Room, a Economic Room, an Apartment, a Tent, a Caravan... """ _name = "hotel.room.type" @@ -17,6 +17,7 @@ class HotelRoomType(models.Model): required=True, delegate=True, ondelete='cascade') room_ids = fields.One2many('hotel.room', 'room_type_id', 'Rooms') + class_id = fields.Many2one('hotel.room.type.class', 'Hotel Type Class') # TODO Hierarchical relationship for parent-child tree ? # parent_id = fields.Many2one ... diff --git a/hotel/models/hotel_room_type_class.py b/hotel/models/hotel_room_type_class.py new file mode 100644 index 000000000..281158a58 --- /dev/null +++ b/hotel/models/hotel_room_type_class.py @@ -0,0 +1,24 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields, api + +class HotelRoomTypeClass(models.Model): + """ Before creating a 'room type_class', you need to consider the following: + With the term 'room type class' is meant a physicial class of + residential accommodation: for example, a Room, a Bed, an Apartment, + a Tent, a Caravan... + """ + _name = "hotel.room.type.class" + _description = "Room Type Class" + _order = "sequence, code_class, name" + _sql_constraints = [('code_type_unique', 'unique(code_type)', + 'code must be unique!')] + + name = fields.Char('Class Name', required=True, translate=True) + room_type_ids = fields.One2many('hotel.room.type', 'class_id', 'Types') + active = fields.Boolean('Active', default=True, + help="The active field allows you to hide the \ + category without removing it.") + sequence = fields.Integer('Sequence', default=0) + code_class = fields.Char('Code') diff --git a/hotel/views/hotel_room_type.xml b/hotel/views/hotel_room_type.xml index 6bb542463..fdaeb9b8d 100644 --- a/hotel/views/hotel_room_type.xml +++ b/hotel/views/hotel_room_type.xml @@ -22,6 +22,7 @@ + diff --git a/hotel/views/hotel_room_type_class.xml b/hotel/views/hotel_room_type_class.xml new file mode 100644 index 000000000..4414b4f04 --- /dev/null +++ b/hotel/views/hotel_room_type_class.xml @@ -0,0 +1,59 @@ + + + + + + hotel.room.type.class.form + hotel.room.type.class + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+
+ + + + hotel.room.type.class.tree + hotel.room.type.class + + + + + + + + + + + + + Room Type Class + hotel.room.type.class + form + tree,form + + + +
From 544b82d022f2111ef01e319ceb061689758dd723 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Thu, 25 Oct 2018 20:26:41 +0200 Subject: [PATCH 03/30] [ADD] Hotel Room Closure Reasons --- hotel/__manifest__.py | 1 + hotel/models/__init__.py | 1 + hotel/models/hotel_folio.py | 1 + hotel/models/hotel_reservation.py | 1 + hotel/models/room_closure_reason.py | 11 ++++++++ hotel/views/hotel_folio.xml | 3 ++- hotel/views/hotel_reservation.xml | 14 ++++++++-- hotel/views/room_closure_reason.xml | 41 +++++++++++++++++++++++++++++ 8 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 hotel/models/room_closure_reason.py create mode 100644 hotel/views/room_closure_reason.xml diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index f5e6d59fb..b63ab57fa 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -51,6 +51,7 @@ 'views/hotel_room_type_restriction_views.xml', 'views/hotel_room_type_restriction_item_views.xml', 'views/hotel_reservation.xml', + 'views/room_closure_reason.xml', # 'views/room_type_views.xml', 'views/cardex.xml', 'views/hotel_room_type_availability.xml', diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index 9afd2028c..ed92091fb 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -31,4 +31,5 @@ from . import res_config from . import inherit_res_partner from . import inherited_mail_compose_message from . import hotel_room_type_class +from . import room_closure_reason #~ from . import hotel_dashboard diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 668106463..270b8acb4 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -48,6 +48,7 @@ class HotelFolio(models.Model): default=lambda self: _('New')) partner_id = fields.Many2one('res.partner', track_visibility='onchange') + closure_reason_id = fields.Many2one('room.closure.reason') # partner_invoice_id = fields.Many2one('res.partner', # string='Invoice Address', # readonly=True, required=True, diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 094fe6179..833d60fc1 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -159,6 +159,7 @@ class HotelReservation(models.Model): required=True, track_visibility='onchange') partner_id = fields.Many2one(related='folio_id.partner_id') + closure_reason_id = fields.Many2one(related='folio_id.closure_reason_id') company_id = fields.Many2one('res.company', 'Company') reservation_line_ids = fields.One2many('hotel.reservation.line', 'reservation_id', diff --git a/hotel/models/room_closure_reason.py b/hotel/models/room_closure_reason.py new file mode 100644 index 000000000..7964f2b46 --- /dev/null +++ b/hotel/models/room_closure_reason.py @@ -0,0 +1,11 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import models, fields, api, _ + + +class RoomClosureReason(models.Model): + _name = "room.closure.reason" + _description = "Cause of out of service" + + name = fields.Char('Name', required=True) + description = fields.Text('Description') diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio.xml index a3a3c73a7..3dac1349b 100644 --- a/hotel/views/hotel_folio.xml +++ b/hotel/views/hotel_folio.xml @@ -124,7 +124,8 @@

- + +

diff --git a/hotel/views/hotel_reservation.xml b/hotel/views/hotel_reservation.xml index 1e57866d8..96e04818b 100644 --- a/hotel/views/hotel_reservation.xml +++ b/hotel/views/hotel_reservation.xml @@ -161,9 +161,19 @@ style="margin-right: 30px;" required='1'/> - + + +

From

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

Calendar colors

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

Calendar colors

+
+
+ + + + + +
+
+ + + + +
diff --git a/hotel_channel_connector/__manifest__.py b/hotel_channel_connector/__manifest__.py index 77b4d51f9..b478791b1 100644 --- a/hotel_channel_connector/__manifest__.py +++ b/hotel_channel_connector/__manifest__.py @@ -22,6 +22,7 @@ 'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_availability.xml', 'views/general.xml', + 'views/hotel_channel_connector_issue_views.xml', 'views/inherited_hotel_reservation_views.xml', 'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_type_availability_views.xml', @@ -32,11 +33,11 @@ 'views/inherited_hotel_room_type_restriction_item_views.xml', 'views/inherited_res_partner_views.xml', 'views/channel_ota_info_views.xml', - 'views/hotel_channel_connector_issue_views.xml', 'views/channel_hotel_reservation_views.xml', 'views/channel_hotel_room_type_views.xml', 'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_restriction_views.xml', + 'views/channel_hotel_room_type_restriction_item_views.xml', 'views/channel_product_pricelist_views.xml', 'views/channel_product_pricelist_item_views.xml', 'views/channel_connector_backend_views.xml', diff --git a/hotel_channel_connector/components/backend_adapter.py b/hotel_channel_connector/components/backend_adapter.py index 930b1dd92..07d2058ba 100644 --- a/hotel_channel_connector/components/backend_adapter.py +++ b/hotel_channel_connector/components/backend_adapter.py @@ -441,7 +441,7 @@ class WuBookAdapter(AbstractComponent): return results def update_plan_periods(self, channel_plan_id, periods): - rcode, results = self.SERVER.update_plan_periods( + rcode, results = self._server.update_plan_periods( self._session_info[0], self._session_info[1], channel_plan_id, @@ -454,7 +454,7 @@ class WuBookAdapter(AbstractComponent): return results def get_pricing_plans(self): - rcode, results = self.SERVER.get_pricing_plans( + rcode, results = self._server.get_pricing_plans( self._session_info[0], self._session_info[1]) if rcode != 0: diff --git a/hotel_channel_connector/models/channel_backend/common.py b/hotel_channel_connector/models/channel_backend/common.py index 1901bfb36..32feb4c9a 100644 --- a/hotel_channel_connector/models/channel_backend/common.py +++ b/hotel_channel_connector/models/channel_backend/common.py @@ -39,6 +39,11 @@ class ChannelBackend(models.Model): restriction_id = fields.Many2one('channel.hotel.room.type.restriction', 'Channel Restriction') + pricelist_from = fields.Date('Pricelist From') + pricelist_to = fields.Date('Pricelist To') + pricelist_id = fields.Many2one('channel.product.pricelist', + 'Channel Product Pricelist') + issue_ids = fields.One2many('hotel.channel.connector.issue', 'backend_id', string='Issues', @@ -98,6 +103,20 @@ class ChannelBackend(models.Model): channel_hotel_restr_item_obj.import_restriction_values(backend) return True + @api.multi + def push_restriction(self): + channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item'] + for backend in self: + channel_hotel_restr_item_obj.push_restriction(backend) + return True + + @api.multi + def import_pricelist_plans(self): + channel_product_pricelist_obj = self.env['channel.product.pricelist'] + for backend in self: + channel_product_pricelist_obj.import_price_plans(backend) + return True + @contextmanager @api.multi def work_on(self, model_name, **kwargs): diff --git a/hotel_channel_connector/models/hotel_channel_connector_issue.py b/hotel_channel_connector/models/hotel_channel_connector_issue.py index fba387289..e9cba78b0 100644 --- a/hotel_channel_connector/models/hotel_channel_connector_issue.py +++ b/hotel_channel_connector/models/hotel_channel_connector_issue.py @@ -11,6 +11,7 @@ class HotelChannelConnectorIssue(models.Model): backend_id = fields.Many2one('channel.backend', 'Restriction Plan', + required=True, ondelete='cascade', index=True) @@ -46,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model): reserv_ids.append(record.channel_object_id) record.to_read = False if any(reserv_ids): - res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids) - if not res: - raise ValidationError( - ("Can't mark reservation as readed in Channel!")) + with self.backend_id.work_on('channel.hotel.reservation') as work: + exporter = work.component(usage='hotel.reservation.exporter') + return exporter.mark_bookings(reserv_ids) @api.model def _needaction_domain_get(self): diff --git a/hotel_channel_connector/models/hotel_room_type/common.py b/hotel_channel_connector/models/hotel_room_type/common.py index ca490e8dd..fb2a84ec7 100644 --- a/hotel_channel_connector/models/hotel_room_type/common.py +++ b/hotel_channel_connector/models/hotel_room_type/common.py @@ -101,9 +101,7 @@ class HotelRoomType(models.Model): self._compute_capacity() @api.multi - def get_restrictions(self, date): - restriction_plan_id = int(self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_restrictions_id')) + def get_restrictions(self, date, restriction_plan_id): self.ensure_one() restriction = self.env['hotel.room.type.restriction.item'].search([ ('date', '=', date), @@ -141,7 +139,9 @@ class BindingHotelRoomTypeListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields: + if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \ + 'room_ids' in fields: + # FIXME: Supossed that only exists one channel connector per record record.channel_bind_ids[0].modify_room() # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) diff --git a/hotel_channel_connector/models/hotel_room_type/importer.py b/hotel_channel_connector/models/hotel_room_type/importer.py index 9a471dcd3..185ea9acf 100644 --- a/hotel_channel_connector/models/hotel_room_type/importer.py +++ b/hotel_channel_connector/models/hotel_room_type/importer.py @@ -165,7 +165,7 @@ class HotelRoomTypeImportMapper(Component): _apply_on = 'channel.hotel.room.type' direct = [ - ('id', 'externa_id'), + ('id', 'external_id'), ('shortname', 'channel_short_code'), ('occupancy', 'ota_capacity'), ('price', 'list_price'), diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index 7210008e6..9a4f78e87 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -164,14 +164,22 @@ class BindingHotelRoomTypeAvailabilityListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'avail' in fields: - for binding in record.channel_bind_ids: - binding.channel_pushed = False + record.channel_bind_ids.write({'channel_pushed': False}) class ChannelBindingHotelRoomTypeAvailabilityListener(Component): _name = 'channel.binding.hotel.room.type.availability.listener' _inherit = 'base.connector.listener' _apply_on = ['channel.hotel.room.type.availability'] + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if 'avail' in fields: + record.channel_pushed = False + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_fix_channel_availability(self, record, fields=None): - record.with_delay(priority=20).update_availability() + record.update_availability() diff --git a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py index 8eb17c2fc..589d7d27a 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/exporter.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/exporter.py @@ -20,7 +20,7 @@ class HotelRoomTypeAvailabilityExporter(Component): if any(binding.room_type_id.channel_bind_ids): try: sday_dt = fields.Date.from_string(binding.date) - # Supossed that only exists one channel connector per record + # FIXME: Supossed that only exists one channel connector per record binding.channel_pushed = True return self.backend_adapter.update_availability({ 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, diff --git a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py index a328904bf..ac687dbca 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction/importer.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction/importer.py @@ -35,15 +35,15 @@ class HotelRoomTypeRestrictionImporter(Component): channel_restriction_obj.with_context({ 'wubook_action': False, 'rules': plan.get('rules'), - }).create(plan_record.values()) + }).create(plan_record.values(for_create=True)) else: plan_bind.with_context({'wubook_action': False}).write( - plan_record.values(for_create=True)) + plan_record.values()) count = count + 1 except ChannelConnectorError as err: self.create_issue( backend=self.backend_adapter.id, - section='rplan', + section='restriction', internal_message=_("Can't fetch restriction plans from wubook"), channel_message=err.data['message']) return count diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py index 06e54858b..fe02f8e98 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/__init__.py @@ -3,3 +3,4 @@ from . import common from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py index c01c39197..5377b8cef 100644 --- a/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/common.py @@ -20,12 +20,6 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, old_name='wpushed') - @job(default_channel='root.channel') - @api.multi - def update_channel_pushed(self, status): - self.ensure_one() - self.channel_pushed = status - @job(default_channel='root.channel') @api.model def import_restriction_values(self, backend): @@ -36,6 +30,13 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model): backend.restriction_to, channel_restr_id=backend.restriction_id) + @job(default_channel='root.channel') + @api.model + def push_restriction(self, backend): + with backend.work_on(self._name) as work: + exporter = work.component(usage='hotel.room.type.restriction.item.exporter') + return exporter.push_restriction() + class HotelRoomTypeRestrictionItem(models.Model): _inherit = 'hotel.room.type.restriction.item' @@ -55,15 +56,34 @@ class HotelRoomTypeRestrictionItemAdapter(Component): date_to, channel_restriction_plan_id) -class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): - _name = 'channel.binding.hotel.room.type.restriction.item.listener' +class BindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'binding.hotel.room.type.restriction.item.listener' _inherit = 'base.connector.listener' - _apply_on = ['channel.hotel.room.type.restriction'] - - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) - def on_record_create(self, record, fields=None): - return True + _apply_on = ['hotel.room.type.restriction.item'] @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - return True + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_bind_ids.write({'channel_pushed': False}) + +class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): + _name = 'channel.binding.hotel.room.type.restriction.item.listener' + _inherit = 'base.connector.listener' + _apply_on = ['channel.hotel.room.type.restriction.item'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + record.channel_pushed = False + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival', + 'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival', + 'date') + fields_checked = [elm for elm in fields_to_check if elm in fields] + if any(fields_checked): + record.channel_pushed = False diff --git a/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py new file mode 100644 index 000000000..b8788aaf1 --- /dev/null +++ b/hotel_channel_connector/models/hotel_room_type_restriction_item/exporter.py @@ -0,0 +1,107 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + +class HotelRoomTypeRestrictionItemExporter(Component): + _name = 'channel.hotel.room.type.restriction.item.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.hotel.room.type.restriction.item'] + _usage = 'hotel.room.type.restriction.item.exporter' + + @api.model + def update_restriction(self, binding): + if any(binding.restriction_id.channel_bind_ids): + try: + # FIXME: Supossed that only exists one channel connector per record + binding.channel_pushed = True + return self.backend_adapter.update_rplan_values( + binding.restriction_id.channel_bind_ids[0].external_id, + binding.date, + { + 'min_stay': binding.min_stay or 0, + 'min_stay_arrival': binding.min_stay_arrival or 0, + 'max_stay': binding.max_stay or 0, + 'max_stay_arrival': binding.max_stay_arrival or 0, + 'closed': binding.closed and 1 or 0, + 'closed_arrival': binding.closed_arrival and 1 or 0, + 'closed_departure': binding.closed_departure and 1 or 0, + }) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restriction in WuBook"), + channel_message=err.data['message']) + + @api.model + def push_restriction(self): + channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction'] + channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item'] + unpushed = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('date', '>=', fields.Date.today()) + ], order="date ASC") + if any(unpushed): + date_start = fields.Date.from_string(unpushed[0].date) + date_end = fields.Date.from_string(unpushed[-1].date) + days_diff = (date_end-date_start).days + 1 + restrictions = {} + channel_restr_plan_ids = channel_room_type_rest_obj.search([]) + for rp in channel_restr_plan_ids: + restrictions.update({rp.external_id: {}}) + unpushed_rp = channel_rest_item_obj.search([ + ('channel_pushed', '=', False), + ('restriction_id', '=', rp.odoo_id.id) + ]) + room_type_ids = unpushed_rp.mapped('room_type_id') + for room_type in room_type_ids: + if any(room_type.channel_bind_ids): + # FIXME: Supossed that only exists one channel connector per record + room_type_external_id = room_type.channel_bind_ids[0].external_id + restrictions[rp.external_id].update({ + room_type_external_id: [], + }) + for i in range(0, days_diff): + ndate_dt = date_start + timedelta(days=i) + restr = room_type.get_restrictions( + ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), + rp.odoo_id.id) + if restr: + restrictions[rp.external_id][room_type_external_id].append({ + 'min_stay': restr.min_stay or 0, + 'min_stay_arrival': restr.min_stay_arrival or 0, + 'max_stay': restr.max_stay or 0, + 'max_stay_arrival': restr.max_stay_arrival or 0, + 'closed': restr.closed and 1 or 0, + 'closed_arrival': restr.closed_arrival and 1 or 0, + 'closed_departure': restr.closed_departure and 1 or 0, + }) + else: + restrictions[rp.external_id][room_type_external_id].append({}) + _logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==") + _logger.info(restrictions) + for k_res, v_res in restrictions.items(): + if any(v_res): + try: + self.backend_adapter.update_rplan_values( + int(k_res), + date_start.strftime(DEFAULT_SERVER_DATE_FORMAT), + v_res) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't update restrictions in WuBook"), + channel_message=err.data['message']) + unpushed.with_context({ + 'wubook_action': False}).write({'channel_pushed': True}) + return True diff --git a/hotel_channel_connector/models/product_pricelist/__init__.py b/hotel_channel_connector/models/product_pricelist/__init__.py index 257ab04fc..fe02f8e98 100644 --- a/hotel_channel_connector/models/product_pricelist/__init__.py +++ b/hotel_channel_connector/models/product_pricelist/__init__.py @@ -2,3 +2,5 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import common +from . import importer +from . import exporter diff --git a/hotel_channel_connector/models/product_pricelist/common.py b/hotel_channel_connector/models/product_pricelist/common.py index 3430fcc87..775bd698a 100644 --- a/hotel_channel_connector/models/product_pricelist/common.py +++ b/hotel_channel_connector/models/product_pricelist/common.py @@ -17,7 +17,6 @@ class ChannelProductPricelist(models.Model): string='Pricelist', required=True, ondelete='cascade') - channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid') is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan') @job(default_channel='root.channel') @@ -25,61 +24,37 @@ class ChannelProductPricelist(models.Model): @api.multi def create_plan(self): self.ensure_one() - if self._context.get('channel_action', True): + if not self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - channel_plan_id = adapter.create_plan(self.name, - self.is_daily_plan and 1 or 0) - if channel_plan_id: - self.channel_plan_id = channel_plan_id - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't create plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.create_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def update_plan_name(self): self.ensure_one() - if self._context.get('channel_action', True): + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.update_plan_name( - self.channel_plan_id, - self.name) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't update plan name on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.rename_plan(self) @job(default_channel='root.channel') @related_action(action='related_action_unwrap_binding') @api.multi def delete_plan(self): self.ensure_one() - if self._context.get('channel_action', True) and self.channel_room_id: + if self.external_id: with self.backend_id.work_on(self._name) as work: - adapter = work.component(usage='backend.adapter') - try: - adapter.delete_plan(self.channel_plan_id) - except ValidationError as e: - self.create_issue( - backend=self.backend_adapter.id, - section='room', - internal_message="Can't delete plan on channel") + exporter = work.component(usage='product.pricelist.exporter') + exporter.delete_plan(self) @job(default_channel='root.channel') - @api.multi - def import_price_plans(self): - if self._context.get('channel_action', True): - with self.backend_id.work_on(self._name) as work: - importer = work.component(usage='channel.importer') - return importer.import_pricing_plans() + @api.model + def import_price_plans(self, backend): + with backend.work_on(self._name) as work: + importer = work.component(usage='product.pricelist.importer') + return importer.import_pricing_plans() class ProductPricelist(models.Model): _inherit = 'product.pricelist' @@ -92,19 +67,47 @@ class ProductPricelist(models.Model): @api.multi @api.depends('name') def name_get(self): - self.ensure_one() pricelist_obj = self.env['product.pricelist'] org_names = super(ProductPricelist, self).name_get() names = [] for name in org_names: priclist_id = pricelist_obj.browse(name[0]) if any(priclist_id.channel_bind_ids) and \ - priclist_id.channel_bind_ids[0].channel_plan_id: - names.append((name[0], '%s (Channel)' % name[1])) + priclist_id.channel_bind_ids[0].external_id: + names.append((name[0], '%s (%s Backend)' % ( + name[1], + priclist_id.channel_bind_ids[0].backend_id.name))) else: names.append((name[0], name[1])) return names +class ProductPricelistAdapter(Component): + _name = 'channel.product.pricelist.adapter' + _inherit = 'wubook.adapter' + _apply_on = 'channel.product.pricelist' + + def get_pricing_plans(self): + return super(ProductPricelistAdapter, self).get_pricing_plans() + + def create_plan(self, name): + return super(ProductPricelistAdapter, self).create_plan(name) + + def delete_plan(self, external_id): + return super(ProductPricelistAdapter, self).delete_plan(external_id) + + def rename_plan(self, external_id, new_name): + return super(ProductPricelistAdapter, self).rename_plan(external_id, new_name) + +class BindingProductPricelistListener(Component): + _name = 'binding.product.pricelist.listener' + _inherit = 'base.connector.listener' + _apply_on = ['product.pricelist'] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + if any(record.channel_bind_ids) and 'name' in fields: + record.channel_bind_ids[0].update_plan_name() + class ChannelBindingProductPricelistListener(Component): _name = 'channel.binding.product.pricelist.listener' _inherit = 'base.connector.listener' @@ -112,13 +115,13 @@ class ChannelBindingProductPricelistListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_create(self, record, fields=None): - record.with_delay(priority=20).create_plan() + record.create_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_unlink(self, record, fields=None): - record.with_delay(priority=20).delete_plan() + record.delete_plan() @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): if 'name' in fields: - record.with_delay(priority=20).update_plan_name() + record.update_plan_name() diff --git a/hotel_channel_connector/models/product_pricelist/exporter.py b/hotel_channel_connector/models/product_pricelist/exporter.py new file mode 100644 index 000000000..45b028d84 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/exporter.py @@ -0,0 +1,52 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo import api, _ +_logger = logging.getLogger(__name__) + +class ProductPricelistExporter(Component): + _name = 'channel.product.pricelist.exporter' + _inherit = 'hotel.channel.exporter' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.exporter' + + @api.model + def rename_plan(self, binding): + try: + return self.backend_adapter.rename_plan( + binding.external_id, + binding.name) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't modify pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def delete_plan(self, binding): + try: + return self.backend_adapter.delete_plan(binding.external_id) + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't delete pricelist plan in WuBook"), + channel_message=err.data['message']) + + @api.model + def create_plan(self, binding): + try: + external_id = self.backend_adapter.create_plan(binding.name) + binding.external_id = external_id + except ChannelConnectorError as err: + self.create_issue( + backend=self.backend_adapter.id, + section='restriction', + internal_message=_("Can't create pricelist plan in WuBook"), + channel_message=err.data['message']) + else: + self.binder.bind(external_id, binding) diff --git a/hotel_channel_connector/models/product_pricelist/importer.py b/hotel_channel_connector/models/product_pricelist/importer.py new file mode 100644 index 000000000..74dabe6e8 --- /dev/null +++ b/hotel_channel_connector/models/product_pricelist/importer.py @@ -0,0 +1,65 @@ +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +from odoo.addons.component.core import Component +from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError +from odoo.addons.connector.components.mapper import mapping, only_create +from odoo.addons.hotel_channel_connector.components.backend_adapter import ( + DEFAULT_WUBOOK_DATE_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import fields, api, _ +_logger = logging.getLogger(__name__) + + +class ProductPricelistImporter(Component): + _name = 'channel.product.pricelist.importer' + _inherit = 'hotel.channel.importer' + _apply_on = ['channel.product.pricelist'] + _usage = 'product.pricelist.importer' + + @api.model + def import_pricing_plans(self): + channel_product_listprice_obj = self.env['channel.product.pricelist'] + pricelist_mapper = self.component(usage='import.mapper', + model_name='channel.product.pricelist') + count = 0 + try: + results = self.backend_adapter.get_pricing_plans() + for plan in results: + if 'vpid' in plan: + continue # FIXME: Ignore Virtual Plans + plan_record = pricelist_mapper.map_record(plan) + plan_bind = channel_product_listprice_obj.search([ + ('external_id', '=', str(plan['id'])) + ], limit=1) + if not plan_bind: + channel_product_listprice_obj.with_context({ + 'wubook_action': False}).create(plan_record.values(for_create=True)) + else: + channel_product_listprice_obj.write(plan_record.values()) + count = count + 1 + except ChannelConnectorError as err: + self.create_issue( + section='pricelist', + internal_message=_("Can't get pricing plans from wubook"), + channel_message=err.data['message']) + return 0 + return count + + +class ProductPricelistMapper(Component): + _name = 'channel.product.pricelist.import.mapper' + _inherit = 'channel.import.mapper' + _apply_on = 'channel.product.pricelist' + + direct = [ + ('id', 'external_id'), + ('name', 'name'), + ('daily', 'is_daily_plan'), + ] + + @mapping + def backend_id(self, record): + return {'backend_id': self.backend_record.id} diff --git a/hotel_channel_connector/views/channel_connector_backend_views.xml b/hotel_channel_connector/views/channel_connector_backend_views.xml index 3e2dcb8c5..2b947a5d0 100644 --- a/hotel_channel_connector/views/channel_connector_backend_views.xml +++ b/hotel_channel_connector/views/channel_connector_backend_views.xml @@ -116,6 +116,15 @@ string="Import in background"/>
+ + @@ -127,6 +136,15 @@ string="Export in background"/>
+ + diff --git a/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml new file mode 100644 index 000000000..da7933657 --- /dev/null +++ b/hotel_channel_connector/views/channel_hotel_room_type_restriction_item_views.xml @@ -0,0 +1,30 @@ + + + + + channel.hotel.room.type.restriction.item.form + channel.hotel.room.type.restriction.item + +
+ + + + + + + +
+
+
+ + + channel.hotel.room.type.restriction.item.tree + channel.hotel.room.type.restriction.item + + + + + + + +
From d485968d78446800c2be4f741b94ec0b1fd7d528 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Tue, 6 Nov 2018 10:18:23 +0100 Subject: [PATCH 09/30] [DEV] Board Service, Wizard refact, hotes services configuration, services lines by day --- hotel/__manifest__.py | 5 +- hotel/models/__init__.py | 4 +- hotel/models/hotel_board_service.py | 15 ++ hotel/models/hotel_folio.py | 20 ++- hotel/models/hotel_reservation.py | 27 ++-- hotel/models/hotel_service.py | 140 +++++++++++++----- hotel/models/hotel_service_line.py | 32 ++++ hotel/models/inherit_product_pricelist.py | 5 +- hotel/models/inherit_product_product.py | 12 -- hotel/models/inherit_product_template.py | 13 ++ hotel/models/inherit_res_partner.py | 1 - ...open_reservation_wizard_listview_button.js | 28 ++++ hotel/views/general.xml | 11 ++ hotel/views/hotel_board_service.xml | 48 ++++++ hotel/views/hotel_folio.xml | 4 +- hotel/views/hotel_reservation.xml | 7 +- hotel/views/inherit_product_product.xml | 15 -- hotel/views/inherit_product_template.xml | 26 ++++ hotel/wizard/__init__.py | 1 + hotel/wizard/hotel_wizard.xml | 4 +- .../wizard/wizard_reservation.py | 0 .../wizard/wizard_reservation.xml | 0 hotel_calendar/__init__.py | 1 - hotel_calendar/__manifest__.py | 1 - .../static/src/xml/hotel_calendar_view.xml | 2 +- hotel_calendar/views/general.xml | 2 - hotel_calendar/wizard/__init__.py | 1 - 27 files changed, 326 insertions(+), 99 deletions(-) create mode 100644 hotel/models/hotel_board_service.py create mode 100644 hotel/models/hotel_service_line.py delete mode 100644 hotel/models/inherit_product_product.py create mode 100644 hotel/models/inherit_product_template.py create mode 100644 hotel/static/src/js/open_reservation_wizard_listview_button.js create mode 100644 hotel/views/general.xml create mode 100644 hotel/views/hotel_board_service.xml delete mode 100644 hotel/views/inherit_product_product.xml create mode 100644 hotel/views/inherit_product_template.xml rename {hotel_calendar => hotel}/wizard/wizard_reservation.py (100%) rename {hotel_calendar => hotel}/wizard/wizard_reservation.xml (100%) delete mode 100644 hotel_calendar/wizard/__init__.py diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index b63ab57fa..b1bcb0e22 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -44,14 +44,16 @@ 'views/hotel_room_type.xml', 'views/hotel_room.xml', 'views/hotel_room_type_class.xml', + 'views/general.xml', # 'views/hotel_service.xml', - 'views/inherit_product_product.xml', + 'views/inherit_product_template.xml', 'views/hotel_room_amenities_type.xml', 'views/hotel_room_amenities.xml', 'views/hotel_room_type_restriction_views.xml', 'views/hotel_room_type_restriction_item_views.xml', 'views/hotel_reservation.xml', 'views/room_closure_reason.xml', + 'views/hotel_board_service.xml', # 'views/room_type_views.xml', 'views/cardex.xml', 'views/hotel_room_type_availability.xml', @@ -61,6 +63,7 @@ 'data/email_template_cancel.xml', 'data/email_template_reserv.xml', 'data/email_template_exit.xml', + 'wizard/wizard_reservation.xml', ], 'css': ['static/src/css/room_kanban.css'], 'auto_install': False, diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index abf50a070..5cab9a32f 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -15,7 +15,7 @@ from . import hotel_room_type from . import hotel_service from . import inherit_account_invoice # from . import inherit_product_category -from . import inherit_product_product +from . import inherit_product_template from . import inherit_res_company # from . import room_type from . import inherit_account_payment @@ -30,4 +30,6 @@ from . import inherit_res_partner from . import inherited_mail_compose_message from . import hotel_room_type_class from . import room_closure_reason +from . import hotel_service_line +from . import hotel_board_service #~ from . import hotel_dashboard diff --git a/hotel/models/hotel_board_service.py b/hotel/models/hotel_board_service.py new file mode 100644 index 000000000..94accdfcc --- /dev/null +++ b/hotel/models/hotel_board_service.py @@ -0,0 +1,15 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import models, fields, api, _ + + +class HotelBoardService(models.Model): + _name = "hotel.board.service" + _description = "Board Services" + + name = fields.Char('Board Name', size=64, required=True, index=True) + service_ids = fields.Many2many(comodel_name='product.template', + relation='board_services_room', + column1='board_id', + column2='service_id') + sequence = fields.Integer('Sequence', size=64) diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 6285b4363..a7ffd4ced 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -353,13 +353,12 @@ class HotelFolio(models.Model): #~ }) return addr = self.partner_id.address_get(['invoice']) - values = {'user_id': self.partner_id.user_id.id or self.env.uid, - 'pricelist_id':self.partner_id.property_product_pricelist and \ + pricelist = self.partner_id.property_product_pricelist and \ self.partner_id.property_product_pricelist.id or \ - self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id'), - 'reservation_type': self.env['hotel.folio'].calcule_reservation_type( - self.partner_id.is_staff, - self.reservation_type)} + self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id') + values = {'user_id': self.partner_id.user_id.id or self.env.uid, + 'pricelist_id': pricelist + } if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \ self.env.user.company_id.sale_note: values['note'] = self.with_context( @@ -369,6 +368,15 @@ class HotelFolio(models.Model): values['team_id'] = self.partner_id.team_id.id self.update(values) + @api.multi + @api.onchange('pricelist_id') + def onchange_pricelist_id(self): + values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type( + self.pricelist_id.is_staff, + self.reservation_type)} + self.update(values) + + @api.model def calcule_reservation_type(self, is_staff, current_type): if current_type == 'out': diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 6fa883e69..f1974b24a 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -196,6 +196,7 @@ class HotelReservation(models.Model): parent_reservation = fields.Many2one('hotel.reservation', 'Parent Reservation') overbooking = fields.Boolean('Is Overbooking', default=False) + reselling = fields.Boolean('Is Reselling', default=False) nights = fields.Integer('Nights', compute='_computed_nights', store=True) channel_type = fields.Selection([ @@ -408,6 +409,7 @@ class HotelReservation(models.Model): 'parent_reservation': self.parent_reservation.id, 'state': self.state, 'overbooking': self.overbooking, + 'reselling': self.reselling, 'price_unit': self.price_unit, 'splitted': self.splitted, # 'room_type_id': self.room_type_id.id, @@ -444,16 +446,22 @@ class HotelReservation(models.Model): @api.onchange('partner_id') def onchange_partner_id(self): #TODO: Change parity pricelist by default pricelist - values = { - 'pricelist_id': self.partner_id.property_product_pricelist and \ + pricelist = self.partner_id.property_product_pricelist and \ self.partner_id.property_product_pricelist.id or \ - self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id'), - 'reservation_type': self.env['hotel.folio'].calcule_reservation_type( - self.partner_id.is_staff, - self.reservation_type) + self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id') + values = { + 'pricelist_id': pricelist, } self.update(values) + @api.multi + @api.onchange('pricelist_id') + def onchange_pricelist_id(self): + values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type( + self.pricelist_id.is_staff, + self.reservation_type)} + self.update(values) + @api.onchange('reservation_type') def assign_partner_company_on_out_service(self): if self.reservation_type == 'out': @@ -507,7 +515,7 @@ class HotelReservation(models.Model): def onchange_room_availabiltiy_domain(self): self.ensure_one() if self.checkin and self.checkout: - if self.overbooking: + if self.overbooking or self.reselling: return occupied = self.env['hotel.reservation'].get_reservations( self.checkin, @@ -842,7 +850,8 @@ class HotelReservation(models.Model): domain = [('reservation_line_ids.date', '>=', dfrom), ('reservation_line_ids.date', '<', dto), ('state', '!=', 'cancelled'), - ('overbooking', '=', False)] + ('overbooking', '=', False), + ('reselling', '=', False),] return domain @api.model @@ -872,7 +881,7 @@ class HotelReservation(models.Model): return reservations_dates # TODO: Use default values on checkin /checkout is empty - @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') + @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking', 'reselling') def check_dates(self): """ 1.-When date_order is less then checkin date or diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index 7352702db..a58f523c2 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -10,18 +10,6 @@ _logger = logging.getLogger(__name__) class HotelService(models.Model): _name = 'hotel.service' _description = 'Hotel Services and its charges' - - @api.model - def _service_checkin(self): - if 'checkin' in self._context: - return self._context['checkin'] - return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - - @api.model - def _service_checkout(self): - if 'checkout' in self._context: - return self._context['checkout'] - return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) @api.model def _default_ser_room_line(self): @@ -33,42 +21,116 @@ class HotelService(models.Model): return False name = fields.Char('Service description') - # services in the hotel are products - product_id = fields.Many2one('product.product', 'Service', required=True) - - folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade') - + product_id = fields.Many2one('product.product', 'Service', + required=True) + folio_id = fields.Many2one('hotel.folio', 'Folio', + ondelete='cascade') ser_room_line = fields.Many2one('hotel.reservation', 'Room', default=_default_ser_room_line) - - list_price = fields.Float( - related='product_id.list_price') - + service_line_ids = fields.One2many('hotel.service.line', + 'service_id') + product_qty = fields.Integer('Quantity') + pricelist_id = fields.Many2one( + related='folio_id.pricelist_id') channel_type = fields.Selection([ ('door', 'Door'), ('mail', 'Mail'), ('phone', 'Phone'), ('call', 'Call Center'), ('web', 'Web')], 'Sales Channel') + currency_id = fields.Many2one('res.currency', + related='pricelist_id.currency_id', + string='Currency', readonly=True, required=True) + price_subtotal = fields.Monetary(string='Subtotal', + readonly=True, + store=True, + compute='_compute_amount_reservation') + price_total = fields.Monetary(string='Total', + readonly=True, + store=True, + compute='_compute_amount_reservation') + price_tax = fields.Float(string='Taxes', + readonly=True, + store=True, + compute='_compute_amount_reservation') - ser_checkin = fields.Datetime('From Date', required=True, - default=_service_checkin) - ser_checkout = fields.Datetime('To Date', required=True, - default=_service_checkout) + @api.onchange('product_id') + def onchange_product_calc_qty(self): + """ + Compute the default quantity according to the + configuration of the selected product + """ + for record in self: + product = record.product_id + reservation = record.ser_room_line + if product and reservation: + qty = 1 + if product.per_day: + qty = qty * reservation.nights + if product.per_person: + qty = qty * (reservation.adults + reservation.children) + record.product_qty = qty + @api.onchange('product_qty') + def onchange_product_qty_days_selection(self): + """ + Try to calculate the days on which the product + should be served as long as the product is per day + """ + for record in self: + reservation = record.ser_room_line + if record.product_id.per_day: + days_diff = ( + fields.Date.from_string(reservation.checkout) - fields.Date.from_string(reservation.checkin) + ).days + record.update(record.prepare_service_lines( + reservation.checkin, + days_diff)) + else: + record.update(rec.prepare_service_lines( + reservation.checkin, 1)) + - # TODO Hierarchical relationship for parent-child tree - # parent_id = fields.Many2one ... + @api.multi + def prepare_service_lines(self, dfrom, days, vals=False): + self.ensure_one() + old_qty = 0 + cmds = [(5, 0, 0)] + if not vals: + vals + product = vals.get('product_id') or self.product_id + old_lines_days = self.mapped('service_line_ids.date') + for day in service_line_ids: + old_qty = old_qty + day.day_qty + qty_day = (self.product_qty - old_qty) // (days - count(old_line_days)) + rest_day = (self.product_qty - old_qty) % (days - count(old_line_days)) + reservation = rec.ser_room_line + for i in range(0, days): + idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime( + DEFAULT_SERVER_DATE_FORMAT) + old_line = self.service_line_ids.filtered(lambda r: r.date == idate) + if idate not in old_lines_days: + cmds.append((0, False, { + 'date': idate, + 'day_qty': qty + })) + else: + cmds.append((4, old_line.id)) + return {'service_line_ids': cmds} - # service_id = fields.Many2one('product.product', 'Service_id', - # required=True, ondelete='cascade', - # delegate=True) - # service_type_id = fields.Many2one('hotel.service.type', - # 'Service Catagory') - # service_line_id = fields.Many2one('hotel.service.line', - # 'Service Line') - # @api.multi - # def unlink(self): - # # for record in self: - # # record.service_id.unlink() - # return super(HotelServices, self).unlink() + @api.depends('qty_product', 'tax_id') + def _compute_amount_service(self): + """ + Compute the amounts of the service line. + """ + for record in self: + product = rec.product_id + price = amount_room * (1 - (record.discount or 0.0) * 0.01) + taxes = record.tax_id.compute_all(price, record.currency_id, 1, product=product) + record.update({ + 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), + 'price_total': taxes['total_included'], + 'price_subtotal': taxes['total_excluded'], + }) + + diff --git a/hotel/models/hotel_service_line.py b/hotel/models/hotel_service_line.py new file mode 100644 index 000000000..7391fdb43 --- /dev/null +++ b/hotel/models/hotel_service_line.py @@ -0,0 +1,32 @@ +# Copyright 2017-2018 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields +from odoo.addons import decimal_precision as dp + +class HotelServiceLine(models.Model): + _name = "hotel.service.line" + _order = "date" + + service_id = fields.Many2one('hotel.service', string='Service', + ondelete='cascade', required=True, + copy=False) + date = fields.Date('Date') + day_qty = fields.Integer('Units') + product_id = fields.Many2one(related='service_id.product_id') + + @api.constrains('day_qty') + def no_free_resources(self): + for record in self: + limit = record.product_id.daily_limit + if limit > 0: + out_qty = sum(self.env['hotel.service.line'].search([( + 'product_id','=',record.product_id, + 'date','=',record.date)]).mapped('day_qty')) + if limit < out_qty + record.day_qty: + raise ValidationError( + _("Limit exceeded for %s")% record.date) + + + + diff --git a/hotel/models/inherit_product_pricelist.py b/hotel/models/inherit_product_pricelist.py index 9e9d47bef..4f9ce2695 100644 --- a/hotel/models/inherit_product_pricelist.py +++ b/hotel/models/inherit_product_pricelist.py @@ -1,6 +1,6 @@ # Copyright 2017 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, api +from openerp import models, fields, api, _ class ProductPricelist(models.Model): @@ -21,3 +21,6 @@ class ProductPricelist(models.Model): else: names.append((name[0], name[1])) return names + + + is_staff = fields.Boolean('Is Staff') diff --git a/hotel/models/inherit_product_product.py b/hotel/models/inherit_product_product.py deleted file mode 100644 index dca420ccb..000000000 --- a/hotel/models/inherit_product_product.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2017 Alexandre Díaz -# Copyright 2017 Dario Lodeiros -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api, _ - - -class ProductProduct(models.Model): - _inherit = "product.product" - - is_room_type = fields.Boolean('Is a Room Type', default=False) - # iscategid = fields.Boolean('Is categ id') - # isservice = fields.Boolean('Is Service id') diff --git a/hotel/models/inherit_product_template.py b/hotel/models/inherit_product_template.py new file mode 100644 index 000000000..4b5a9f180 --- /dev/null +++ b/hotel/models/inherit_product_template.py @@ -0,0 +1,13 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import models, fields, api, _ + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + is_hotel_service = fields.Boolean('Is a Hotel Service', default=False) + per_day = fields.Boolean('Unit increment per day') + per_person = fields.Boolean('Unit increment per person') + daily_limit = fields.Integer('Daily limit') diff --git a/hotel/models/inherit_res_partner.py b/hotel/models/inherit_res_partner.py index ea5bca3d0..88a1192f3 100644 --- a/hotel/models/inherit_res_partner.py +++ b/hotel/models/inherit_res_partner.py @@ -24,7 +24,6 @@ class ResPartner(models.Model): reservations_count = fields.Integer('Reservations', compute='_compute_reservations_count') folios_count = fields.Integer('Folios', compute='_compute_folios_count') - is_staff = fields.Boolean('Is Staff') """ TODO @api.onchange('is_staff') diff --git a/hotel/static/src/js/open_reservation_wizard_listview_button.js b/hotel/static/src/js/open_reservation_wizard_listview_button.js new file mode 100644 index 000000000..974594a59 --- /dev/null +++ b/hotel/static/src/js/open_reservation_wizard_listview_button.js @@ -0,0 +1,28 @@ +// Copyright 2018 Alexandre Díaz +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +odoo.define('hotel_calendar.listview_button_open_reservation_wizard', function(require) { +'use strict'; + +var ListView = require('web.ListView'), + Core = require('web.core'), + + _t = Core._t; + + +ListView.include({ + render_buttons: function () { + var self = this; + this._super.apply(this, arguments); // Sets this.$buttons + + if (this.dataset.model == 'hotel.reservation') { + this.$buttons.append(""); + this.$buttons.find('.oe_open_reservation_wizard').on('click', function(){ + self.do_action('hotel_calendar.open_wizard_reservations'); + }); + } + } +}); + +return ListView; + +}); diff --git a/hotel/views/general.xml b/hotel/views/general.xml new file mode 100644 index 000000000..a02d866be --- /dev/null +++ b/hotel/views/general.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/hotel/views/hotel_board_service.xml b/hotel/views/hotel_board_service.xml new file mode 100644 index 000000000..ba37c0cee --- /dev/null +++ b/hotel/views/hotel_board_service.xml @@ -0,0 +1,48 @@ + + + + + + + hotel.board.service.form + hotel.board.service + +
+ + + + + + + + + +
+
+
+ + + + hotel.board.service.tree + hotel.board.service + + + + + + + + + + + Board Services + hotel.board.service + form + tree,form + + + + +
diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio.xml index 3dac1349b..31c67da7d 100644 --- a/hotel/views/hotel_folio.xml +++ b/hotel/views/hotel_folio.xml @@ -414,10 +414,10 @@ - + - + diff --git a/hotel/views/inherit_product_product.xml b/hotel/views/inherit_product_product.xml deleted file mode 100644 index 26a650cf4..000000000 --- a/hotel/views/inherit_product_product.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - view.product.product.form.inherited - product.product - - - - - - - - - diff --git a/hotel/views/inherit_product_template.xml b/hotel/views/inherit_product_template.xml new file mode 100644 index 000000000..f10b6dd02 --- /dev/null +++ b/hotel/views/inherit_product_template.xml @@ -0,0 +1,26 @@ + + + + + view.product.template.form.inherited + product.template + + + + + + + + + + + + + + + + + + + + diff --git a/hotel/wizard/__init__.py b/hotel/wizard/__init__.py index ae08f23cd..87e69afbd 100644 --- a/hotel/wizard/__init__.py +++ b/hotel/wizard/__init__.py @@ -28,3 +28,4 @@ from . import massive_changes from . import split_reservation from . import duplicate_reservation from . import massive_price_reservation_days +from . import wizard_reservation diff --git a/hotel/wizard/hotel_wizard.xml b/hotel/wizard/hotel_wizard.xml index 1d6ff68c0..5d089dcb7 100644 --- a/hotel/wizard/hotel_wizard.xml +++ b/hotel/wizard/hotel_wizard.xml @@ -21,7 +21,7 @@ - + Hotel Folio Report folio.report.wizard form @@ -29,7 +29,7 @@ new - diff --git a/hotel_calendar/wizard/wizard_reservation.py b/hotel/wizard/wizard_reservation.py similarity index 100% rename from hotel_calendar/wizard/wizard_reservation.py rename to hotel/wizard/wizard_reservation.py diff --git a/hotel_calendar/wizard/wizard_reservation.xml b/hotel/wizard/wizard_reservation.xml similarity index 100% rename from hotel_calendar/wizard/wizard_reservation.xml rename to hotel/wizard/wizard_reservation.xml diff --git a/hotel_calendar/__init__.py b/hotel_calendar/__init__.py index b3576e732..bc60815fd 100644 --- a/hotel_calendar/__init__.py +++ b/hotel_calendar/__init__.py @@ -2,4 +2,3 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models from . import controllers -from . import wizard diff --git a/hotel_calendar/__manifest__.py b/hotel_calendar/__manifest__.py index 9c977338a..0e910b00b 100644 --- a/hotel_calendar/__manifest__.py +++ b/hotel_calendar/__manifest__.py @@ -33,7 +33,6 @@ 'data/menus.xml', 'data/records.xml', 'security/ir.model.access.csv', - 'wizard/wizard_reservation.xml' ], 'qweb': [ 'static/src/xml/*.xml', diff --git a/hotel_calendar/static/src/xml/hotel_calendar_view.xml b/hotel_calendar/static/src/xml/hotel_calendar_view.xml index ed12414fc..45bd912e2 100644 --- a/hotel_calendar/static/src/xml/hotel_calendar_view.xml +++ b/hotel_calendar/static/src/xml/hotel_calendar_view.xml @@ -17,7 +17,7 @@ BOOKS
- "); - this.$buttons.find('.oe_open_reservation_wizard').on('click', function(){ - self.do_action('hotel_calendar.open_wizard_reservations'); - }); - } - } -}); - -return ListView; - -}); diff --git a/hotel/static/src/js/views/list/list_controller.js b/hotel/static/src/js/views/list/list_controller.js new file mode 100644 index 000000000..e45f554cd --- /dev/null +++ b/hotel/static/src/js/views/list/list_controller.js @@ -0,0 +1,28 @@ +odoo.define('hotel.ListController', function(require) { +'use strict'; +/* + * Hotel + * GNU Public License + * Alexandre Díaz + */ + +var ListController = require('web.ListController'); +var Core = require('web.core'); + +var _t = Core._t; + +ListController.include({ + + renderButtons: function () { + this._super.apply(this, arguments); // Sets this.$buttons + var self = this; + if (this.modelName === 'hotel.reservation') { + this.$buttons.append(""); + this.$buttons.find('.oe_open_reservation_wizard').on('click', function(){ + self.do_action('hotel.open_wizard_reservations'); + }); + } + } +}); + +}); diff --git a/hotel/views/currency_exchange.xml b/hotel/views/currency_exchange.xml deleted file mode 100644 index 1efd9fba3..000000000 --- a/hotel/views/currency_exchange.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - currency.exchange.form - currency.exchange - -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- -

- -
-
-
-
- - - - currency.exchange.tree - currency.exchange - - - - - - - - - - - - - Currency Exchange - currency.exchange - form - tree,form - - -
diff --git a/hotel/views/general.xml b/hotel/views/general.xml index a02d866be..8431bcca3 100644 --- a/hotel/views/general.xml +++ b/hotel/views/general.xml @@ -4,7 +4,7 @@ diff --git a/hotel/views/hotel_board_service.xml b/hotel/views/hotel_board_service_views.xml similarity index 100% rename from hotel/views/hotel_board_service.xml rename to hotel/views/hotel_board_service_views.xml diff --git a/hotel/views/cardex.xml b/hotel/views/hotel_checkin_partner_views.xml similarity index 100% rename from hotel/views/cardex.xml rename to hotel/views/hotel_checkin_partner_views.xml diff --git a/hotel/views/hotel_dashboard.xml b/hotel/views/hotel_dashboard.xml deleted file mode 100644 index f214e9310..000000000 --- a/hotel/views/hotel_dashboard.xml +++ /dev/null @@ -1,210 +0,0 @@ - - - - - Folios - hotel.dashboard - - - - Folios - hotel.dashboard - - - - Folios - hotel.dashboard - - - - Folios - hotel.dashboard - - - - hotel.dashboard.view - hotel.dashboard - -
- - - -
-
-
- View -
- - - - -
- -
-
- New -
- - -
- -
-
- Reports -
- -
-
-
- -
- Settings -
-
-
- - - -
- - - -
-
-
- -
- -
-
-
- -
- -
-
-
-
- - -
- - - - - - -
- -
- - -
-
-
- -
-
- -
-
-
- More -
-
- - - - Hotel Dashboard - hotel.dashboard - ir.actions.act_window - form - {} - kanban - - - - - - - - - Chekins Dashboard - sales - bar - 1 - - - On Board - sales - line - 1 - - - - diff --git a/hotel/views/hotel_floor.xml b/hotel/views/hotel_floor_views.xml similarity index 100% rename from hotel/views/hotel_floor.xml rename to hotel/views/hotel_floor_views.xml diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio_views.xml similarity index 100% rename from hotel/views/hotel_folio.xml rename to hotel/views/hotel_folio_views.xml diff --git a/hotel/views/hotel_report.xml b/hotel/views/hotel_report.xml deleted file mode 100644 index 1512c356a..000000000 --- a/hotel/views/hotel_report.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - diff --git a/hotel/views/hotel_reservation.xml b/hotel/views/hotel_reservation_views.xml similarity index 100% rename from hotel/views/hotel_reservation.xml rename to hotel/views/hotel_reservation_views.xml diff --git a/hotel/views/hotel_reservation_workflow.xml b/hotel/views/hotel_reservation_workflow.xml deleted file mode 100644 index 937c66de7..000000000 --- a/hotel/views/hotel_reservation_workflow.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - hotel.reservation.basic - hotel.reservation - True - - - #---------------------------------------------- - # Activity - #---------------------------------------------- - - - - True - draft - - - - confirm - function - confirmed_reservation() - - - - done - True - function - _create_folio() - - - - checkin - function - action_reservation_checkin() - - - - checkin - function - action_reservation_checkin() - - - - cancelled - True - function - action_cancel() - - - - - - - - confirm - - - - - done - - - - - - done - - - - - %(launch_checkin_wizard)d - - - - - - done - - - - - %(launch_checkin_wizard)d - - - diff --git a/hotel/views/hotel_room_amenities_type.xml b/hotel/views/hotel_room_amenities_type_views.xml similarity index 100% rename from hotel/views/hotel_room_amenities_type.xml rename to hotel/views/hotel_room_amenities_type_views.xml diff --git a/hotel/views/hotel_room_amenities.xml b/hotel/views/hotel_room_amenities_views.xml similarity index 100% rename from hotel/views/hotel_room_amenities.xml rename to hotel/views/hotel_room_amenities_views.xml diff --git a/hotel/views/room_closure_reason.xml b/hotel/views/hotel_room_closure_reason_views.xml similarity index 100% rename from hotel/views/room_closure_reason.xml rename to hotel/views/hotel_room_closure_reason_views.xml diff --git a/hotel/views/hotel_room_type.xml b/hotel/views/hotel_room_type.xml deleted file mode 100644 index fdaeb9b8d..000000000 --- a/hotel/views/hotel_room_type.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - hotel.room_type.form - hotel.room.type - -
- -
- -
- - - - - - - - - - - - - - - - -
-
-
-
- - - - hotel.room_type.tree - hotel.room.type - - - - - - - - - - - - - - - Room Type - hotel.room.type - form - tree,form - - - - -
diff --git a/hotel/views/hotel_room_type_availability.xml b/hotel/views/hotel_room_type_availability_views.xml similarity index 100% rename from hotel/views/hotel_room_type_availability.xml rename to hotel/views/hotel_room_type_availability_views.xml diff --git a/hotel/views/hotel_room_type_class.xml b/hotel/views/hotel_room_type_class_views.xml similarity index 100% rename from hotel/views/hotel_room_type_class.xml rename to hotel/views/hotel_room_type_class_views.xml diff --git a/hotel/views/hotel_room_type_views.xml b/hotel/views/hotel_room_type_views.xml index 7177a4f6e..fdaeb9b8d 100644 --- a/hotel/views/hotel_room_type_views.xml +++ b/hotel/views/hotel_room_type_views.xml @@ -1,38 +1,34 @@ - + - - - room.type.view.form + + + hotel.room_type.form hotel.room.type -
+ -
- -
+
+ +
- - - - - - + + + + + + + - - + - - + - - - - +
@@ -40,33 +36,33 @@ - - - room.type.view.tree + + + hotel.room_type.tree hotel.room.type - - - + + + + + - - - - - Virtual Rooms + + + Room Type hotel.room.type form tree,form - + + + - - - - + diff --git a/hotel/views/hotel_room.xml b/hotel/views/hotel_room_views.xml similarity index 100% rename from hotel/views/hotel_room.xml rename to hotel/views/hotel_room_views.xml diff --git a/hotel/views/hotel_service_line.xml b/hotel/views/hotel_service_line_views.xml similarity index 100% rename from hotel/views/hotel_service_line.xml rename to hotel/views/hotel_service_line_views.xml diff --git a/hotel/views/hotel_service_type.xml b/hotel/views/hotel_service_type_views.xml similarity index 100% rename from hotel/views/hotel_service_type.xml rename to hotel/views/hotel_service_type_views.xml diff --git a/hotel/views/hotel_service.xml b/hotel/views/hotel_service_views.xml similarity index 100% rename from hotel/views/hotel_service.xml rename to hotel/views/hotel_service_views.xml diff --git a/hotel/views/inherit_reservation_workflow.xml b/hotel/views/inherit_reservation_workflow.xml deleted file mode 100755 index a00e64a73..000000000 --- a/hotel/views/inherit_reservation_workflow.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - checkin - function - action_reservation_checkin() - - - - - - done - - - - - %(launch_checkin_wizard)d - - - diff --git a/hotel/views/inherit_account_invoice_views.xml b/hotel/views/inherited_account_invoice_views.xml similarity index 100% rename from hotel/views/inherit_account_invoice_views.xml rename to hotel/views/inherited_account_invoice_views.xml diff --git a/hotel/views/inherit_account_payment_views.xml b/hotel/views/inherited_account_payment_views.xml similarity index 100% rename from hotel/views/inherit_account_payment_views.xml rename to hotel/views/inherited_account_payment_views.xml diff --git a/hotel/views/inherit_product_template.xml b/hotel/views/inherited_product_template_views.xml similarity index 100% rename from hotel/views/inherit_product_template.xml rename to hotel/views/inherited_product_template_views.xml diff --git a/hotel/views/inherit_res_partner.xml b/hotel/views/inherited_res_partner_views.xml similarity index 100% rename from hotel/views/inherit_res_partner.xml rename to hotel/views/inherited_res_partner_views.xml diff --git a/hotel/views/report_hotel_management.xml b/hotel/views/report_hotel_management.xml deleted file mode 100644 index 5e24f6971..000000000 --- a/hotel/views/report_hotel_management.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - diff --git a/hotel/wizard/__init__.py b/hotel/wizard/__init__.py index 87e69afbd..9fbcd6f9e 100644 --- a/hotel/wizard/__init__.py +++ b/hotel/wizard/__init__.py @@ -21,7 +21,6 @@ # along with this program. If not, see . # ############################################################################## -from . import hotel_wizard from . import folio_make_invoice_advance from . import checkinwizard from . import massive_changes diff --git a/hotel/wizard/hotel_wizard.py b/hotel/wizard/hotel_wizard.py deleted file mode 100644 index b662e2940..000000000 --- a/hotel/wizard/hotel_wizard.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2017 Alexandre Díaz -# Copyright 2017 Dario Lodeiros -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api - - -class FolioReportWizard(models.TransientModel): - _name = 'folio.report.wizard' - _rec_name = 'date_start' - - date_start = fields.Datetime('Start Date') - date_end = fields.Datetime('End Date') - - @api.multi - def print_report(self): - data = { - 'ids': self.ids, - 'model': 'hotel.folio', - 'form': self.read(['date_start', 'date_end'])[0] - } - return self.env.ref('hotel.report_hotel_folio').report_action(self, data=data) diff --git a/hotel/wizard/hotel_wizard.xml b/hotel/wizard/hotel_wizard.xml deleted file mode 100644 index 5d089dcb7..000000000 --- a/hotel/wizard/hotel_wizard.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - folio.report.wizard - folio.report.wizard - - - - - - -
-
- -
-
- - - - Hotel Folio Report - folio.report.wizard - form - form - new - - - - -
diff --git a/hotel_calendar/static/src/js/open_reservation_wizard_listview_button.js b/hotel_calendar/static/src/js/open_reservation_wizard_listview_button.js deleted file mode 100644 index 974594a59..000000000 --- a/hotel_calendar/static/src/js/open_reservation_wizard_listview_button.js +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 Alexandre Díaz -// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -odoo.define('hotel_calendar.listview_button_open_reservation_wizard', function(require) { -'use strict'; - -var ListView = require('web.ListView'), - Core = require('web.core'), - - _t = Core._t; - - -ListView.include({ - render_buttons: function () { - var self = this; - this._super.apply(this, arguments); // Sets this.$buttons - - if (this.dataset.model == 'hotel.reservation') { - this.$buttons.append(""); - this.$buttons.find('.oe_open_reservation_wizard').on('click', function(){ - self.do_action('hotel_calendar.open_wizard_reservations'); - }); - } - } -}); - -return ListView; - -}); From 2071c04ac855ca114da3d5677a5e7659dea3ea85 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 7 Nov 2018 11:11:08 +0100 Subject: [PATCH 12/30] [WIP] Refactoring Cardex --- hotel/__manifest__.py | 2 +- hotel/i18n/es.po | 74 +++++----- hotel/models/hotel_checkin_partner.py | 136 +++++++++--------- hotel/models/hotel_folio.py | 28 ++-- hotel/models/hotel_reservation.py | 36 ++--- hotel/security/ir.model.access.csv | 4 +- hotel/views/hotel_checkin_partner_views.xml | 114 +++++++-------- hotel/views/hotel_folio_views.xml | 34 ++--- hotel/views/hotel_reservation_views.xml | 22 +-- hotel/wizard/checkinwizard.py | 84 +++++------ hotel/wizard/checkinwizard.xml | 20 +-- .../views/hotel_reservation.xml | 2 +- .../inherited_hotel_reservation_views.xml | 2 +- 13 files changed, 279 insertions(+), 279 deletions(-) diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 23eda4660..9fccf6821 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -20,7 +20,7 @@ 'demo': ['data/hotel_data.xml'], 'data': [ 'security/hotel_security.xml', - 'security/ir.model.access.csv', + # 'security/ir.model.access.csv', 'wizard/massive_changes.xml', 'wizard/split_reservation.xml', 'wizard/duplicate_reservation.xml', diff --git a/hotel/i18n/es.po b/hotel/i18n/es.po index 18b940d34..d991f9240 100644 --- a/hotel/i18n/es.po +++ b/hotel/i18n/es.po @@ -2999,13 +2999,13 @@ msgid "Action Needed" msgstr "Action Needed" #. module: hotel -#: model:ir.actions.act_window,name:hotel.action_cardex -msgid "Action cardex" +#: model:ir.actions.act_window,name:hotel.action_checkin_partner +msgid "Action checkin" msgstr "Acción Checkin" #. module: hotel -#: model:ir.actions.act_window,name:hotel.action_cardex_download -msgid "Action cardex download" +#: model:ir.actions.act_window,name:hotel.action_checkin_partner_download +msgid "Action checkin download" msgstr "Accción descarga de checkin" #. module: hotel @@ -3311,39 +3311,39 @@ msgid "Capacity:" msgstr "Capacidad:" #. module: hotel -#: model:ir.ui.menu,name:hotel.menu_cardex -msgid "Cardex" +#: model:ir.ui.menu,name:hotel.menu_checkin_partner +msgid "Checkin" msgstr "Checkin" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_hotel_folio_cardex_pending -#: model:ir.model.fields,field_description:hotel.field_hotel_folio_cardex_pending_num -#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_cardex_pending -msgid "Cardex Pending" +#: model:ir.model.fields,field_description:hotel.field_hotel_folio_checkin_partner_pending +#: model:ir.model.fields,field_description:hotel.field_hotel_folio_checkin_partner_pending_num +#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_checkin_partner_pending +msgid "Checkin Pending" msgstr "Checkin Pendientes" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_cardex_pending_num -msgid "Cardex Pending Num" +#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_checkin_partner_pending_num +msgid "Checkin Pending Num" msgstr "Nº de Checkin Pendientes" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_hotel_folio_cardex_count -#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_cardex_count -msgid "Cardex counter" +#: model:ir.model.fields,field_description:hotel.field_hotel_folio_checkin_partner_count +#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_checkin_partner_count +msgid "Checkin counter" msgstr "Contador de Checkin" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_cardex_ids -#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_cardex_ids -msgid "Cardex ids" +#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_checkin_partner_ids +#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_checkin_partner_ids +msgid "Checkin ids" msgstr "Checkin ids" #. module: hotel #: code:addons/hotel/models/hotel_folio.py:333 #: code:addons/hotel/models/hotel_reservation.py:359 #, python-format -msgid "Cardexs" +msgid "Checkins" msgstr "Checkin" #. module: hotel @@ -3483,7 +3483,7 @@ msgstr "Niños" #. module: hotel #: model:ir.ui.view,arch_db:hotel.checkin_wizard_form_2 -#: model:ir.ui.view,arch_db:hotel.view_tree_cardex +#: model:ir.ui.view,arch_db:hotel.view_tree_checkin_partner msgid "Client name" msgstr "Nombre de Cliente" @@ -3653,7 +3653,7 @@ msgid "Create invoice/bill" msgstr "Crear Factura/cuenta" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_create_uid +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_create_uid #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_create_uid #: model:ir.model.fields,field_description:hotel.field_currency_exchange_create_uid #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_create_uid @@ -3682,7 +3682,7 @@ msgid "Created by" msgstr "Creado por" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_create_date +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_create_date #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_create_date #: model:ir.model.fields,field_description:hotel.field_currency_exchange_create_date #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_create_date @@ -3926,13 +3926,13 @@ msgid "Delivery address for current sales order." msgstr "Dirección de entrega para el pedido de venta actual." #. module: hotel -#: code:addons/hotel/models/cardex.py:38 +#: code:addons/hotel/models/checkin.py:38 #, python-format msgid "Departure date (%s) is prior to arrival on %s" msgstr "La fecha de salida (%s) es anterior a la llegada el %s" #. module: hotel -#: code:addons/hotel/models/cardex.py:80 +#: code:addons/hotel/models/checkin.py:80 #, python-format msgid "Departure date, is prior to arrival. Check it now. %s" msgstr "La fecha de salida es anterior a la llegada. Revíselo ahora. %s" @@ -3999,7 +3999,7 @@ msgid "Discount Type" msgstr "Tipo de Descuento" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_display_name +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_display_name #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_display_name #: model:ir.model.fields,field_description:hotel.field_currency_exchange_display_name #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_display_name @@ -4098,7 +4098,7 @@ msgid "Duration in Days" msgstr "Duración en días" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_email_cardex +#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_email_checkin_partner #: model:ir.model.fields,field_description:hotel.field_hotel_folio_email #: model:ir.model.fields,field_description:hotel.field_hotel_reservation_email msgid "E-mail" @@ -4127,13 +4127,13 @@ msgid "End Date" msgstr "Fecha finalización" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_enter_date +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_enter_date #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_enter_date msgid "Enter date" msgstr "Fecha Entrada" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_exit_date +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_exit_date #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_exit_date msgid "Exit date" msgstr "Fecha Salida" @@ -4532,7 +4532,7 @@ msgid "Hotel services detail provide tocustomer and it will include in main Invo msgstr "Los detalles de servicios al cliente que se incluirán en la Factura principal." #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_id +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_id #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_id #: model:ir.model.fields,field_description:hotel.field_currency_exchange_id #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_id @@ -4962,7 +4962,7 @@ msgid "Last Message Date" msgstr "Last Message Date" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex___last_update +#: model:ir.model.fields,field_description:hotel.field_checkin_partner___last_update #: model:ir.model.fields,field_description:hotel.field_checkin_wizard___last_update #: model:ir.model.fields,field_description:hotel.field_currency_exchange___last_update #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv___last_update @@ -4997,7 +4997,7 @@ msgid "Last Updated" msgstr "Actualizado el" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_write_uid +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_write_uid #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_write_uid #: model:ir.model.fields,field_description:hotel.field_currency_exchange_write_uid #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_write_uid @@ -5026,7 +5026,7 @@ msgid "Last Updated by" msgstr "Última actualización de" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_write_date +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_write_date #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_write_date #: model:ir.model.fields,field_description:hotel.field_currency_exchange_write_date #: model:ir.model.fields,field_description:hotel.field_folio_advance_payment_inv_write_date @@ -5288,7 +5288,7 @@ msgid "Minimum Stock Rules" msgstr "Reglas de stock mínimo" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_mobile_cardex +#: model:ir.model.fields,field_description:hotel.field_checkin_wizard_mobile_checkin_partner #: model:ir.model.fields,field_description:hotel.field_hotel_folio_mobile #: model:ir.model.fields,field_description:hotel.field_hotel_reservation_mobile msgid "Mobile" @@ -5570,7 +5570,7 @@ msgid "Partner for checkin" msgstr "Partner for checkin" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_partner_id +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_partner_id #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_partner_id msgid "Partner id" msgstr "Id del cliente" @@ -6089,7 +6089,7 @@ msgid "Reservation has no adults" msgstr "La reserva no tiene adultos" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_cardex_reservation_id +#: model:ir.model.fields,field_description:hotel.field_checkin_partner_reservation_id #: model:ir.model.fields,field_description:hotel.field_checkin_wizard_reservation_id msgid "Reservation id" msgstr "Reserva id" @@ -7477,8 +7477,8 @@ msgid "can't assign the same date to more than msgstr "no puede asignar la misma fecha a más de una habitación virtual" #. module: hotel -#: model:ir.model,name:hotel.model_cardex -msgid "cardex" +#: model:ir.model,name:hotel.model_checkin_partner +msgid "checkin" msgstr "checkin" #. module: hotel diff --git a/hotel/models/hotel_checkin_partner.py b/hotel/models/hotel_checkin_partner.py index 6b23fd574..fafc6ab6b 100644 --- a/hotel/models/hotel_checkin_partner.py +++ b/hotel/models/hotel_checkin_partner.py @@ -1,68 +1,68 @@ -# Copyright 2017 Dario Lodeiros -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import datetime -from openerp import models, fields, api, _ -from openerp.exceptions import except_orm, ValidationError - - -class Cardex(models.Model): - _name = 'cardex' - - # Validation for Departure date is after arrival date. - @api.constrains('exit_date') - def validation_dates(self): - if self.exit_date < self.enter_date: - raise models.ValidationError( - _('Departure date (%s) is prior to arrival on %s') % - (self.exit_date, self.enter_date)) - - def default_reservation_id(self): - if 'reservation_id' in self.env.context: - reservation = self.env['hotel.reservation'].search([ - ('id', '=', self.env.context['reservation_id']) - ]) - return reservation - return False - - def default_enter_date(self): - if 'reservation_id' in self.env.context: - reservation = self.env['hotel.reservation'].search([ - ('id', '=', self.env.context['reservation_id']) - ]) - return reservation.checkin - return False - - def default_exit_date(self): - if 'reservation_id' in self.env.context: - reservation = self.env['hotel.reservation'].search([ - ('id', '=', self.env.context['reservation_id']) - ]) - return reservation.checkout - return False - - def default_partner_id(self): - if 'reservation_id' in self.env.context: - reservation = self.env['hotel.reservation'].search([ - ('id', '=', self.env.context['reservation_id']) - ]) - return reservation.partner_id - return False - - @api.onchange('enter_date', 'exit_date') - def check_change_dates(self): - if self.exit_date <= self.enter_date: - date_1 = fields.Date.from_string(self.enter_date) - date_2 = date_1 + datetime.timedelta(days=1) - self.update({'exit_date': date_2, }) - raise ValidationError( - _('Departure date, is prior to arrival. Check it now. %s') % - (date_2)) - - partner_id = fields.Many2one('res.partner', default=default_partner_id, - required=True) - reservation_id = fields.Many2one( - 'hotel.reservation', - default=default_reservation_id, readonly=True) - enter_date = fields.Date(default=default_enter_date, required=True) - exit_date = fields.Date(default=default_exit_date, required=True) +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +from odoo import models, fields, api, _ +from odoo.exceptions import except_orm, ValidationError + + +class HotelCheckinPartner(models.Model): + _name = 'hotel_checkin_partner' + + # Validation for Departure date is after arrival date. + @api.constrains('exit_date') + def validation_dates(self): + if self.exit_date < self.enter_date: + raise models.ValidationError( + _('Departure date (%s) is prior to arrival on %s') % + (self.exit_date, self.enter_date)) + + def default_reservation_id(self): + if 'reservation_id' in self.env.context: + reservation = self.env['hotel.reservation'].search([ + ('id', '=', self.env.context['reservation_id']) + ]) + return reservation + return False + + def default_enter_date(self): + if 'reservation_id' in self.env.context: + reservation = self.env['hotel.reservation'].search([ + ('id', '=', self.env.context['reservation_id']) + ]) + return reservation.checkin + return False + + def default_exit_date(self): + if 'reservation_id' in self.env.context: + reservation = self.env['hotel.reservation'].search([ + ('id', '=', self.env.context['reservation_id']) + ]) + return reservation.checkout + return False + + def default_partner_id(self): + if 'reservation_id' in self.env.context: + reservation = self.env['hotel.reservation'].search([ + ('id', '=', self.env.context['reservation_id']) + ]) + return reservation.partner_id + return False + + @api.onchange('enter_date', 'exit_date') + def check_change_dates(self): + if self.exit_date <= self.enter_date: + date_1 = fields.Date.from_string(self.enter_date) + date_2 = date_1 + datetime.timedelta(days=1) + self.update({'exit_date': date_2, }) + raise ValidationError( + _('Departure date, is prior to arrival. Check it now. %s') % + (date_2)) + + partner_id = fields.Many2one('res.partner', default=default_partner_id, + required=True) + reservation_id = fields.Many2one( + 'hotel.reservation', + default=default_reservation_id, readonly=True) + enter_date = fields.Date(default=default_enter_date, required=True) + exit_date = fields.Date(default=default_exit_date, required=True) diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index a7ffd4ced..761e3c59a 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -105,11 +105,11 @@ class HotelFolio(models.Model): string="Payments") booking_pending = fields.Integer('Booking pending', - compute='_compute_cardex_count') - cardex_count = fields.Integer('Cardex counter', - compute='_compute_cardex_count') - cardex_pending_count = fields.Integer('Cardex Pending', - compute='_compute_cardex_count') + compute='_compute_checkin_partner_count') + checkin_partner_count = fields.Integer('Checkin counter', + compute='_compute_checkin_partner_count') + checkin_partner_pending_count = fields.Integer('Checkin Pending', + compute='_compute_checkin_partner_count') checkins_reservations = fields.Integer('checkins reservations') checkouts_reservations = fields.Integer('checkouts reservations') partner_internal_comment = fields.Text(string='Internal Partner Notes', @@ -467,10 +467,10 @@ class HotelFolio(models.Model): self.ensure_one() rooms = self.mapped('room_lines.id') return { - 'name': _('Cardexs'), + 'name': _('Checkins'), 'view_type': 'form', 'view_mode': 'tree,form', - 'res_model': 'cardex', + 'res_model': 'hotel_checkin_partner', 'type': 'ir.actions.act_window', 'domain': [('reservation_id', 'in', rooms)], 'target': 'new', @@ -502,19 +502,19 @@ class HotelFolio(models.Model): return True @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_amount') + def _compute_checkin_partner_count(self): + _logger.info('_compute_checkin_partner_amount') for record in self: if record.reservation_type == 'normal' and record.room_lines: write_vals = {} filtered_reservs = record.room_lines.filtered( lambda x: x.state != 'cancelled' and \ not x.parent_reservation) - mapped_cardex = filtered_reservs.mapped('cardex_ids.id') - record.cardex_count = len(mapped_cardex) - mapped_cardex_count = filtered_reservs.mapped( - lambda x: (x.adults + x.children) - len(x.cardex_ids)) - record.cardex_pending_count = sum(mapped_cardex_count) + mapped_checkin_partner = filtered_reservs.mapped('checkin_partner_ids.id') + record.checkin_partner_count = len(mapped_checkin_partner) + mapped_checkin_partner_count = filtered_reservs.mapped( + lambda x: (x.adults + x.children) - len(x.checkin_partner_ids)) + record.checkin_partner_pending_count = sum(mapped_checkin_partner_count) """ MAILING PROCESS diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 1952791f5..369194114 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -179,16 +179,16 @@ class HotelReservation(models.Model): pricelist_id = fields.Many2one('product.pricelist', related='folio_id.pricelist_id', readonly="1") - cardex_ids = fields.One2many('cardex', 'reservation_id') - # TODO: As cardex_count is a computed field, it can't not be used in a domain filer - # Non-stored field hotel.reservation.cardex_count cannot be searched + checkin_partner_ids = fields.One2many('hotel_checkin_partner', 'reservation_id') + # TODO: As checkin_partner_count is a computed field, it can't not be used in a domain filer + # Non-stored field hotel.reservation.checkin_partner_count cannot be searched # searching on a computed field can also be enabled by setting the search parameter. # The value is a method name returning a Domains - cardex_count = fields.Integer('Cardex counter', - compute='_compute_cardex_count') - cardex_pending_count = fields.Integer('Cardex Pending Num', - compute='_compute_cardex_count', - search='_search_cardex_pending') + checkin_partner_count = fields.Integer('Checkin counter', + compute='_compute_checkin_partner_count') + checkin_partner_pending_count = fields.Integer('Checkin Pending Num', + compute='_compute_checkin_partner_count', + search='_search_checkin_partner_pending') # check_rooms = fields.Boolean('Check Rooms') is_checkin = fields.Boolean() is_checkout = fields.Boolean() @@ -646,7 +646,7 @@ class HotelReservation(models.Model): hotel_reserv_obj = self.env['hotel.reservation'] for record in self: vals = {} - if record.cardex_ids: + if record.checkin_partner_ids: vals.update({'state': 'booking'}) else: vals.update({'state': 'confirm'}) @@ -912,18 +912,18 @@ class HotelReservation(models.Model): """ @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_count') + def _compute_checkin_partner_count(self): + _logger.info('_compute_checkin_partner_count') for record in self: - record.cardex_count = len(record.cardex_ids) - record.cardex_pending_count = (record.adults + record.children) \ - - len(record.cardex_ids) + record.checkin_partner_count = len(record.checkin_partner_ids) + record.checkin_partner_pending_count = (record.adults + record.children) \ + - len(record.checkin_partner_ids) # https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501 @api.multi - def _search_cardex_pending(self, operator, value): + def _search_checkin_partner_pending(self, operator, value): self.ensure_one() - recs = self.search([]).filtered(lambda x: x.cardex_pending_count > 0) + recs = self.search([]).filtered(lambda x: x.checkin_partner_pending_count > 0) return [('id', 'in', [x.id for x in recs])] if recs else [] @api.multi @@ -991,10 +991,10 @@ class HotelReservation(models.Model): def action_checks(self): self.ensure_one() return { - 'name': _('Cardexs'), + 'name': _('Checkins'), 'view_type': 'form', 'view_mode': 'tree,form', - 'res_model': 'cardex', + 'res_model': 'hotel_checkin_partner', 'type': 'ir.actions.act_window', 'domain': [('reservation_id', '=', self.id)], 'target': 'new', diff --git a/hotel/security/ir.model.access.csv b/hotel/security/ir.model.access.csv index fad9d9dee..73c9ce80a 100644 --- a/hotel/security/ir.model.access.csv +++ b/hotel/security/ir.model.access.csv @@ -14,8 +14,8 @@ access_hotel_folio_line,hotel_folio.line.user,model_hotel_reservation,hotel.grou access_hotel_folio_line_call,hotel_folio.line.call,model_hotel_reservation,hotel.group_hotel_call,1,1,1,1 access_hotel_invoice_call,account.invoice.call,account.model_account_invoice,hotel.group_hotel_call,1,1,1,1 access_hotel_invoice_user,account.invoice.user,account.model_account_invoice,hotel.group_hotel_user,1,1,1,1 -access_hotel_model_cardex_call,hotel.currency_exchange.call,hotel.model_cardex,hotel.group_hotel_call,1,1,1,1 -access_hotel_model_cardex_user,hotel.currency_exchange.user,hotel.model_cardex,hotel.group_hotel_user,1,1,1,1 +access_hotel_model_checkin_partner_call,hotel.currency_exchange.call,hotel.model_checkin_partner,hotel.group_hotel_call,1,1,1,1 +access_hotel_model_checkin_partner_user,hotel.currency_exchange.user,hotel.model_checkin_partner,hotel.group_hotel_user,1,1,1,1 access_hotel_order_call,hotel.order.call,sale.model_sale_order,hotel.group_hotel_call,1,1,1,1 access_hotel_order_line_call,hotel.order.line.call,sale.model_sale_order_line,hotel.group_hotel_call,1,1,1,1 access_hotel_order_line_user,hotel.order.line.user,sale.model_sale_order_line,hotel.group_hotel_user,1,1,1,1 diff --git a/hotel/views/hotel_checkin_partner_views.xml b/hotel/views/hotel_checkin_partner_views.xml index e5b7dfa4f..8963788f8 100644 --- a/hotel/views/hotel_checkin_partner_views.xml +++ b/hotel/views/hotel_checkin_partner_views.xml @@ -1,57 +1,57 @@ - - - - - - - - - - Cardex Form - cardex - -
- - - - - - - - - - - - -
-
-
- - - Cardex Tree - cardex - - - - - - - - - - - -
+ + + + + + + + + + Checkin Form + hotel_checkin_partner + +
+ + + + + + + + + + + + +
+
+
+ + + Checkin Tree + hotel_checkin_partner + + + + + + + + + + + +
diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 31c67da7d..8a03b03be 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -40,26 +40,26 @@
- + diff --git a/hotel_calendar_channel_connector/views/hotel_reservation.xml b/hotel_calendar_channel_connector/views/hotel_reservation.xml index 8bbb016e8..981f0ba31 100644 --- a/hotel_calendar_channel_connector/views/hotel_reservation.xml +++ b/hotel_calendar_channel_connector/views/hotel_reservation.xml @@ -58,7 +58,7 @@