From 3111a80c22f16aef76ea9c39f92aa5b946bf2163 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Sat, 11 May 2019 17:39:28 +0200 Subject: [PATCH 1/3] [FIX] rate_id conversion pricelist on wubook import reservation --- .../models/hotel_reservation/importer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hotel_channel_connector_wubook/models/hotel_reservation/importer.py b/hotel_channel_connector_wubook/models/hotel_reservation/importer.py index fbb8048ff..a653d0614 100644 --- a/hotel_channel_connector_wubook/models/hotel_reservation/importer.py +++ b/hotel_channel_connector_wubook/models/hotel_reservation/importer.py @@ -145,9 +145,14 @@ class HotelReservationImporter(Component): tprice += room_day_price rate_id = brday['rate_id'] # TODO: Review different pricelist in the different booked rooms (folio in Odoo) - rate_id = rate_id > 0 and rate_id or self.env['channel.backend'].sudo().search([ + if rate_id > 0: + rate_id = self.env['channel.product.pricelist'].search( + 'external_id', '=', rate_id).odoo_id.id + if rate_id <= 0: + rate_id = self.env['channel.backend'].sudo().search([ ('id', '=', self.backend_record.id) ]).wubook_parity_pricelist_id.id + # Get OTA ota_id = self.env['channel.ota.info'].search([ ('backend_id', '=', self.backend_record.id), @@ -170,7 +175,7 @@ class HotelReservationImporter(Component): 'checkout': checkout_str, 'adults': persons, 'children': book['children'], - #'pricelist_id': rate_id, + 'pricelist_id': rate_id, 'reservation_line_ids': reservation_lines, 'to_assign': True, 'state': is_cancellation and 'cancelled' or 'confirm', From 73081289e45123921cc44b38d2e168bdf72d950f Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Mon, 13 May 2019 10:08:09 +0200 Subject: [PATCH 2/3] [ADD] Shared Rooms --- hotel/__manifest__.py | 1 + hotel/models/__init__.py | 1 + hotel/models/hotel_folio.py | 23 ++-- hotel/models/hotel_reservation.py | 7 +- hotel/models/hotel_room.py | 39 +++++- hotel/models/hotel_room_type.py | 16 ++- hotel/models/hotel_service.py | 12 +- hotel/models/hotel_shared_room.py | 101 +++++++++++++++ hotel/security/ir.model.access.csv | 3 + hotel/views/hotel_room_type_views.xml | 1 + hotel/views/hotel_room_views.xml | 20 ++- hotel/views/hotel_shared_room_views.xml | 121 ++++++++++++++++++ .../models/inherited_hotel_reservation.py | 2 +- .../models/hotel_room_type/common.py | 12 +- 14 files changed, 320 insertions(+), 39 deletions(-) create mode 100644 hotel/models/hotel_shared_room.py create mode 100644 hotel/views/hotel_shared_room_views.xml diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index b1d1b9cfc..634c2b78f 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -40,6 +40,7 @@ 'views/inherited_res_partner_views.xml', 'views/hotel_room_type_views.xml', 'views/hotel_room_views.xml', + 'views/hotel_shared_room_views.xml', 'views/hotel_room_type_class_views.xml', 'views/general.xml', 'views/inherited_product_template_views.xml', diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index ae46acfd3..b56e8af46 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -8,6 +8,7 @@ from . import hotel_floor from . import hotel_folio from . import hotel_reservation from . import hotel_room +from . import hotel_shared_room from . import hotel_amenity from . import hotel_amenity_type from . import hotel_room_type diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index a121a1a96..bd7a68a2c 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -99,7 +99,8 @@ class HotelFolio(models.Model): default=lambda self: _('New')) client_order_ref = fields.Char(string='Customer Reference', copy=False) partner_id = fields.Many2one('res.partner', - track_visibility='onchange') + track_visibility='onchange', + ondelete='restrict',) room_lines = fields.One2many('hotel.reservation', 'folio_id', readonly=False, @@ -107,19 +108,20 @@ class HotelFolio(models.Model): help="Hotel room reservation detail.",) service_ids = fields.One2many('hotel.service', 'folio_id', - readonly=False, - states={'done': [('readonly', True)]}, - help="Hotel services detail provide to " - "customer and it will include in " - "main Invoice.") + readonly=False, + states={'done': [('readonly', True)]}, + help="Hotel services detail provide to " + "customer and it will include in " + "main Invoice.") company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('hotel.folio')) analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a folio.", copy=False) currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', - string='Currency', readonly=True, required=True) + string='Currency', readonly=True, required=True, ondelete='restrict',) pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, + ondelete='restrict', states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current folio.") @@ -136,10 +138,11 @@ class HotelFolio(models.Model): ('agency', 'Agencia'), ('operator', 'Tour operador'), ('virtualdoor', 'Virtual Door'),], 'Sales Channel', default='door') - user_id = fields.Many2one('res.users', string='Salesperson', index=True, + user_id = fields.Many2one('res.users', string='Salesperson', index=True, ondelete='restrict', track_visibility='onchange', default=lambda self: self.env.user) tour_operator_id = fields.Many2one('res.partner', 'Tour Operator', + ondelete='restrict', domain=[('is_tour_operator', '=', True)]) date_order = fields.Datetime( string='Order Date', @@ -245,12 +248,14 @@ class HotelFolio(models.Model): help='Margin in days to create a notice if a payment \ advance has not been recorded') segmentation_ids = fields.Many2many('res.partner.category', - string='Segmentation') + string='Segmentation', + ondelete='restrict') client_order_ref = fields.Char(string='Customer Reference', copy=False) note = fields.Text('Terms and conditions') sequence = fields.Integer(string='Sequence', default=10) team_id = fields.Many2one('crm.team', 'Sales Channel', + ondelete='restrict', change_default=True, default=_get_default_team, oldname='section_id') diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 27ff6d72d..50d61279e 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -171,7 +171,7 @@ class HotelReservation(models.Model): name = fields.Text('Reservation Description', required=True) sequence = fields.Integer(string='Sequence', default=10) - room_id = fields.Many2one('hotel.room', string='Room') + room_id = fields.Many2one('hotel.room', string='Room', ondelete='restrict') reservation_no = fields.Char('Reservation No', size=64, readonly=True) adults = fields.Integer('Adults', size=64, readonly=False, @@ -316,8 +316,9 @@ class HotelReservation(models.Model): ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no') tax_ids = fields.Many2many('account.tax', - string='Taxes', - domain=['|', ('active', '=', False), ('active', '=', True)]) + string='Taxes', + ondelete='restrict', + domain=['|', ('active', '=', False), ('active', '=', True)]) qty_to_invoice = fields.Float( compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True, digits=dp.get_precision('Product Unit of Measure')) diff --git a/hotel/models/hotel_room.py b/hotel/models/hotel_room.py index fcbf4224e..5e7501ce0 100644 --- a/hotel/models/hotel_room.py +++ b/hotel/models/hotel_room.py @@ -17,16 +17,16 @@ class HotelRoom(models.Model): active = fields.Boolean('Active', default=True) sequence = fields.Integer('Sequence', default=0) room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type', - required=True, - ondelete='restrict') + required=True, + ondelete='restrict') floor_id = fields.Many2one('hotel.floor', 'Ubication', help='At which floor the room is located.') max_adult = fields.Integer('Max Adult') max_child = fields.Integer('Max Child') capacity = fields.Integer('Capacity') - # FIXME not used to_be_cleaned = fields.Boolean('To be Cleaned', default=False) - shared_room = fields.Boolean('Shared Room', default=False) + shared_room_id = fields.Many2one('hotel.shared.room', 'Shared Room', + default=False) description_sale = fields.Text( 'Sale Description', translate=True, help="A description of the Product that you want to communicate to " @@ -36,11 +36,36 @@ class HotelRoom(models.Model): default='0', required=True) + @api.constrains('room_type_id') + def _constrain_shared_room_type(self): + for record in self: + if record.shared_room_id: + if not record.room_type_id.shared_room: + raise ValidationError(_('We cant save normal rooms \ + in a shared room type')) + else: + if record.room_type_id.shared_room: + raise ValidationError(_('We cant save shared rooms \ + in a normal room type')) + + @api.constrains('shared_room_id') + def _constrain_shared_room(self): + for record in self: + if record.shared_room_id: + if not record.capacity > 1: + raise ValidationError(_('We cant save normal rooms \ + in a shared room type')) + @api.constrains('capacity') def _check_capacity(self): - if self.capacity < 1: - raise ValidationError(_("Room capacity can't be less than one")) + for record in self: + if record.shared_room_id and record.capacity != 1: + raise ValidationError(_("A Bed only can has capacity one")) + if record.capacity < 1: + raise ValidationError(_("Room capacity can't be less than one")) @api.multi def get_capacity(self, extra_bed=0): - return self.capacity + extra_bed + if not self.shared_room_id: + return self.capacity + extra_bed + return self.capacity diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 0d21fd957..eb6783d0a 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -34,6 +34,8 @@ class HotelRoomType(models.Model): active = fields.Boolean('Active', default=True, help="The active field allows you to hide the \ category without removing it.") + shared_room = fields.Boolean('Shared Room', default=False, + help="This room type is reservation by beds") # Used for ordering sequence = fields.Integer('Sequence', default=0) @@ -47,7 +49,7 @@ class HotelRoomType(models.Model): _sql_constraints = [('code_unique', 'unique(code_type)', 'Room Type Code must be unique!')] - @api.depends('room_ids') + @api.depends('room_ids', 'room_ids.active') def _compute_total_rooms(self): for record in self: record.total_rooms_count = len(record.room_ids) @@ -107,6 +109,18 @@ class HotelRoomType(models.Model): }) return super().create(vals) + @api.constrains('shared_room', 'room_ids') + def _constrain_shared_room(self): + for record in self: + if record.shared_room: + if any(not room.shared_room_id for room in record.room_ids): + raise ValidationError(_('We cant save normal rooms \ + in a shared room type')) + else: + if any(room.shared_room_id for room in record.room_ids): + raise ValidationError(_('We cant save shared rooms \ + in a normal room type')) + @api.multi def unlink(self): for record in self: diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index c822e115c..23db11ef5 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -115,7 +115,8 @@ class HotelService(models.Model): name = fields.Char('Service description', required=True) sequence = fields.Integer(string='Sequence', default=10) - product_id = fields.Many2one('product.product', 'Service', required=True) + product_id = fields.Many2one('product.product', 'Service', + ondelete='restrict', required=True) folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade', default=_default_folio_id) @@ -132,10 +133,11 @@ class HotelService(models.Model): product_image = fields.Binary('Product Image', related="product_id.image", store=False, related_sudo=True) company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True) invoice_status = fields.Selection([ - ('invoiced', 'Fully Invoiced'), - ('to invoice', 'To Invoice'), - ('no', 'Nothing to Invoice') - ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no') + ('invoiced', 'Fully Invoiced'), + ('to invoice', 'To Invoice'), + ('no', 'Nothing to Invoice') + ], string='Invoice Status', compute='_compute_invoice_status', + store=True, readonly=True, default='no') channel_type = fields.Selection([ ('door', 'Door'), ('mail', 'Mail'), diff --git a/hotel/models/hotel_shared_room.py b/hotel/models/hotel_shared_room.py new file mode 100644 index 000000000..5e6c49d93 --- /dev/null +++ b/hotel/models/hotel_shared_room.py @@ -0,0 +1,101 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Pablo Quesada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError + + +class HotelSharedRoom(models.Model): + _name = 'hotel.shared.room' + _description = 'Hotel Shared Room' + _order = "room_type_id, name" + + name = fields.Char('Room Name', required=True) + active = fields.Boolean('Active', default=True) + room_type_id = fields.Many2one( + 'hotel.room.type', 'Hotel Room Type', + required=True, ondelete='restrict', + domain=[('shared_room', '=', True)] + ) + floor_id = fields.Many2one('hotel.floor', 'Ubication', + help='At which floor the room is located.', + ondelete='restrict',) + sequence = fields.Integer('Sequence', required=True) + beds = fields.Integer('Beds') + bed_ids = fields.One2many('hotel.room', + 'shared_room_id', + readonly=True, + ondelete='restrict',) + description_sale = fields.Text( + 'Sale Description', translate=True, + help="A description of the Product that you want to communicate to " + " your customers. This description will be copied to every Sales " + " Order, Delivery Order and Customer Invoice/Credit Note") + + @api.constrains('beds') + def _constrain_beds(self): + self.ensure_one() + if self.beds < 1: + raise ValidationError(_("Room beds can't be less than one")) + if len(self.bed_ids) > self.beds: + raise ValidationError(_( + "If you want to eliminate beds in the \ + room you must deactivate the beds from your form")) + beds = [] + inactive_beds = self.env['hotel.room'].search([ + ('active', '=', False), + ('shared_room_id', '=', self.id) + ]) + for i in range(len(self.bed_ids), self.beds): + if inactive_beds: + bed = inactive_beds[0] + bed.update({'active': True}) + inactive_beds -= bed + continue + name = u'%s (%s)' % (self.name, i) + bed_vals = { + 'name': name, + 'max_adult': 1, + 'max_child': 0, + 'capacity': 1, + 'room_type_id': self.room_type_id.id, + 'sequence': self.sequence, + 'floor_id': self.floor_id.id if self.floor_id else False, + 'shared_room_id': self.id, + } + beds.append((0, False, bed_vals)) + if beds: + self.update({ + 'bed_ids': beds + }) + + @api.constrains('active') + def _constrain_active(self): + self.bed_ids.write({ + 'active': self.active, + }) + + @api.constrains('room_type_id') + def _constrain_room_type_id(self): + self.bed_ids.write({ + 'room_type_id': self.room_type_id.id, + }) + + @api.constrains('floor_id') + def _constrain_floor_id(self): + self.bed_ids.write({ + 'floor_id': self.floor_id.id, + }) + + @api.constrains('sequence') + def _constrain_sequence(self): + self.bed_ids.write({ + 'sequence': self.sequence, + }) + + @api.constrains('descrition_sale') + def _constrain_descrition_sale(self): + self.bed_ids.write({ + 'description_sale': self.descrition_sale, + }) diff --git a/hotel/security/ir.model.access.csv b/hotel/security/ir.model.access.csv index af083d8f4..65f761c03 100644 --- a/hotel/security/ir.model.access.csv +++ b/hotel/security/ir.model.access.csv @@ -11,6 +11,7 @@ user_access_hotel_board_service,user_access_hotel_board_service,model_hotel_boar user_access_hotel_checkin_partner,user_access_hotel_checkin_partner,model_hotel_checkin_partner,hotel.group_hotel_user,1,1,1,1 user_access_hotel_room_type_class,user_access_hotel_room_type_class,model_hotel_room_type_class,hotel.group_hotel_user,1,0,0,0 user_access_hotel_room,user_access_hotel_room,model_hotel_room,hotel.group_hotel_user,1,0,0,0 +user_access_shared_hotel_room,user_access_hotel_shared_room,model_hotel_shared_room,hotel.group_hotel_user,1,0,0,0 user_access_hotel_room_type_restriction_item,user_access_hotel_room_type_restriction_item,model_hotel_room_type_restriction_item,hotel.group_hotel_user,1,0,0,0 user_access_hotel_reservation,user_access_hotel_reservation,model_hotel_reservation,hotel.group_hotel_user,1,1,1,1 user_access_hotel_folio,user_access_hotel_folio,model_hotel_folio,hotel.group_hotel_user,1,1,1,1 @@ -32,6 +33,7 @@ manager_access_hotel_board_service,manager_access_hotel_board_service,model_hote manager_access_hotel_checkin_partner,manager_access_hotel_checkin_partner,model_hotel_checkin_partner,hotel.group_hotel_manager,1,1,1,1 manager_access_hotel_room_type_class,manager_access_hotel_room_type_class,model_hotel_room_type_class,hotel.group_hotel_manager,1,1,1,1 manager_access_hotel_room,manager_access_hotel_room,model_hotel_room,hotel.group_hotel_manager,1,1,1,1 +manager_access_hotel_shared_room,manager_access_hotel_shared_room,model_hotel_shared_room,hotel.group_hotel_manager,1,1,1,1 manager_access_hotel_room_type_restriction_item,manager_access_hotel_room_type_restriction_item,model_hotel_room_type_restriction_item,hotel.group_hotel_manager,1,1,1,1 manager_access_hotel_reservation,manager_access_hotel_reservation,model_hotel_reservation,hotel.group_hotel_manager,1,1,1,1 manager_access_hotel_folio,manager_access_hotel_folio,model_hotel_folio,hotel.group_hotel_manager,1,1,1,1 @@ -52,6 +54,7 @@ call_access_hotel_board_service,call_access_hotel_board_service,model_hotel_boar call_access_hotel_checkin_partner,call_access_hotel_checkin_partner,model_hotel_checkin_partner,hotel.group_hotel_call,1,1,1,1 call_access_hotel_room_type_class,call_access_hotel_room_type_class,model_hotel_room_type_class,hotel.group_hotel_call,1,0,0,0 call_access_hotel_room,call_access_hotel_room,model_hotel_room,hotel.group_hotel_call,1,0,0,0 +call_access_hotel_shared_room,call_access_hotel_shared_room,model_hotel_shared_room,hotel.group_hotel_call,1,0,0,0 call_access_hotel_room_type_restriction_item,call_access_hotel_room_type_restriction_item,model_hotel_room_type_restriction_item,hotel.group_hotel_call,1,0,0,0 call_access_hotel_reservation,call_access_hotel_reservation,model_hotel_reservation,hotel.group_hotel_call,1,1,1,1 call_access_hotel_folio,call_access_hotel_folio,model_hotel_folio,hotel.group_hotel_call,1,1,1,1 diff --git a/hotel/views/hotel_room_type_views.xml b/hotel/views/hotel_room_type_views.xml index ba341fbb5..15aa43600 100644 --- a/hotel/views/hotel_room_type_views.xml +++ b/hotel/views/hotel_room_type_views.xml @@ -27,6 +27,7 @@ + diff --git a/hotel/views/hotel_room_views.xml b/hotel/views/hotel_room_views.xml index ac7982998..9f48ceab0 100644 --- a/hotel/views/hotel_room_views.xml +++ b/hotel/views/hotel_room_views.xml @@ -19,7 +19,10 @@