From bf886eaeef43cf29c95337f73c3dd4778f26f22a Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Thu, 26 Jul 2018 17:08:20 +0200 Subject: [PATCH 01/28] [WIP] Minor cleanup --- hotel/models/hotel_room.py | 58 -------------------------------------- 1 file changed, 58 deletions(-) diff --git a/hotel/models/hotel_room.py b/hotel/models/hotel_room.py index 3c6cd26a8..f1aaa0faa 100644 --- a/hotel/models/hotel_room.py +++ b/hotel/models/hotel_room.py @@ -30,18 +30,6 @@ class HotelRoom(models.Model): 'room_amenities', 'rcateg_id', string='Room Amenities', help='List of room amenities.') - - # default price for this room - list_price = fields.Float(store=True, - string='Room Rate', - help='The room rate is fixed unless a room type' - ' is selected, in which case the rate is taken from' - ' the room type.') - # how to manage the price - # sale_price_type = fields.Selection([ - # ('fixed', 'Fixed Price'), - # ('vroom', 'Room Type'), - # ], 'Price Type', default='fixed', required=True) # max number of adults and children per room max_adult = fields.Integer('Max Adult') max_child = fields.Integer('Max Child') @@ -57,49 +45,3 @@ class HotelRoom(models.Model): 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") - - - # In case the price is managed from a specific type of room - # price_virtual_room = fields.Many2one( - # 'hotel.virtual.room', - # 'Price Virtual Room', - # help='Price will be based on selected Virtual Room') - - # virtual_rooms = fields.Many2many('hotel.virtual.room', - # string='Virtual Rooms') - # categ_id = fields.Selection([('room', 'Room '), - # ('shared_room', 'Shared Room'), - # ('parking', 'Parking')], - # string='Hotel Lodging Type', - # store=True, default='room') - -# price_virtual_room_domain = fields.Char( -# compute=_compute_price_virtual_room_domain, -# readonly=True, -# store=False, -# ) - -# @api.multi -# @api.depends('categ_id') -# def _compute_price_virtual_room_domain(self): -# for rec in self: -# rec.price_virtual_room_domain = json.dumps( -# ['|', ('room_ids.id', '=', rec.id), ('room_type_ids.cat_id.id', '=', rec.categ_id.id)] -# ) - - # @api.onchange('categ_id') - # def price_virtual_room_domain(self): - # return { - # 'domain': { - # 'price_virtual_room': [ - # '|', ('room_ids.id', '=', self._origin.id), - # ('room_type_ids.cat_id.id', '=', self.categ_id.id) - # ] - # } - # } - - # @api.multi - # def unlink(self): - # for record in self: - # record.product_id.unlink() - # return super(HotelRoom, self).unlink() From 8ba964fc09237d1e51487300f2ca53d223a77380 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Thu, 26 Jul 2018 18:01:04 +0200 Subject: [PATCH 02/28] [WIP] Minor cleanup --- hotel/views/hotel_room.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/hotel/views/hotel_room.xml b/hotel/views/hotel_room.xml index 0c0ece07c..c3499f3b5 100644 --- a/hotel/views/hotel_room.xml +++ b/hotel/views/hotel_room.xml @@ -41,7 +41,6 @@ - @@ -75,7 +74,6 @@ - - @@ -34,9 +30,6 @@ - @@ -55,7 +48,6 @@ - @@ -65,7 +57,6 @@ Room Type hotel.room.type form - tree,form Date: Fri, 27 Jul 2018 19:51:30 +0200 Subject: [PATCH 10/28] [WIP] Minor cleanup --- hotel/models/hotel_room_type.py | 4 +--- hotel/views/hotel_room_type.xml | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index b4bf2b111..654e8dd9c 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -53,9 +53,7 @@ class HotelRoomType(models.Model): @api.depends('room_ids') def _compute_total_rooms(self): for record in self: - count = 0 - count += len(record.room_ids) - record.total_rooms_count = count + record.total_rooms_count = len(record.room_ids) def _check_duplicated_rooms(self): # FIXME Using a Many2one relationship duplicated should not been possible diff --git a/hotel/views/hotel_room_type.xml b/hotel/views/hotel_room_type.xml index 2195971bf..6bb542463 100644 --- a/hotel/views/hotel_room_type.xml +++ b/hotel/views/hotel_room_type.xml @@ -23,7 +23,6 @@ - From 083c21c40f03d9f0818cdac2f25096a3310b1421 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Fri, 27 Jul 2018 19:52:08 +0200 Subject: [PATCH 11/28] [WIP] Using date formar for occupancy --- hotel/models/hotel_reservation.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 3817058c0..c77a85ab8 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -1307,23 +1307,16 @@ class HotelReservation(models.Model): return super(HotelReservation, self).unlink() @api.model - def occupied(self, str_checkin_utc, str_checkout_utc): + # TODO change function name to get_reservations(self, dfrom, dto): + def occupied(self, dfrom, dto): """ - Return a RESERVATIONS array between in and out parameters - IMPORTANT: This function should receive the dates in UTC datetime zone, - as String format + @param self: The object pointer + @param dfrom: starting range date from + @param dto: starting range date to + @return: array with the reservations confirmed between dfrom and dto """ - tz_hotel = self.env['ir.default'].sudo().get( - 'res.config.settings', 'tz_hotel') - checkin_utc_dt = date_utils.get_datetime(str_checkin_utc) - checkin_dt = date_utils.dt_as_timezone(checkin_utc_dt, tz_hotel) - days_diff = date_utils.date_diff(str_checkin_utc, str_checkout_utc, - hours=False) - dates_list = date_utils.generate_dates_list(checkin_dt, days_diff or 1, - stz=tz_hotel) - reservations = self.env['hotel.reservation'].search([ - ('reservation_line_ids.date', 'in', dates_list), - ('state', '!=', 'cancelled'), - ('overbooking', '=', False) - ]) - return reservations + domain = [('reservation_line_ids.date', '>=', dfrom), + ('reservation_line_ids.date', '<=', dto), + ('state', '!=', 'cancelled'), + ('overbooking', '=', False)] + return self.env['hotel.reservation'].search(domain) From a31ac17936943e713b2867fdcecb74cb0db34a8a Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Sat, 28 Jul 2018 11:34:16 +0200 Subject: [PATCH 12/28] Remove Virtual Room --- hotel/models/virtual_room.py | 137 ----------------------------------- 1 file changed, 137 deletions(-) delete mode 100644 hotel/models/virtual_room.py diff --git a/hotel/models/virtual_room.py b/hotel/models/virtual_room.py deleted file mode 100644 index 2ccfa9a99..000000000 --- a/hotel/models/virtual_room.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# Dario Lodeiros <> -# Alexandre Díaz -# -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -############################################################################## -from decimal import Decimal -from datetime import datetime, timedelta -import dateutil.parser -# For Python 3.0 and later -from urllib.request import urlopen -import time -from openerp.exceptions import except_orm, UserError, ValidationError -from openerp.tools import ( - misc, - DEFAULT_SERVER_DATE_FORMAT, - DEFAULT_SERVER_DATETIME_FORMAT) -from openerp import models, fields, api, _ -from odoo.addons.hotel import date_utils - - -class VirtualRoom(models.Model): - _name = 'hotel.virtual.room' - _inherits = {'product.product': 'product_id'} - - @api.depends('room_ids', 'room_type_ids') - def _compute_total_rooms(self): - for r in self: - count = 0 - count += len(r.room_ids) # Rooms linked directly - room_categories = r.room_type_ids.mapped('room_ids.id') - count += self.env['hotel.room'].search_count([ - ('categ_id.id', 'in', room_categories) - ]) # Rooms linked through room type - r.total_rooms_count = count - - @api.constrains('room_ids', 'room_type_ids') - def _check_duplicated_rooms(self): - warning_msg = "" - for r in self: - room_categories = self.room_type_ids.mapped('room_ids.id') - if self.room_ids & self.env['hotel.room'].search([ - ('categ_id.id', 'in', room_categories)]): - room_ids = self.room_ids & self.env['hotel.room'].search([ - ('categ_id.id', 'in', room_categories) - ]) - rooms_name = ','.join(str(x.name) for x in room_ids) - warning_msg += _('You can not enter the same room in duplicate \ - (check the room types) %s') % rooms_name - raise models.ValidationError(warning_msg) - - @api.constrains('max_real_rooms', 'room_ids', 'room_type_ids') - def _check_max_rooms(self): - warning_msg = "" - for r in self: - if self.max_real_rooms > self.total_rooms_count: - warning_msg += _('The Maxime rooms allowed can not be greate \ - than total rooms count') - raise models.ValidationError(warning_msg) - - virtual_code = fields.Char('Code') # not used - room_ids = fields.Many2many('hotel.room', string='Rooms') - room_type_ids = fields.Many2many('hotel.room.type', string='Room Types') - total_rooms_count = fields.Integer(compute='_compute_total_rooms') - product_id = fields.Many2one('product.product', 'Product_id', - required=True, delegate=True, - ondelete='cascade') - # FIXME services are related to real rooms - service_ids = fields.Many2many('hotel.services', - string='Included Services') - max_real_rooms = fields.Integer('Default Max Room Allowed') - product_id = fields.Many2one( - 'product.product', required=True, - ondelete='cascade') - active = fields.Boolean(default=True, help="The active field allows you to hide the category without removing it.") - - @api.multi - def get_capacity(self): - self.ensure_one() - hotel_room_obj = self.env['hotel.room'] - room_categories = self.room_type_ids.mapped('room_ids.id') - room_ids = self.room_ids + hotel_room_obj.search([ - ('categ_id.id', 'in', room_categories) - ]) - capacities = room_ids.mapped('capacity') - return any(capacities) and min(capacities) or 0 - - @api.model - def check_availability_virtual_room(self, checkin, checkout, - virtual_room_id=False, notthis=[]): - occupied = self.env['hotel.reservation'].occupied(checkin, checkout) - rooms_occupied = occupied.mapped('product_id.id') - free_rooms = self.env['hotel.room'].search([ - ('product_id.id', 'not in', rooms_occupied), - ('id', 'not in', notthis) - ]) - if virtual_room_id: - hotel_room_obj = self.env['hotel.room'] - virtual_room = self.env['hotel.virtual.room'].search([ - ('id', '=', virtual_room_id) - ]) - room_categories = virtual_room.room_type_ids.mapped('room_ids.id') - rooms_linked = virtual_room.room_ids | hotel_room_obj.search([ - ('categ_id.id', 'in', room_categories)]) - free_rooms = free_rooms & rooms_linked - return free_rooms.sorted(key=lambda r: r.sequence) - - @api.multi - def unlink(self): - for record in self: - # Set fixed price to rooms with price from this virtual rooms - rooms = self.env['hotel.room'].search([ - ('sale_price_type', '=', 'vroom'), - ('price_virtual_room', '=', record.id) - ]) - for room in rooms: - room.sale_price_type = 'fixed' - # Remove product.product - record.product_id.unlink() - return super(VirtualRoom, self).unlink() From 84fcd13c7c15eac27be8ecf1792253079a3af547 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Sat, 28 Jul 2018 12:15:31 +0200 Subject: [PATCH 13/28] [WIP] Refactoring occupied function --- hotel/models/hotel_reservation.py | 22 +++++++++++----------- hotel/models/hotel_room_type.py | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index c77a85ab8..0f5c8da8a 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -1190,7 +1190,7 @@ class HotelReservation(models.Model): if self.overbooking: return checkout_dt = date_utils.get_datetime(self.checkout) - occupied = self.env['hotel.reservation'].occupied( + occupied = self.env['hotel.reservation'].get_reservations( self.checkin, checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( lambda r: r.id != self._origin.id) @@ -1281,15 +1281,16 @@ class HotelReservation(models.Model): Checkout date should be greater than the checkin date. 3.-Check the reservation dates are not occuped """ - chkin_utc_dt = date_utils.get_datetime(self.checkin) - chkout_utc_dt = date_utils.get_datetime(self.checkout) - if chkin_utc_dt >= chkout_utc_dt: + # chkin_utc_dt = date_utils.get_datetime(self.checkin) + # chkout_utc_dt = date_utils.get_datetime(self.checkout) + if self.checkin >= self.checkout: raise ValidationError(_('Room line Check In Date Should be \ less than the Check Out Date!')) if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): - occupied = self.env['hotel.reservation'].occupied( + occupied = self.env['hotel.reservation'].get_reservations( self.checkin, - chkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + self.checkout) + # chkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) occupied = occupied.filtered( lambda r: r.room_id.id == self.room_id.id and r.id != self.id) @@ -1307,13 +1308,12 @@ class HotelReservation(models.Model): return super(HotelReservation, self).unlink() @api.model - # TODO change function name to get_reservations(self, dfrom, dto): - def occupied(self, dfrom, dto): + def get_reservations(self, dfrom, dto): """ @param self: The object pointer - @param dfrom: starting range date from - @param dto: starting range date to - @return: array with the reservations confirmed between dfrom and dto + @param dfrom: range date from + @param dto: range date to + @return: array with the reservations _confirmed_ between dfrom and dto """ domain = [('reservation_line_ids.date', '>=', dfrom), ('reservation_line_ids.date', '<=', dto), diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 654e8dd9c..3ab86f464 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -73,16 +73,24 @@ class HotelRoomType(models.Model): # return any(capacities) and min(capacities) or 0 @api.model - def check_availability_virtual_room(self, checkin, checkout, + # TODO Rename to check_availability_room_type + def check_availability_virtual_room(self, dfrom, dto, room_type_id=False, notthis=[]): """ Check the avalability for an specific type of room + @param self: The object pointer + @param dfrom: Range date from + @param dto: Range date to + @param room_type_id: Room Type + @param notthis: Array excluding Room Types @return: A recordset of free rooms ? """ - occupied = self.env['hotel.reservation'].occupied(checkin, checkout) - rooms_occupied = occupied.mapped('product_id.id') + # occupied = self.env['hotel.reservation'].get_reservations(dfrom, dto) + # rooms_occupied = occupied.mapped('product_id.id') + reservations = self.env['hotel.reservation'].get_reservations(dfrom, dto) + reservations_rooms = reservations.mapped('room_id.id') free_rooms = self.env['hotel.room'].search([ - ('product_id.id', 'not in', rooms_occupied), + ('id', 'not in', reservations_rooms), ('id', 'not in', notthis) ]) if room_type_id: From 4c3208a5f87fd0cf2b48851e3b4c38001526a352 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Sat, 28 Jul 2018 14:34:23 +0200 Subject: [PATCH 14/28] [WIP] Minor cleanup --- hotel/models/hotel_reservation.py | 20 +++++--------------- hotel/models/hotel_room_type.py | 8 +------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 0f5c8da8a..881aa9b5e 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -494,9 +494,8 @@ class HotelReservation(models.Model): def _computed_nights(self): for res in self: if res.checkin and res.checkout: - timedelta = fields.Date.from_string(res.checkout) - \ - fields.Date.from_string(res.checkin) - res.nights = timedelta.days + res.nights = (fields.Date.from_string(res.checkout) - \ + fields.Date.from_string(res.checkin)).days @api.model def recompute_reservation_totals(self): @@ -940,17 +939,6 @@ class HotelReservation(models.Model): }) return res - # @api.multi - # def uos_change(self, product_uos, product_uos_qty=0, product_id=None): - # ''' - # @param self: object pointer - # ''' - # # for folio in self: - # # line = folio.order_line_id - # # line.uos_change(product_uos, product_uos_qty=0, - # # product_id=None) - # return True - # FIXME add room.id to on change after removing inheritance @api.onchange('adults', 'children') def check_capacity(self): @@ -963,8 +951,8 @@ class HotelReservation(models.Model): _('%s people do not fit in this room! ;)') % (persons)) @api.onchange('room_type_id') - # def on_change_virtual_room_id(self): def on_change_room_type_id(self): + # import wdb; wdb.set_trace() if not self.checkin: self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if not self.checkout: @@ -1013,6 +1001,7 @@ class HotelReservation(models.Model): # UTC -> Hotel tz tz = self.env['ir.default'].sudo().get('res.config.settings', 'tz_hotel') + # import wdb; wdb.set_trace() chkin_utc_dt = date_utils.get_datetime(self.checkin) chkout_utc_dt = date_utils.get_datetime(self.checkout) @@ -1281,6 +1270,7 @@ class HotelReservation(models.Model): Checkout date should be greater than the checkin date. 3.-Check the reservation dates are not occuped """ + _logger.info('check_dates') # chkin_utc_dt = date_utils.get_datetime(self.checkin) # chkout_utc_dt = date_utils.get_datetime(self.checkout) if self.checkin >= self.checkout: diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 3ab86f464..e86221153 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -85,8 +85,6 @@ class HotelRoomType(models.Model): @param notthis: Array excluding Room Types @return: A recordset of free rooms ? """ - # occupied = self.env['hotel.reservation'].get_reservations(dfrom, dto) - # rooms_occupied = occupied.mapped('product_id.id') reservations = self.env['hotel.reservation'].get_reservations(dfrom, dto) reservations_rooms = reservations.mapped('room_id.id') free_rooms = self.env['hotel.room'].search([ @@ -94,14 +92,10 @@ class HotelRoomType(models.Model): ('id', 'not in', notthis) ]) if room_type_id: - # hotel_room_obj = self.env['hotel.room'] room_type_id = self.env['hotel.room.type'].search([ ('id', '=', room_type_id) ]) - # room_categories = virtual_room.room_type_ids.mapped('room_ids.id') - # rooms_linked = virtual_room.room_ids | hotel_room_obj.search([ - # ('categ_id.id', 'in', room_categories)]) - # rooms_linked = room_type_id.room_ids + # QUESTION What linked represent? Rooms in this type ? rooms_linked = self.room_ids free_rooms = free_rooms & rooms_linked return free_rooms.sorted(key=lambda r: r.sequence) From d1940f87c1d12eaf28d20e879312f4a12e7fe478 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Mon, 30 Jul 2018 11:41:39 +0200 Subject: [PATCH 15/28] Ignore Pycharm Configuration --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index be0ddc715..416d59a46 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .settings/ +.idea From 0071cb9991c1405bdfb8abc4defd7e6b20e3924d Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Mon, 30 Jul 2018 12:02:42 +0200 Subject: [PATCH 16/28] [WIP] datetime by date --- hotel/models/hotel_reservation.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 881aa9b5e..89aeab283 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -930,8 +930,9 @@ class HotelReservation(models.Model): record.folio_id.compute_invoices_amount() checkin = vals.get('checkin', record.checkin) checkout = vals.get('checkout', record.checkout) - days_diff = date_utils.date_diff(checkin, - checkout, hours=False) + # days_diff = date_utils.date_diff(checkin, + # checkout, hours=False) + days_diff = (fields.Date.from_string(checkout) - fields.Date.from_string(checkin)).days rlines = record.prepare_reservation_lines(checkin, days_diff) record.update({ 'reservation_line_ids': rlines['commands'], @@ -957,8 +958,9 @@ class HotelReservation(models.Model): self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if not self.checkout: self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - days_diff = date_utils.date_diff( - self.checkin, self.checkout, hours=False) + # days_diff = date_utils.date_diff( + # self.checkin, self.checkout, hours=False) + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days rlines = self.prepare_reservation_lines( self.checkin, days_diff, @@ -1039,8 +1041,9 @@ class HotelReservation(models.Model): ('folio_id', '=', folio.id), ('is_checkout', '=', True) ]) - days_diff = date_utils.date_diff( - self.checkin, self.checkout, hours=False) + # days_diff = date_utils.date_diff( + # self.checkin, self.checkout, hours=False) + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days rlines = self.prepare_reservation_lines( self.checkin, days_diff, From ebc8891f76ae8fffcd7dd5b066512bf86e0a8fe1 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Mon, 30 Jul 2018 16:47:10 +0200 Subject: [PATCH 17/28] [WIP] datetime by date --- hotel/models/hotel_reservation.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 89aeab283..bc2127fd5 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -225,15 +225,6 @@ class HotelReservation(models.Model): else: return default_departure_hour - # @api.constrains('checkin', 'checkout') #Why dont run api.depends?¿? - # def _computed_nights(self): - # for res in self: - # if res.checkin and res.checkout: - # nights = days_diff = date_utils.date_diff( - # self.checkin, - # self.checkout, hours=False) - # res.nights = nights - @api.model def name_search(self, name='', args=None, operator='ilike', limit=100): if args is None: @@ -628,17 +619,12 @@ class HotelReservation(models.Model): @api.model def checkin_is_today(self): self.ensure_one() - date_now_str = date_utils.now().strftime( - DEFAULT_SERVER_DATE_FORMAT) - return date_utils.date_compare(self.checkin, date_now_str, hours=False) + return (self.checkin == fields.Date.context_today(self)) @api.model def checkout_is_today(self): self.ensure_one() - date_now_str = date_utils.now().strftime( - DEFAULT_SERVER_DATE_FORMAT) - return date_utils.date_compare(self.checkout, date_now_str, - hours=False) + return (self.checkout == fields.Date.context_today(self)) @api.multi def action_cancel(self): @@ -1308,8 +1294,12 @@ class HotelReservation(models.Model): @param dto: range date to @return: array with the reservations _confirmed_ between dfrom and dto """ + # QUESTION dto must be strictly < domain = [('reservation_line_ids.date', '>=', dfrom), - ('reservation_line_ids.date', '<=', dto), + ('reservation_line_ids.date', '<', dto), ('state', '!=', 'cancelled'), ('overbooking', '=', False)] + reservations = self.env['hotel.reservation'].search(domain) + _logger.info('get_reservations: Found') + _logger.info(reservations) return self.env['hotel.reservation'].search(domain) From e4b82bab3764956f8a8f05e65bec6d8dff98fdc6 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Mon, 30 Jul 2018 17:57:38 +0200 Subject: [PATCH 18/28] [WIP] datetime by date --- hotel/models/hotel_reservation.py | 90 +++++++++++++------------------ 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index bc2127fd5..a5c0e7207 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -151,7 +151,7 @@ class HotelReservation(models.Model): @api.multi def set_call_center_user(self): user = self.env['res.users'].browse(self.env.uid) - rec.call_center = user.has_group('hotel.group_hotel_call') + self.call_center = user.has_group('hotel.group_hotel_call') def _get_default_checkin(self): folio = False @@ -263,13 +263,8 @@ class HotelReservation(models.Model): # The record's name should now be used for description of the reservation ? name = fields.Text('Reservation Description', required=True) - # _defaults = { - # 'product_id': False - # } - room_id = fields.Many2one('hotel.room', string='Room') - reservation_no = fields.Char('Reservation No', size=64, readonly=True) adults = fields.Integer('Adults', size=64, readonly=False, track_visibility='onchange', @@ -485,20 +480,19 @@ class HotelReservation(models.Model): def _computed_nights(self): for res in self: if res.checkin and res.checkout: - res.nights = (fields.Date.from_string(res.checkout) - \ - fields.Date.from_string(res.checkin)).days + res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days - @api.model - def recompute_reservation_totals(self): - reservations = self.env['hotel.reservation'].search([]) - for res in reservations: - if res.folio_id.state not in ('done','cancel'): - _logger.info('---------BOOK-----------') - _logger.info(res.amount_reservation) - _logger.info(res.id) - res._computed_amount_reservation() - _logger.info(res.amount_reservation) - _logger.info('---------------------------') + # @api.model + # def recompute_reservation_totals(self): + # reservations = self.env['hotel.reservation'].search([]) + # for res in reservations: + # if res.folio_id.state not in ('done','cancel'): + # _logger.info('---------BOOK-----------') + # _logger.info(res.amount_reservation) + # _logger.info(res.id) + # res._computed_amount_reservation() + # _logger.info(res.amount_reservation) + # _logger.info('---------------------------') @api.depends('reservation_line_ids.price') def _computed_amount_reservation(self): @@ -842,17 +836,12 @@ class HotelReservation(models.Model): vals.update({'reservation_type': 'normal'}) if 'folio_id' in vals: folio = self.env["hotel.folio"].browse(vals['folio_id']) - # vals.update({'order_id': folio.order_id.id, - # 'channel_type': folio.channel_type}) vals.update({'channel_type': folio.channel_type}) elif 'partner_id' in vals: folio_vals = {'partner_id':int(vals.get('partner_id')), 'channel_type': vals.get('channel_type')} + # Create the folio in case of need folio = self.env["hotel.folio"].create(folio_vals) - # vals.update({'order_id': folio.order_id.id, - # 'folio_id': folio.id, - # 'reservation_type': vals.get('reservation_type'), - # 'channel_type': vals.get('channel_type')}) vals.update({'folio_id': folio.id, 'reservation_type': vals.get('reservation_type'), 'channel_type': vals.get('channel_type')}) @@ -865,12 +854,7 @@ class HotelReservation(models.Model): }) if folio: record = super(HotelReservation, self).create(vals) - # Check Capacity - # NOTE the room is not a product anymore - # room = self.env['hotel.room'].search([ - # ('product_id', '=', record.product_id.id) - # ]) - #persons = record.adults # Not count childrens + # TODO: Check Capacity should be done before creating the Folio if record.adults > record.room_id.capacity: raise ValidationError( _("Reservation persons can't be higher than room capacity")) @@ -1046,6 +1030,7 @@ class HotelReservation(models.Model): @api.model def get_availability(self, checkin, checkout, dbchanged=True, dtformat=DEFAULT_SERVER_DATE_FORMAT): + _logger.info('get_availability') date_start = date_utils.get_datetime(checkin) date_end = date_utils.get_datetime(checkout) # Not count end day of the reservation @@ -1086,38 +1071,38 @@ class HotelReservation(models.Model): return rooms_avail @api.multi - def prepare_reservation_lines(self, str_start_date_utc, days, + def prepare_reservation_lines(self, dfrom, days, update_old_prices=False): self.ensure_one() total_price = 0.0 cmds = [(5, False, False)] + # import wdb; + # wdb.set_trace() # TO-DO: Redesign relation between hotel.reservation # and sale.order.line to allow manage days by units in order #~ if self.invoice_status == 'invoiced' and not self.splitted: #~ raise ValidationError(_("This reservation is already invoiced. \ #~ To expand it you must create a new reservation.")) - hotel_tz = self.env['ir.default'].sudo().get( - 'res.config.settings', 'hotel_tz') - start_date_utc_dt = date_utils.get_datetime(str_start_date_utc) - start_date_dt = date_utils.dt_as_timezone(start_date_utc_dt, hotel_tz) - - # import wdb; wdb.set_trace() + # hotel_tz = self.env['ir.default'].sudo().get( + # 'res.config.settings', 'hotel_tz') + # start_date_utc_dt = date_utils.get_datetime(str_start_date_utc) + # start_date_dt = date_utils.dt_as_timezone(start_date_utc_dt, hotel_tz) # room = self.env['hotel.room'].search([ # ('product_id', '=', self.product_id.id) # ]) # product_id = self.room_id.sale_price_type == 'vroom' and self.room_id.price_virtual_room.product_id - product_id = self.room_type_id + product_id = self.room_type_id.product_id pricelist_id = self.env['ir.default'].sudo().get( 'res.config.settings', 'parity_pricelist_id') if pricelist_id: pricelist_id = int(pricelist_id) old_lines_days = self.mapped('reservation_line_ids.date') + # import wdb; wdb.set_trace() for i in range(0, days): - ndate = start_date_dt + timedelta(days=i) - ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT) - _logger.info('ndate_str: %s', ndate_str) - if update_old_prices or ndate_str not in old_lines_days: + idate = fields.Date.from_string(dfrom) + timedelta(days=i) + idate_str = fields.Date.to_string(idate) + if update_old_prices or idate_str not in old_lines_days: # prod = product_id.with_context( # lang=self.partner_id.lang, # partner=self.partner_id.id, @@ -1125,18 +1110,19 @@ class HotelReservation(models.Model): # date=ndate_str, # pricelist=pricelist_id, # uom=self.product_uom.id) - prod = product_id.with_context( - lang=self.partner_id.lang, - partner=self.partner_id.id, - quantity=1, - date=ndate_str, - pricelist=pricelist_id) - line_price = prod.price + # prod = product_id.with_context( + # lang=self.partner_id.lang, + # partner=self.partner_id.id, + # quantity=1, + # date=ndate_str, # AttributeError("'product.product' object has no attribute 'date'",) + # pricelist=pricelist_id) + # line_price = prod.price + line_price = product_id.list_price else: - line = self.reservation_line_ids.filtered(lambda r: r.date == ndate_str) + line = self.reservation_line_ids.filtered(lambda r: r.date == idate_str) line_price = line.price cmds.append((0, False, { - 'date': ndate_str, + 'date': idate_str, 'price': line_price })) total_price += line_price From 8ebb38953e419c820c34a5088938bb15d690df71 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 10:15:22 +0200 Subject: [PATCH 19/28] [ADD] Reservations by date function --- hotel/models/hotel_reservation.py | 69 ++++++++++++++++++------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index a5c0e7207..c6ce24ed3 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -173,8 +173,8 @@ class HotelReservation(models.Model): # ndate_dt = date_utils.get_datetime(ndate, stz=tz_hotel) # ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC') # return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - # return fields.Date.today() ¿? - return fields.Date.context_today(self) + return fields.Date.today() + # return fields.Date.context_today(self) def _get_default_checkout(self): folio = False @@ -197,7 +197,7 @@ class HotelReservation(models.Model): # ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC') # return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) # return fields.Date.today() ¿? - return fields.Date.context_today(self, datetime.now() + timedelta(days=1)) + return fields.Date.from_string(fields.Date.today()) + timedelta(days=1) def _get_default_arrival_hour(self): folio = False @@ -613,7 +613,7 @@ class HotelReservation(models.Model): @api.model def checkin_is_today(self): self.ensure_one() - return (self.checkin == fields.Date.context_today(self)) + return fields.Date.from_string(self.checkin) == fields.Date.today() @api.model def checkout_is_today(self): @@ -974,26 +974,21 @@ class HotelReservation(models.Model): tz = self.env['ir.default'].sudo().get('res.config.settings', 'tz_hotel') # import wdb; wdb.set_trace() - chkin_utc_dt = date_utils.get_datetime(self.checkin) - chkout_utc_dt = date_utils.get_datetime(self.checkout) + # chkin_utc_dt = date_utils.get_datetime(self.checkin) + # chkout_utc_dt = date_utils.get_datetime(self.checkout) + checkin_dt = fields.Date.from_string(self.checkin) + checkout_dt = fields.Date.from_string(self.checkout) if self.room_type_id: - checkin_str = chkin_utc_dt.strftime('%d/%m/%Y') - checkout_str = chkout_utc_dt.strftime('%d/%m/%Y') + checkin_str = checkin_dt.strftime('%d/%m/%Y') + checkout_str = checkout_dt.strftime('%d/%m/%Y') self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ + checkout_str - # self.product_uom = self.product_id.uom_id - if chkin_utc_dt >= chkout_utc_dt: - dpt_hour = self.env['ir.default'].sudo().get( - 'res.config.settings', 'default_departure_hour') - checkout_str = (chkin_utc_dt + timedelta(days=1)).strftime( - DEFAULT_SERVER_DATE_FORMAT) - checkout_str = "%s %s:00" % (checkout_str, dpt_hour) - checkout_dt = date_utils.get_datetime(checkout_str, stz=tz) - checkout_utc_dt = date_utils.dt_as_timezone(checkout_dt, 'UTC') - self.checkout = checkout_utc_dt.strftime( - DEFAULT_SERVER_DATETIME_FORMAT) + if checkin_dt >= checkout_dt: + _logger.info('checkin_dt >= checkout_dt as expected') + self.checkout = fields.Date.from_string(self.checkin) + timedelta(days=1) + if self.state == 'confirm' and self.checkin_is_today(): self.is_checkin = True @@ -1246,8 +1241,6 @@ class HotelReservation(models.Model): 3.-Check the reservation dates are not occuped """ _logger.info('check_dates') - # chkin_utc_dt = date_utils.get_datetime(self.checkin) - # chkout_utc_dt = date_utils.get_datetime(self.checkout) if self.checkin >= self.checkout: raise ValidationError(_('Room line Check In Date Should be \ less than the Check Out Date!')) @@ -1266,12 +1259,6 @@ class HotelReservation(models.Model): reservation period: %s ') % occupied_name raise ValidationError(warning_msg) - @api.multi - def unlink(self): - # for record in self: - # record.order_line_id.unlink() - return super(HotelReservation, self).unlink() - @api.model def get_reservations(self, dfrom, dto): """ @@ -1286,6 +1273,30 @@ class HotelReservation(models.Model): ('state', '!=', 'cancelled'), ('overbooking', '=', False)] reservations = self.env['hotel.reservation'].search(domain) - _logger.info('get_reservations: Found') - _logger.info(reservations) return self.env['hotel.reservation'].search(domain) + + @api.model + def get_reservations_dates(self, dfrom, dto): + """ + @param self: The object pointer + @param dfrom: range date from + @param dto: range date to + @return: dictionary of lists with reservations (a hash of arrays!) + with the reservations dates between dfrom and dto + reservations_dates + {'2018-07-30': [[hotel.reservation(29,), hotel.room.type(1,)], + [hotel.reservation(30,), hotel.room.type(1,)], + [hotel.reservation(31,), hotel.room.type(3,)]], + '2018-07-31': [[hotel.reservation(30,), hotel.room.type(1,)], + [hotel.reservation(31,), hotel.room.type(3,)]]} + """ + domain = [('date', '>=', dfrom), + ('date', '<', dto)] + lines = self.env['hotel.reservation.line'].search(domain) + reservations_dates = {} + for record in lines: + # kumari.net/index.php/programming/programmingcat/22-python-making-a-dictionary-of-lists-a-hash-of-arrays + # reservations_dates.setdefault(record.date,[]).append(record.reservation_id.room_type_id) + reservations_dates.setdefault(record.date, []).append( + [record.reservation_id, record.reservation_id.room_type_id]) + return reservations_dates From 4d448e9fc1ac07f011f68731c69e5039ef48d49f Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 11:04:18 +0200 Subject: [PATCH 20/28] [WIP] Refactoring get_capacity() --- hotel/models/hotel_room_type.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index e86221153..303368d9e 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -61,16 +61,14 @@ class HotelRoomType(models.Model): @api.multi def get_capacity(self): - # WARNING use selg.capacity directly ? - pass - # self.ensure_one() - # hotel_room_obj = self.env['hotel.room'] - # room_categories = self.room_type_ids.mapped('room_ids.id') - # room_ids = self.room_ids + hotel_room_obj.search([ - # ('categ_id.id', 'in', room_categories) - # ]) - # capacities = room_ids.mapped('capacity') - # return any(capacities) and min(capacities) or 0 + """ + Get the minimum capacity in the rooms of this type or zero if has no rooms + @param self: The object pointer + @return: An integer with the capacity of this room type + """ + self.ensure_one() + capacities = self.room_ids.mapped('capacity') + return any(capacities) and min(capacities) or 0 @api.model # TODO Rename to check_availability_room_type From fbca316b0992c68e51565cf918a2bc137393fd14 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 11:31:13 +0200 Subject: [PATCH 21/28] [WIP] datetime by date --- hotel/models/hotel_reservation.py | 35 ++++++++----------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index c6ce24ed3..84c147b17 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -10,6 +10,7 @@ from odoo.tools import ( from odoo import models, fields, api, _ from decimal import Decimal from dateutil.relativedelta import relativedelta +from dateutil import tz from datetime import datetime, timedelta, date from odoo.addons.hotel import date_utils import pytz @@ -155,8 +156,6 @@ class HotelReservation(models.Model): def _get_default_checkin(self): folio = False - # default_arrival_hour = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'default_arrival_hour') if 'folio_id' in self._context: folio = self.env['hotel.folio'].search([ ('id', '=', self._context['folio_id']) @@ -164,22 +163,13 @@ class HotelReservation(models.Model): if folio and folio.room_lines: return folio.room_lines[0].checkin else: - # tz_hotel = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'tz_hotel') - # now_utc_dt = date_utils.now() - # ndate = "%s %s:00" % \ - # (now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), - # default_arrival_hour) - # ndate_dt = date_utils.get_datetime(ndate, stz=tz_hotel) - # ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC') - # return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - return fields.Date.today() - # return fields.Date.context_today(self) + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') + return datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) def _get_default_checkout(self): folio = False - # default_departure_hour = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'default_departure_hour') if 'folio_id' in self._context: folio = self.env['hotel.folio'].search([ ('id', '=', self._context['folio_id']) @@ -187,17 +177,10 @@ class HotelReservation(models.Model): if folio and folio.room_lines: return folio.room_lines[0].checkout else: - # tz_hotel = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'tz_hotel') - # now_utc_dt = date_utils.now() + timedelta(days=1) - # ndate = "%s %s:00" % \ - # (now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT), - # default_departure_hour) - # ndate_dt = date_utils.get_datetime(ndate, stz=tz_hotel) - # ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC') - # return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - # return fields.Date.today() ¿? - return fields.Date.from_string(fields.Date.today()) + timedelta(days=1) + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') + return datetime.date(datetime.now(tz=tzinfo) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) def _get_default_arrival_hour(self): folio = False From 2b339e7022f7941cabea1b58e6c33383f3795ab2 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 12:00:06 +0200 Subject: [PATCH 22/28] [WIP] datetime by date --- hotel/models/hotel_reservation.py | 54 ++++++++++++++----------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 84c147b17..872dbe456 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -231,12 +231,12 @@ class HotelReservation(models.Model): result.append((res.id, name)) return result - # FIXME added for migration - def _compute_qty_delivered_updateable(self): - pass - # FIXME added for migration - def _compute_invoice_status(self): - pass + # # FIXME added for migration + # def _compute_qty_delivered_updateable(self): + # pass + # # FIXME added for migration + # def _compute_invoice_status(self): + # pass _name = 'hotel.reservation' _description = 'Hotel Reservation' @@ -465,18 +465,6 @@ class HotelReservation(models.Model): if res.checkin and res.checkout: res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days - # @api.model - # def recompute_reservation_totals(self): - # reservations = self.env['hotel.reservation'].search([]) - # for res in reservations: - # if res.folio_id.state not in ('done','cancel'): - # _logger.info('---------BOOK-----------') - # _logger.info(res.amount_reservation) - # _logger.info(res.id) - # res._computed_amount_reservation() - # _logger.info(res.amount_reservation) - # _logger.info('---------------------------') - @api.depends('reservation_line_ids.price') def _computed_amount_reservation(self): _logger.info('_computed_amount_reservation') @@ -596,12 +584,20 @@ class HotelReservation(models.Model): @api.model def checkin_is_today(self): self.ensure_one() - return fields.Date.from_string(self.checkin) == fields.Date.today() + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') + today = datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) + return fields.Date.from_string(self.checkin) == today @api.model def checkout_is_today(self): self.ensure_one() - return (self.checkout == fields.Date.context_today(self)) + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') + today = datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) + return fields.Date.from_string(self.checkout) == today @api.multi def action_cancel(self): @@ -814,7 +810,7 @@ class HotelReservation(models.Model): @param vals: dictionary of fields value. @return: new record set for hotel folio line. """ - # import wdb; wdb.set_trace() + import wdb; wdb.set_trace() if not 'reservation_type' in vals or not vals.get('reservation_type'): vals.update({'reservation_type': 'normal'}) if 'folio_id' in vals: @@ -908,9 +904,9 @@ class HotelReservation(models.Model): def on_change_room_type_id(self): # import wdb; wdb.set_trace() if not self.checkin: - self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkin = self._get_default_checkin() if not self.checkout: - self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkout = self._get_default_checkout() # days_diff = date_utils.date_diff( # self.checkin, self.checkout, hours=False) days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days @@ -932,9 +928,9 @@ class HotelReservation(models.Model): _logger.info('on_change_checkin_checkout_product_id') # import wdb; wdb.set_trace() if not self.checkin: - self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkin = self._get_default_checkin() if not self.checkout: - self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkout = self._get_default_checkout() # WARNING Need a review # if self.product_id: # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] @@ -1029,7 +1025,7 @@ class HotelReservation(models.Model): rdays = [] for i in range(0, date_diff): ndate_dt = date_start + timedelta(days=i) - ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) avail = len(hotel_vroom_obj.check_availability_virtual_room( ndate_str, ndate_str, @@ -1124,11 +1120,9 @@ class HotelReservation(models.Model): self.ensure_one() now_utc_dt = date_utils.now() if not self.checkin: - self.checkin = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkin = self._get_default_checkin() if not self.checkout: - now_utc_dt = date_utils.get_datetime(self.checkin)\ - + timedelta(days=1) - self.checkout = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.checkout = self._get_default_checkout() if self.overbooking: return checkout_dt = date_utils.get_datetime(self.checkout) From f4fa0839ea08bb02f0387542631c7c163900ebfc Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 19:19:05 +0200 Subject: [PATCH 23/28] [WIP] datetime by date using Odoo --- hotel/models/hotel_reservation.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 872dbe456..213a00c42 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -165,8 +165,8 @@ class HotelReservation(models.Model): else: tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') - tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') - return datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) + today = fields.Date.context_today(self.with_context(tz=tz_hotel)) + return fields.Date.from_string(today).strftime(DEFAULT_SERVER_DATE_FORMAT) def _get_default_checkout(self): folio = False @@ -179,8 +179,8 @@ class HotelReservation(models.Model): else: tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') - tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') - return datetime.date(datetime.now(tz=tzinfo) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + today = fields.Date.context_today(self.with_context(tz=tz_hotel)) + return (fields.Date.from_string(today) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) def _get_default_arrival_hour(self): folio = False @@ -810,7 +810,7 @@ class HotelReservation(models.Model): @param vals: dictionary of fields value. @return: new record set for hotel folio line. """ - import wdb; wdb.set_trace() + # import wdb; wdb.set_trace() if not 'reservation_type' in vals or not vals.get('reservation_type'): vals.update({'reservation_type': 'normal'}) if 'folio_id' in vals: @@ -903,6 +903,7 @@ class HotelReservation(models.Model): @api.onchange('room_type_id') def on_change_room_type_id(self): # import wdb; wdb.set_trace() + # TODO: Remove this check once added as contrain if not self.checkin: self.checkin = self._get_default_checkin() if not self.checkout: @@ -927,6 +928,7 @@ class HotelReservation(models.Model): def on_change_checkin_checkout_product_id(self): _logger.info('on_change_checkin_checkout_product_id') # import wdb; wdb.set_trace() + # TODO: Remove this check once added as contrain if not self.checkin: self.checkin = self._get_default_checkin() if not self.checkout: @@ -1119,6 +1121,7 @@ class HotelReservation(models.Model): _logger.info('on_change_checkout') self.ensure_one() now_utc_dt = date_utils.now() + # TODO: Remove this check once added as contrain if not self.checkin: self.checkin = self._get_default_checkin() if not self.checkout: @@ -1210,6 +1213,7 @@ class HotelReservation(models.Model): # sale_line_obj = self.env['sale.order.line'].browse(line_id) # return sale_line_obj.copy_data(default=default) + # TODO: Use default values on checkin /checkout is empty @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') def check_dates(self): """ From 27d314eac1d1f48933cda65f0302ab5ce88ce566 Mon Sep 17 00:00:00 2001 From: Pablo Quesada Barriuso Date: Tue, 31 Jul 2018 22:20:20 +0200 Subject: [PATCH 24/28] [WIP] datetime by date using Odoo --- hotel/models/hotel_reservation.py | 51 +++++++++---------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 213a00c42..2330898ba 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -586,18 +586,16 @@ class HotelReservation(models.Model): self.ensure_one() tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') - tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') - today = datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) - return fields.Date.from_string(self.checkin) == today + today = fields.Date.context_today(self.with_context(tz=tz_hotel)) + return self.checkin == today @api.model def checkout_is_today(self): self.ensure_one() tz_hotel = self.env['ir.default'].sudo().get( 'res.config.settings', 'tz_hotel') - tzinfo = tz.gettz(tz_hotel and str(tz_hotel) or 'UTC') - today = datetime.date(datetime.now(tz=tzinfo)).strftime(DEFAULT_SERVER_DATE_FORMAT) - return fields.Date.from_string(self.checkout) == today + today = fields.Date.context_today(self.with_context(tz=tz_hotel)) + return self.checkout == today @api.multi def action_cancel(self): @@ -928,7 +926,6 @@ class HotelReservation(models.Model): def on_change_checkin_checkout_product_id(self): _logger.info('on_change_checkin_checkout_product_id') # import wdb; wdb.set_trace() - # TODO: Remove this check once added as contrain if not self.checkin: self.checkin = self._get_default_checkin() if not self.checkout: @@ -951,26 +948,18 @@ class HotelReservation(models.Model): self.room_type_id = self.room_id.room_type_id self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] - # UTC -> Hotel tz - tz = self.env['ir.default'].sudo().get('res.config.settings', - 'tz_hotel') - # import wdb; wdb.set_trace() - # chkin_utc_dt = date_utils.get_datetime(self.checkin) - # chkout_utc_dt = date_utils.get_datetime(self.checkout) checkin_dt = fields.Date.from_string(self.checkin) checkout_dt = fields.Date.from_string(self.checkout) + if checkin_dt >= checkout_dt: + self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + # ensure checkin and checkout are correct before changing the name if self.room_type_id: checkin_str = checkin_dt.strftime('%d/%m/%Y') checkout_str = checkout_dt.strftime('%d/%m/%Y') self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ + checkout_str - if checkin_dt >= checkout_dt: - _logger.info('checkin_dt >= checkout_dt as expected') - self.checkout = fields.Date.from_string(self.checkin) + timedelta(days=1) - - if self.state == 'confirm' and self.checkin_is_today(): self.is_checkin = True folio = self.env['hotel.folio'].browse(self.folio_id.id) @@ -987,8 +976,6 @@ class HotelReservation(models.Model): ('folio_id', '=', folio.id), ('is_checkout', '=', True) ]) - # days_diff = date_utils.date_diff( - # self.checkin, self.checkout, hours=False) days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days rlines = self.prepare_reservation_lines( self.checkin, @@ -1074,11 +1061,9 @@ class HotelReservation(models.Model): if pricelist_id: pricelist_id = int(pricelist_id) old_lines_days = self.mapped('reservation_line_ids.date') - # import wdb; wdb.set_trace() for i in range(0, days): - idate = fields.Date.from_string(dfrom) + timedelta(days=i) - idate_str = fields.Date.to_string(idate) - if update_old_prices or idate_str not in old_lines_days: + idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) + if update_old_prices or idate not in old_lines_days: # prod = product_id.with_context( # lang=self.partner_id.lang, # partner=self.partner_id.id, @@ -1095,10 +1080,10 @@ class HotelReservation(models.Model): # line_price = prod.price line_price = product_id.list_price else: - line = self.reservation_line_ids.filtered(lambda r: r.date == idate_str) + line = self.reservation_line_ids.filtered(lambda r: r.date == idate) line_price = line.price cmds.append((0, False, { - 'date': idate_str, + 'date': idate, 'price': line_price })) total_price += line_price @@ -1110,6 +1095,7 @@ class HotelReservation(models.Model): self.adults = self.room_id.capacity @api.multi + # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') def on_change_checkout(self): ''' @@ -1120,18 +1106,12 @@ class HotelReservation(models.Model): ''' _logger.info('on_change_checkout') self.ensure_one() - now_utc_dt = date_utils.now() - # TODO: Remove this check once added as contrain - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() if self.overbooking: return - checkout_dt = date_utils.get_datetime(self.checkout) + occupied = self.env['hotel.reservation'].get_reservations( self.checkin, - checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( + fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( lambda r: r.id != self._origin.id) rooms_occupied = occupied.mapped('room_id.id') if self.room_id and self.room_id.id in rooms_occupied: @@ -1222,14 +1202,13 @@ class HotelReservation(models.Model): 3.-Check the reservation dates are not occuped """ _logger.info('check_dates') - if self.checkin >= self.checkout: + if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout): raise ValidationError(_('Room line Check In Date Should be \ less than the Check Out Date!')) if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): occupied = self.env['hotel.reservation'].get_reservations( self.checkin, self.checkout) - # chkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) occupied = occupied.filtered( lambda r: r.room_id.id == self.room_id.id and r.id != self.id) From b341e39760317adcf6062419c5b971b67d809b64 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Tue, 14 Aug 2018 19:25:59 +0200 Subject: [PATCH 25/28] [IMP] Refactoring hotel reservation --- hotel/i18n/es.po | 2 +- hotel/models/hotel_folio.py | 621 +++++----- hotel/models/hotel_reservation.py | 1441 ++++++++++------------- hotel/models/hotel_room.py | 18 +- hotel/models/inherit_account_payment.py | 2 +- hotel/models/inherit_payment_return.py | 2 +- hotel/views/hotel_folio.xml | 28 +- hotel/views/hotel_reservation.xml | 28 +- 8 files changed, 939 insertions(+), 1203 deletions(-) diff --git a/hotel/i18n/es.po b/hotel/i18n/es.po index a10548771..9e4b9e51c 100644 --- a/hotel/i18n/es.po +++ b/hotel/i18n/es.po @@ -4842,7 +4842,7 @@ msgid "Invoices" msgstr "Facturas" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_hotel_folio_invoices_amount +#: model:ir.model.fields,field_description:hotel.field_hotel_folio_pending_amount #: model:ir.model.fields,field_description:hotel.field_hotel_reservation_folio_pending_amount msgid "Invoices amount" msgstr "Cantidad Facturada" diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 54dd4fbe8..2169bc68e 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -23,50 +23,6 @@ from odoo.addons import decimal_precision as dp class HotelFolio(models.Model): - @api.model - def name_search(self, name='', args=None, operator='ilike', limit=100): - if args is None: - args = [] - args += ([('name', operator, name)]) - mids = self.search(args, limit=100) - return mids.name_get() - - @api.model - def _needaction_count(self, domain=None): - """ - Show a count of draft state folio on the menu badge. - @param self: object pointer - """ - return self.search_count([('state', '=', 'draft')]) - - @api.multi - def copy(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - return super(HotelFolio, self).copy(default=default) - - @api.multi - def _invoiced(self, name, arg): - ''' - @param self: object pointer - @param name: Names of fields. - @param arg: User defined arguments - ''' - pass - # return self.env['sale.order']._invoiced(name, arg) - - @api.multi - def _invoiced_search(self, obj, name, args): - ''' - @param self: object pointer - @param name: Names of fields. - @param arg: User defined arguments - ''' - pass - # return self.env['sale.order']._invoiced_search(obj, name, args) - # @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity') def _get_invoice_qty(self): pass @@ -93,7 +49,7 @@ class HotelFolio(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] name = fields.Char('Folio Number', readonly=True, index=True, - default='New') + default=lambda self: _('New')) partner_id = fields.Many2one('res.partner', track_visibility='onchange') # partner_invoice_id = fields.Many2one('res.partner', @@ -108,6 +64,7 @@ class HotelFolio(models.Model): mobile = fields.Char('Mobile', related='partner_id.mobile') phone = fields.Char('Phone', related='partner_id.phone') + #Review: How to use state in folio? state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'), ('booking', 'On Board'), ('done', 'Out'), ('cancelled', 'Cancelled')], @@ -126,37 +83,27 @@ class HotelFolio(models.Model): help="Hotel services detail provide to " "customer and it will include in " "main Invoice.") - # service_line_ids = fields.One2many('hotel.service.line', 'folio_id', - # readonly=False, - # states={'done': [('readonly', True)]}, - # help="Hotel services detail provide to" - # "customer and it will include in " - # "main Invoice.") - # has no sense used as this way hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice') company_id = fields.Many2one('res.company', 'Company') - # currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', - # string='Currency', readonly=True, required=True) + currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', + string='Currency', readonly=True, required=True) - # pricelist_id = fields.Many2one('product.pricelist', - # string='Pricelist', - # required=True, - # readonly=True, - # states={'draft': [('readonly', False)], - # 'sent': [('readonly', False)]}, - # help="Pricelist for current sales order.") - # Monetary to Float - invoices_amount = fields.Float(compute='compute_invoices_amount', + pricelist_id = fields.Many2one('product.pricelist', + string='Pricelist', + required=True, + readonly=True, + states={'draft': [('readonly', False)], + 'sent': [('readonly', False)]}, + help="Pricelist for current folio.") + pending_amount = fields.Monetary(compute='compute_amount', store=True, string="Pending in Folio") - # Monetary to Float - refund_amount = fields.Float(compute='compute_invoices_amount', + refund_amount = fields.Monetary(compute='compute_amount', store=True, string="Payment Returns") - # Monetary to Float - invoices_paid = fields.Float(compute='compute_invoices_amount', + invoices_paid = fields.Monetary(compute='compute_amount', store=True, track_visibility='onchange', string="Payments") @@ -228,75 +175,27 @@ class HotelFolio(models.Model): amount_total = fields.Float(string='Total', store=True, readonly=True, track_visibility='always') - - def _compute_fix_price(self): - for record in self: - for res in record.room_lines: - if res.fix_total == True: - record.fix_price = True - break - else: - record.fix_price = False - - def action_recalcule_payment(self): - for record in self: - for res in record.room_lines: - res.on_change_checkin_checkout_product_id() - def _computed_rooms_char(self): for record in self: rooms = ', '.join(record.mapped('room_lines.room_id.name')) record.rooms_char = rooms - @api.model - def recompute_amount(self): - folios = self.env['hotel.folio'] - if folios: - folios = folios.filtered(lambda x: ( - x.name == folio_name)) - folios.compute_invoices_amount() - @api.multi def _compute_num_invoices(self): pass # for fol in self: # fol.num_invoices = len(self.mapped('invoice_ids.id')) - @api.model - def daily_plan(self): - _logger.info('daily_plan') - self._cr.execute("update hotel_folio set checkins_reservations = 0, \ - checkouts_reservations = 0 where checkins_reservations > 0 \ - or checkouts_reservations > 0") - folios_in = self.env['hotel.folio'].search([ - ('room_lines.is_checkin', '=', True) - ]) - folios_out = self.env['hotel.folio'].search([ - ('room_lines.is_checkout', '=', True) - ]) - for fol in folios_in: - count_checkin = fol.room_lines.search_count([ - ('is_checkin', '=', True), ('folio_id.id', '=', fol.id) - ]) - fol.write({'checkins_reservations': count_checkin}) - for fol in folios_out: - count_checkout = fol.room_lines.search_count([ - ('is_checkout', '=', True), - ('folio_id.id', '=', fol.id) - ]) - fol.write({'checkouts_reservations': count_checkout}) - return True - # @api.depends('order_line.price_total', 'payment_ids', 'return_ids') @api.multi - def compute_invoices_amount(self): - _logger.info('compute_invoices_amount') + def compute_amount(self): + _logger.info('compute_amount') @api.multi def action_pay(self): self.ensure_one() partner = self.partner_id.id - amount = self.invoices_amount + amount = self.pending_amount view_id = self.env.ref('hotel.view_account_payment_folio_form').id return{ 'name': _('Register Payment'), @@ -373,6 +272,202 @@ class HotelFolio(models.Model): 'domain': [('id', 'in', return_move_ids)], } + @api.multi + def action_folios_amount(self): + now_utc_dt = date_utils.now() + now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + reservations = self.env['hotel.reservation'].search([ + ('checkout', '<=', now_utc_str) + ]) + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)]) + folios = folios.filtered(lambda r: r.pending_amount > 0) + return { + 'name': _('Pending'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'hotel.folio', + 'type': 'ir.actions.act_window', + 'domain': [('id', 'in', folios.ids)] + } + + @api.multi + def go_to_currency_exchange(self): + ''' + when Money Exchange button is clicked then this method is called. + ------------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('go_to_currency_exchange') + pass + # cr, uid, context = self.env.args + # context = dict(context) + # for rec in self: + # if rec.partner_id.id and len(rec.room_lines) != 0: + # context.update({'folioid': rec.id, 'guest': rec.partner_id.id, + # 'room_no': rec.room_lines[0].product_id.name}) + # self.env.args = cr, uid, misc.frozendict(context) + # else: + # raise except_orm(_('Warning'), _('Please Reserve Any Room.')) + # return {'name': _('Currency Exchange'), + # 'res_model': 'currency.exchange', + # 'type': 'ir.actions.act_window', + # 'view_id': False, + # 'view_mode': 'form,tree', + # 'view_type': 'form', + # 'context': {'default_folio_no': context.get('folioid'), + # 'default_hotel_id': context.get('hotel'), + # 'default_guest_name': context.get('guest'), + # 'default_room_number': context.get('room_no') + # }, + # } + + @api.model + def create(self, vals, check=True): + if vals.get('name', _('New')) == _('New'): + if 'company_id' in vals: + vals['name'] = self.env['ir.sequence'].with_context(force_company=vals['company_id']).next_by_code('sale.order') or _('New') + else: + vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New') + + # Makes sure partner_invoice_id' and 'pricelist_id' are defined + if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']): + partner = self.env['res.partner'].browse(vals.get('partner_id')) + addr = partner.address_get(['delivery', 'invoice']) + vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice']) + vals['pricelist_id'] = vals.setdefault('pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id) + result = super(HotelFolio, self).create(vals) + return result + + @api.multi + @api.onchange('partner_id') + def onchange_partner_id(self): + ''' + When you change partner_id it will update the partner_invoice_id, + partner_shipping_id and pricelist_id of the hotel folio as well + --------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('onchange_partner_id') + pass + # self.update({ + # 'currency_id': self.env.ref('base.main_company').currency_id, + # 'partner_invoice_id': self.partner_id and self.partner_id.id or False, + # 'partner_shipping_id': self.partner_id and self.partner_id.id or False, + # 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False, + # }) + # """ + # Warning messajes saved in partner form to folios + # """ + # if not self.partner_id: + # return + # warning = {} + # title = False + # message = False + # partner = self.partner_id + # + # # If partner has no warning, check its company + # if partner.sale_warn == 'no-message' and partner.parent_id: + # partner = partner.parent_id + # + # if partner.sale_warn != 'no-message': + # # Block if partner only has warning but parent company is blocked + # if partner.sale_warn != 'block' and partner.parent_id \ + # and partner.parent_id.sale_warn == 'block': + # partner = partner.parent_id + # title = _("Warning for %s") % partner.name + # message = partner.sale_warn_msg + # warning = { + # 'title': title, + # 'message': message, + # } + # if self.partner_id.sale_warn == 'block': + # self.update({ + # 'partner_id': False, + # 'partner_invoice_id': False, + # 'partner_shipping_id': False, + # 'pricelist_id': False + # }) + # return {'warning': warning} + # + # if warning: + # return {'warning': warning} + + @api.multi + def action_invoice_create(self, grouped=False, states=None): + ''' + @param self: object pointer + ''' + pass + # if states is None: + # states = ['confirmed', 'done'] + # order_ids = [folio.order_id.id for folio in self] + # sale_obj = self.env['sale.order'].browse(order_ids) + # invoice_id = (sale_obj.action_invoice_create(grouped=False, + # states=['confirmed', + # 'done'])) + # for line in self: + # values = {'invoiced': True, + # 'state': 'progress' if grouped else 'progress', + # 'hotel_invoice_id': invoice_id + # } + # line.write(values) + # return invoice_id + + @api.multi + def advance_invoice(self): + pass + + ''' + WORKFLOW STATE + ''' + + @api.multi + def button_dummy(self): + ''' + @param self: object pointer + ''' + # for folio in self: + # folio.order_id.button_dummy() + return True + + @api.multi + def action_done(self): + for line in self.room_lines: + if line.state == "booking": + line.action_reservation_checkout() + + @api.multi + def action_cancel(self): + ''' + @param self: object pointer + ''' + pass + # for sale in self: + # if not sale.order_id: + # raise ValidationError(_('Order id is not available')) + # for invoice in sale.invoice_ids: + # invoice.state = 'cancel' + # sale.room_lines.action_cancel() + # sale.order_id.action_cancel() + + @api.multi + def print_quotation(self): + pass + # TODO- New report to reservation order + # self.order_id.filtered(lambda s: s.state == 'draft').write({ + # 'state': 'sent', + # }) + # return self.env.ref('sale.report_saleorder').report_action(self, data=data) + + @api.multi + def action_confirm(self): + _logger.info('action_confirm') + + + """ + CHECKIN/OUT PROCESS + """ @api.multi def action_checks(self): self.ensure_one() @@ -386,25 +481,59 @@ class HotelFolio(models.Model): 'domain': [('reservation_id', 'in', rooms)], 'target': 'new', } + + @api.model + def daily_plan(self): + _logger.info('daily_plan') + self._cr.execute("update hotel_folio set checkins_reservations = 0, \ + checkouts_reservations = 0 where checkins_reservations > 0 \ + or checkouts_reservations > 0") + folios_in = self.env['hotel.folio'].search([ + ('room_lines.is_checkin', '=', True) + ]) + folios_out = self.env['hotel.folio'].search([ + ('room_lines.is_checkout', '=', True) + ]) + for fol in folios_in: + count_checkin = fol.room_lines.search_count([ + ('is_checkin', '=', True), ('folio_id.id', '=', fol.id) + ]) + fol.write({'checkins_reservations': count_checkin}) + for fol in folios_out: + count_checkout = fol.room_lines.search_count([ + ('is_checkout', '=', True), + ('folio_id.id', '=', fol.id) + ]) + fol.write({'checkouts_reservations': count_checkout}) + return True @api.multi - def action_folios_amount(self): - now_utc_dt = date_utils.now() - now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - reservations = self.env['hotel.reservation'].search([ - ('checkout', '<=', now_utc_str) - ]) - folio_ids = reservations.mapped('folio_id.id') - folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)]) - folios = folios.filtered(lambda r: r.invoices_amount > 0) - return { - 'name': _('Pending'), - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': 'hotel.folio', - 'type': 'ir.actions.act_window', - 'domain': [('id', 'in', folios.ids)] - } + def _compute_cardex_count(self): + _logger.info('_compute_cardex_amount') + for fol in self: + num_cardex = 0 + pending = False + if fol.reservation_type == 'normal': + for reser in fol.room_lines: + if reser.state != 'cancelled' and \ + not reser.parent_reservation: + num_cardex += len(reser.cardex_ids) + fol.cardex_count = num_cardex + pending = 0 + for reser in fol.room_lines: + if reser.state != 'cancelled' and \ + not reser.parent_reservation: + pending += (reser.adults + reser.children) \ + - len(reser.cardex_ids) + if pending <= 0: + fol.cardex_pending = False + else: + fol.cardex_pending = True + fol.cardex_pending_num = pending + + """ + MAILING PROCESS + """ @api.depends('room_lines') def _compute_has_confirmed_reservations_to_send(self): @@ -468,221 +597,6 @@ class HotelFolio(models.Model): break self.has_checkout_to_send = has_to_send - @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_amount') - for fol in self: - num_cardex = 0 - pending = False - if fol.reservation_type == 'normal': - for reser in fol.room_lines: - if reser.state != 'cancelled' and \ - not reser.parent_reservation: - num_cardex += len(reser.cardex_ids) - fol.cardex_count = num_cardex - pending = 0 - for reser in fol.room_lines: - if reser.state != 'cancelled' and \ - not reser.parent_reservation: - pending += (reser.adults + reser.children) \ - - len(reser.cardex_ids) - if pending <= 0: - fol.cardex_pending = False - else: - fol.cardex_pending = True - fol.cardex_pending_num = pending - - @api.multi - def go_to_currency_exchange(self): - ''' - when Money Exchange button is clicked then this method is called. - ------------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('go_to_currency_exchange') - pass - # cr, uid, context = self.env.args - # context = dict(context) - # for rec in self: - # if rec.partner_id.id and len(rec.room_lines) != 0: - # context.update({'folioid': rec.id, 'guest': rec.partner_id.id, - # 'room_no': rec.room_lines[0].product_id.name}) - # self.env.args = cr, uid, misc.frozendict(context) - # else: - # raise except_orm(_('Warning'), _('Please Reserve Any Room.')) - # return {'name': _('Currency Exchange'), - # 'res_model': 'currency.exchange', - # 'type': 'ir.actions.act_window', - # 'view_id': False, - # 'view_mode': 'form,tree', - # 'view_type': 'form', - # 'context': {'default_folio_no': context.get('folioid'), - # 'default_hotel_id': context.get('hotel'), - # 'default_guest_name': context.get('guest'), - # 'default_room_number': context.get('room_no') - # }, - # } - - @api.model - def create(self, vals, check=True): - """ - Overrides orm create method. - @param self: The object pointer - @param vals: dictionary of fields value. - @return: new record set for hotel folio. - """ - _logger.info('create') - if not 'service_line_ids' and 'folio_id' in vals: - tmp_room_lines = vals.get('room_lines', []) - vals['order_policy'] = vals.get('hotel_policy', 'manual') - vals.update({'room_lines': []}) - for line in (tmp_room_lines): - line[2].update({'folio_id': folio_id}) - vals.update({'room_lines': tmp_room_lines}) - folio_id = super(HotelFolio, self).create(vals) - else: - if not vals: - vals = {} - vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') - folio_id = super(HotelFolio, self).create(vals) - - return folio_id - - @api.multi - def write(self, vals): - if 'room_lines' in vals and vals['room_lines'][0][2] and 'reservation_line_ids' in vals['room_lines'][0][2] and vals['room_lines'][0][2]['reservation_line_ids'][0][0] == 5: - del vals['room_lines'] - return super(HotelFolio, self).write(vals) - - @api.multi - @api.onchange('partner_id') - def onchange_partner_id(self): - ''' - When you change partner_id it will update the partner_invoice_id, - partner_shipping_id and pricelist_id of the hotel folio as well - --------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('onchange_partner_id') - pass - # self.update({ - # 'currency_id': self.env.ref('base.main_company').currency_id, - # 'partner_invoice_id': self.partner_id and self.partner_id.id or False, - # 'partner_shipping_id': self.partner_id and self.partner_id.id or False, - # 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False, - # }) - # """ - # Warning messajes saved in partner form to folios - # """ - # if not self.partner_id: - # return - # warning = {} - # title = False - # message = False - # partner = self.partner_id - # - # # If partner has no warning, check its company - # if partner.sale_warn == 'no-message' and partner.parent_id: - # partner = partner.parent_id - # - # if partner.sale_warn != 'no-message': - # # Block if partner only has warning but parent company is blocked - # if partner.sale_warn != 'block' and partner.parent_id \ - # and partner.parent_id.sale_warn == 'block': - # partner = partner.parent_id - # title = _("Warning for %s") % partner.name - # message = partner.sale_warn_msg - # warning = { - # 'title': title, - # 'message': message, - # } - # if self.partner_id.sale_warn == 'block': - # self.update({ - # 'partner_id': False, - # 'partner_invoice_id': False, - # 'partner_shipping_id': False, - # 'pricelist_id': False - # }) - # return {'warning': warning} - # - # if warning: - # return {'warning': warning} - - @api.multi - def button_dummy(self): - ''' - @param self: object pointer - ''' - # for folio in self: - # folio.order_id.button_dummy() - return True - - @api.multi - def action_done(self): - for line in self.room_lines: - if line.state == "booking": - line.action_reservation_checkout() - - @api.multi - def action_invoice_create(self, grouped=False, states=None): - ''' - @param self: object pointer - ''' - pass - # if states is None: - # states = ['confirmed', 'done'] - # order_ids = [folio.order_id.id for folio in self] - # sale_obj = self.env['sale.order'].browse(order_ids) - # invoice_id = (sale_obj.action_invoice_create(grouped=False, - # states=['confirmed', - # 'done'])) - # for line in self: - # values = {'invoiced': True, - # 'state': 'progress' if grouped else 'progress', - # 'hotel_invoice_id': invoice_id - # } - # line.write(values) - # return invoice_id - - @api.multi - def advance_invoice(self): - pass - # order_ids = [folio.order_id.id for folio in self] - # sale_obj = self.env['sale.order'].browse(order_ids) - # invoices = action_invoice_create(self, grouped=True) - # return invoices - - @api.multi - def action_cancel(self): - ''' - @param self: object pointer - ''' - pass - # for sale in self: - # if not sale.order_id: - # raise ValidationError(_('Order id is not available')) - # for invoice in sale.invoice_ids: - # invoice.state = 'cancel' - # sale.room_lines.action_cancel() - # sale.order_id.action_cancel() - - - @api.multi - def action_confirm(self): - _logger.info('action_confirm') - - @api.multi - def print_quotation(self): - pass - # self.order_id.filtered(lambda s: s.state == 'draft').write({ - # 'state': 'sent', - # }) - # return self.env.ref('sale.report_saleorder').report_action(self, data=data) - - @api.multi - def action_cancel_draft(self): - _logger.info('action_confirm') - @api.multi def send_reservation_mail(self): ''' @@ -847,12 +761,6 @@ class HotelFolio(models.Model): template_rec.send_mail(reserv_rec.id, force_send=True) return True - @api.multi - def unlink(self): - # for record in self: - # record.order_id.unlink() - return super(HotelFolio, self).unlink() - @api.multi def get_grouped_reservations_json(self, state, import_all=False): self.ensure_one() @@ -882,3 +790,4 @@ class HotelFolio(models.Model): if not founded: info_grouped.append(vals) return sorted(sorted(info_grouped, key=lambda k: k['num'], reverse=True), key=lambda k: k['room_type']['id']) + diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 2330898ba..bd17a56f4 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -22,138 +22,6 @@ from odoo.addons import decimal_precision as dp class HotelReservation(models.Model): - @api.multi - def _generate_color(self): - self.ensure_one() - now_utc_dt = date_utils.now() - # unused variables - # diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin, - # hours=False) - # diff_checkout_now = date_utils.date_diff(now_utc_dt, self.checkout, - # hours=False) - - ir_values_obj = self.env['ir.default'] - reserv_color = '#FFFFFF' - reserv_color_text = '#000000' - # FIXME added for migration - return ('#4E9DC4', '#000000') - - if self.reservation_type == 'staff': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_staff') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_staff') - elif self.reservation_type == 'out': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_dontsell') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_dontsell') - elif self.to_assign: - reserv_color = ir_values_obj.get('res.config.settings', - 'color_to_assign') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_to_assign') - elif self.state == 'draft': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_pre_reservation') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_pre_reservation') - elif self.state == 'confirm': - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_reservation_pay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_reservation_pay') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_reservation') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_reservation') - elif self.state == 'booking': - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_stay_pay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_stay_pay') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_stay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_stay') - else: - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_checkout') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_checkout') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_payment_pending') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_payment_pending') - return (reserv_color, reserv_color_text) - - @api.depends('state', 'reservation_type', 'folio_id.invoices_amount', 'to_assign') - def _compute_color(self): - _logger.info('_compute_color') - for rec in self: - colors = rec._generate_color() - rec.update({ - 'reserve_color': colors[0], - 'reserve_color_text': colors[1], - }) - rec.folio_id.color = colors[0] - - # hotel_reserv_obj = self.env['hotel.reservation'] - # if rec.splitted: - # master_reservation = rec.parent_reservation or rec - # splitted_reservs = hotel_reserv_obj.search([ - # ('splitted', '=', True), - # '|', ('parent_reservation', '=', master_reservation.id), - # ('id', '=', master_reservation.id), - # ('folio_id', '=', rec.folio_id.id), - # ('id', '!=', rec.id), - # ]) - # splitted_reservs.write({'reserve_color': rec.reserve_color}) - - @api.multi - def copy(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - - return super(HotelReservation, self).copy(default=default) - - @api.multi - def _amount_line(self, field_name, arg): - ''' - @param self: object pointer - @param field_name: Names of fields. - @param arg: User defined arguments - ''' - return False - # return self.env['sale.order.line']._amount_line(field_name, arg) - - @api.multi - def _number_packages(self, field_name, arg): - ''' - @param self: object pointer - @param field_name: Names of fields. - @param arg: User defined arguments - ''' - return False - # return self.env['sale.order.line']._number_packages(field_name, arg) - - @api.multi - def set_call_center_user(self): - user = self.env['res.users'].browse(self.env.uid) - self.call_center = user.has_group('hotel.group_hotel_call') - def _get_default_checkin(self): folio = False if 'folio_id' in self._context: @@ -215,35 +83,43 @@ class HotelReservation(models.Model): if not(name == '' and operator == 'ilike'): args += [ '|', - ('folio_id.name', operator, name) - # FIXME Remove product inheritance - # ('product_id.name', operator, name) + ('folio_id.name', operator, name), + ('room_id.name', operator, name) ] return super(HotelReservation, self).name_search( name='', args=args, operator='ilike', limit=limit) @api.multi def name_get(self): - # FIXME Remove product inheritance result = [] for res in self: name = u'%s (%s)' % (res.folio_id.name, res.room_id.name) result.append((res.id, name)) return result - # # FIXME added for migration - # def _compute_qty_delivered_updateable(self): - # pass - # # FIXME added for migration - # def _compute_invoice_status(self): - # pass + @api.multi + def _computed_shared(self): + # Has this reservation more charges associates in folio?, Yes?, then, this is share folio ;) + for record in self: + if record.folio_id: + if len(record.folio_id.room_lines) > 1 or \ + record.folio_id.service_line_ids.filtered(lambda x: ( + x.ser_room_line != record.id)): + record.shared_folio = True + else: + record.shared_folio = False + + @api.depends('checkin', 'checkout') + def _computed_nights(self): + for res in self: + if res.checkin and res.checkout: + res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days _name = 'hotel.reservation' _description = 'Hotel Reservation' _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] _order = "last_updated_res desc, name" - # The record's name should now be used for description of the reservation ? name = fields.Text('Reservation Description', required=True) room_id = fields.Many2one('hotel.room', string='Room') @@ -305,9 +181,9 @@ class HotelReservation(models.Model): store=True) service_line_ids = fields.One2many('hotel.service', 'ser_room_line') - # pricelist_id = fields.Many2one('product.pricelist', - # related='folio_id.pricelist_id', - # readonly="1") + 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 @@ -327,22 +203,7 @@ class HotelReservation(models.Model): parent_reservation = fields.Many2one('hotel.reservation', 'Parent Reservation') overbooking = fields.Boolean('Is Overbooking', default=False) - # To show de total amount line in read_only mode - amount_reservation = fields.Float('Total', - compute='_computed_amount_reservation', - store=True) - amount_reservation_services = fields.Float('Services Amount', - compute='_computed_amount_reservation', - store=True) - amount_room = fields.Float('Amount Room', compute="_computed_amount_reservation", - store=True) - amount_discount = fields.Float('Room with Discount', compute="_computed_amount_reservation", - store=True) - discount_type = fields.Selection([ - ('percent', 'Percent'), - ('fixed', 'Fixed')], 'Discount Type', default=lambda *a: 'percent') - discount_fixed = fields.Float('Fixed Discount') - + nights = fields.Integer('Nights', compute='_computed_nights', store=True) channel_type = fields.Selection([ ('door', 'Door'), @@ -351,8 +212,7 @@ class HotelReservation(models.Model): ('call', 'Call Center'), ('web', 'Web')], 'Sales Channel', default='door') last_updated_res = fields.Datetime('Last Updated') - # Monetary to Float - folio_pending_amount = fields.Float(related='folio_id.invoices_amount') + folio_pending_amount = fields.Monetary(related='folio_id.pending_amount') segmentation_ids = fields.Many2many(related='folio_id.segmentation_ids') shared_folio = fields.Boolean(compute='_computed_shared') #Used to notify is the reservation folio has other reservations or services @@ -364,7 +224,6 @@ class HotelReservation(models.Model): folio_internal_comment = fields.Text(string='Internal Folio Notes', related='folio_id.internal_comment') preconfirm = fields.Boolean('Auto confirm to Save', default=True) - call_center = fields.Boolean(compute='set_call_center_user') to_send = fields.Boolean('To Send', default=True) has_confirmed_reservations_to_send = fields.Boolean( related='folio_id.has_confirmed_reservations_to_send', @@ -375,17 +234,14 @@ class HotelReservation(models.Model): has_checkout_to_send = fields.Boolean( related='folio_id.has_checkout_to_send', readonly=True) - # fix_total = fields.Boolean(compute='_compute_fix_total') - # fix_folio_pending = fields.Boolean(related='folio_id.fix_price') - # order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True) # product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product') # product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True) # product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0) - # currency_id = fields.Many2one('res.currency', - # related='pricelist_id.currency_id', - # string='Currency', readonly=True, required=True) + currency_id = fields.Many2one('res.currency', + related='pricelist_id.currency_id', + string='Currency', readonly=True, required=True) # invoice_status = fields.Selection([ # ('upselling', 'Upselling Opportunity'), # ('invoiced', 'Fully Invoiced'), @@ -401,113 +257,503 @@ class HotelReservation(models.Model): # digits=dp.get_precision('Product Unit of Measure')) # qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True) - price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0) - # Monetary to Float - price_subtotal = fields.Float(compute='_compute_amount', string='Subtotal', readonly=True, store=True) - # Monetary to Float - price_total = fields.Float(compute='_compute_amount', string='Total', readonly=True, store=True) - + price_subtotal = fields.Monetary(compute='_compute_amount_reservation', string='Subtotal', readonly=True, store=True) + price_total = fields.Monetary(compute='_compute_amount_reservation', string='Total', readonly=True, store=True) + price_tax = fields.Float(compute='_compute_amount_reservation', string='Taxes', readonly=True, store=True) + currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True) # FIXME discount per night - # discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) + discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) # analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') + @api.model + def create(self, vals): + if not 'reservation_type' in vals or not vals.get('reservation_type'): + vals.update({'reservation_type': 'normal'}) + if 'folio_id' in vals: + folio = self.env["hotel.folio"].browse(vals['folio_id']) + vals.update({'channel_type': folio.channel_type}) + elif 'partner_id' in vals: + folio_vals = {'partner_id':int(vals.get('partner_id')), + 'channel_type': vals.get('channel_type')} + # Create the folio in case of need (To Allow create direct reservations) + folio = self.env["hotel.folio"].create(folio_vals) + vals.update({'folio_id': folio.id, + 'reservation_type': vals.get('reservation_type'), + 'channel_type': vals.get('channel_type')}) + vals.update({ + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + }) + if folio: + record = super(HotelReservation, self).create(vals) + if (record.state == 'draft' and record.folio_id.state == 'sale') or \ + record.preconfirm: + record.confirm() + record._compute_color() + return record - def action_recalcule_payment(self): + @api.multi + def write(self, vals): for record in self: - for res in record.folio_id.room_lines: - res.on_change_checkin_checkout_product_id() + if ('checkin' in vals and record.checkin != vals['checkin']) or \ + ('checkout' in vals and record.checkout != vals['checkout']) or \ + ('state' in vals and record.state != vals['state']) : + vals.update({'to_send': True}) - def _computed_folio_name(self): - for res in self: - res.folio_name = res.folio_id.name + '-' + \ - res.folio_id.date_order + pricesChanged = ('checkin' in vals or \ + 'checkout' in vals or \ + 'discount' in vals) + import wdb; wdb.set_trace() + if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals: + vals.update({ + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + }) + res = super(HotelReservation, self).write(vals) + return res @api.multi - def send_reservation_mail(self): - return self.folio_id.send_reservation_mail() + def overbooking_button(self): + self.ensure_one() + return self.write({'overbooking': not self.overbooking}) @api.multi - def send_exit_mail(self): - return self.folio_id.send_exit_mail() + def open_folio(self): + action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0] + if self.folio_id: + action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')] + action['res_id'] = self.folio_id.id + else: + action = {'type': 'ir.actions.act_window_close'} + return action @api.multi - def send_cancel_mail(self): - return self.folio_id.send_cancel_mail() + def open_reservation_form(self): + action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] + action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] + action['res_id'] = self.id + return action @api.multi - def action_checks(self): + def generate_copy_values(self, checkin=False, checkout=False): self.ensure_one() return { - 'name': _('Cardexs'), - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': 'cardex', - 'type': 'ir.actions.act_window', - 'domain': [('reservation_id', '=', self.id)], - 'target': 'new', + 'name': self.name, + 'adults': self.adults, + 'children': self.children, + 'checkin': checkin or self.checkin, + 'checkout': checkout or self.checkout, + 'folio_id': self.folio_id.id, + # 'product_id': self.product_id.id, + 'parent_reservation': self.parent_reservation.id, + 'state': self.state, + 'overbooking': self.overbooking, + 'price_unit': self.price_unit, + 'splitted': self.splitted, + # 'virtual_room_id': self.virtual_room_id.id, + 'room_type_id': self.room_type_id.id, } - @api.multi - def _computed_shared(self): + @api.onchange('adults', 'room_id') + def check_capacity(self): + if self.room_id: + if self.room_id.capacity < self.adults: + self.adults = self.room_id.capacity + raise UserError( + _('%s people do not fit in this room! ;)') % (persons)) + + @api.constrains('adults') + def _check_adults(self): for record in self: - if record.folio_id: - if len(record.folio_id.room_lines) > 1 or \ - record.folio_id.service_line_ids.filtered(lambda x: ( - x.ser_room_line != record.id)): - record.shared_folio = True - else: - record.shared_folio = False + if record.adults > record.room_id.capacity: + raise ValidationError( + _("Reservation persons can't be higher than room capacity")) + if record.adults == 0: - @api.depends('checkin', 'checkout') - def _computed_nights(self): - for res in self: - if res.checkin and res.checkout: - res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days + raise ValidationError(_("Reservation has no adults")) - @api.depends('reservation_line_ids.price') - def _computed_amount_reservation(self): - _logger.info('_computed_amount_reservation') - # FIXME commented during migration - # import wdb; wdb.set_trace() - # for res in self: - # amount_service = amount_room = 0 - # for line in res.reservation_line_ids: - # amount_room += line.price - # for service in res.service_line_ids: - # # We must calc the line to can show the price in edit mode - # # on smartbutton whithout having to wait to save. - # total_line = service.price_unit * service.product_uom_qty - # discount = (service.discount * total_line) / 100 - # amount_service += total_line - discount - # res.amount_room = amount_room #To view price_unit with read_only - # if res.discount_type == 'fixed' and amount_room > 0: - # res.discount = (res.discount_fixed * 100) / amount_room # WARNING Posible division by zero - # else: - # res.discount_fixed = (res.discount * amount_room) / 100 - # res.amount_discount = amount_room - res.discount_fixed - # res.price_unit = amount_room - # res.amount_reservation_services = amount_service - # res.amount_reservation = res.amount_discount + amount_service #To the smartbutton + @api.onchange('room_type_id') + def on_change_room_type_id(self): + if self.room_type_id: + # TODO: Remove this check once added as contrain + if not self.checkin: + self.checkin = self._get_default_checkin() + if not self.checkout: + self.checkout = self._get_default_checkout() + # days_diff = date_utils.date_diff( + # self.checkin, self.checkout, hours=False) + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + rlines = self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices=True) + self.reservation_line_ids = rlines['commands'] - @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_count') - for res in self: - res.cardex_count = len(res.cardex_ids) - res.cardex_pending_num = (res.adults + res.children) \ - - len(res.cardex_ids) - if (res.adults + res.children - len(res.cardex_ids)) <= 0: - res.cardex_pending = False + if self.reservation_type in ['staff', 'out']: + self.price_unit = 0.0 + self.cardex_pending = 0 else: - res.cardex_pending = True + self.price_unit = rlines['total_price'] + + @api.onchange('checkin', 'checkout', 'room_id', + 'reservation_type', 'room_type_id') + def on_change_checkin_checkout_product_id(self): + _logger.info('on_change_checkin_checkout_product_id') + # import wdb; wdb.set_trace() + if not self.checkin: + self.checkin = self._get_default_checkin() + if not self.checkout: + self.checkout = self._get_default_checkout() + # WARNING Need a review + # if self.product_id: + # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] + # room = self.env['hotel.room'].search([ + # ('product_id', '=', self.product_id.id) + # ]) + # if self.adults == 0: + # self.adults = room.capacity + # if not self.virtual_room_id and room.price_virtual_room: + # self.virtual_room_id = room.price_virtual_room.id + if self.room_id: + # self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)] + if self.adults == 0: + self.adults = self.room_id.capacity + if not self.room_type_id: + self.room_type_id = self.room_id.room_type_id + self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] + + checkin_dt = fields.Date.from_string(self.checkin) + checkout_dt = fields.Date.from_string(self.checkout) + + if checkin_dt >= checkout_dt: + self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + + if self.state == 'confirm' and self.checkin_is_today(): + self.is_checkin = True + folio = self.env['hotel.folio'].browse(self.folio_id.id) + if folio: + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkin', '=', True) + ]) + + if self.state == 'booking' and self.checkout_is_today(): + self.is_checkout = False + folio = self.env['hotel.folio'].browse(self.folio_id.id) + if folio: + folio.checkouts_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkout', '=', True) + ]) + + # ensure checkin and checkout are correct before changing the name + if self.room_type_id: + checkin_str = checkin_dt.strftime('%d/%m/%Y') + checkout_str = checkout_dt.strftime('%d/%m/%Y') + self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ + + checkout_str + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + rlines = self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices=False) + self.reservation_line_ids = rlines['commands'] + + if self.reservation_type in ['staff', 'out']: + self.price_unit = 0.0 + self.cardex_pending = 0 + else: + self.price_unit = rlines['total_price'] - # 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): - recs = self.search([]).filtered(lambda x: x.cardex_pending is True) - if recs: - return [('id', 'in', [x.id for x in recs])] + # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? + @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') + def on_change_checkout(self): + ''' + When you change checkin or checkout it will checked it + and update the qty of hotel folio line + ----------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('on_change_checkout') + self.ensure_one() + if self.overbooking: + return + + occupied = self.env['hotel.reservation'].get_reservations( + self.checkin, + fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( + lambda r: r.id != self._origin.id) + rooms_occupied = occupied.mapped('room_id.id') + if self.room_id and self.room_id.id in rooms_occupied: + warning_msg = _('You tried to change \ + reservation with room those already reserved in this \ + reservation period') + raise ValidationError(warning_msg) + domain_rooms = [ + # ('isroom', '=', True), + ('id', 'not in', rooms_occupied) + ] + # if self.check_rooms: + # if self.room_type_id: + # domain_rooms.append( + # ('categ_id.id', '=', self.room_type_id.cat_id.id) + # ) + # if self.virtual_room_id: + # room_categories = self.virtual_room_id.room_type_ids.mapped( + # 'cat_id.id') + # link_virtual_rooms = self.virtual_room_id.room_ids\ + # | self.env['hotel.room'].search([ + # ('categ_id.id', 'in', room_categories)]) + # room_ids = link_virtual_rooms.mapped('room_id.id') + # domain_rooms.append(('id', 'in', room_ids)) + return {'domain': {'room_id': domain_rooms}} + + """ + COMPUTE RESERVE COLOR + """ + + @api.multi + def _generate_color(self): + self.ensure_one() + now_utc_dt = date_utils.now() + # unused variables + # diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin, + # hours=False) + # diff_checkout_now = date_utils.date_diff(now_utc_dt, self.checkout, + # hours=False) + + ir_values_obj = self.env['ir.default'] + reserv_color = '#FFFFFF' + reserv_color_text = '#000000' + # FIXME added for migration + return ('#4E9DC4', '#000000') + + if self.reservation_type == 'staff': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_staff') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_staff') + elif self.reservation_type == 'out': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_dontsell') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_dontsell') + elif self.to_assign: + reserv_color = ir_values_obj.get('res.config.settings', + 'color_to_assign') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_to_assign') + elif self.state == 'draft': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_pre_reservation') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_pre_reservation') + elif self.state == 'confirm': + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_reservation_pay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_reservation_pay') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_reservation') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_reservation') + elif self.state == 'booking': + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_stay_pay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_stay_pay') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_stay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_stay') + else: + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_checkout') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_checkout') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_payment_pending') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_payment_pending') + return (reserv_color, reserv_color_text) + + @api.depends('state', 'reservation_type', 'folio_id.pending_amount', 'to_assign') + def _compute_color(self): + _logger.info('_compute_color') + for rec in self: + colors = rec._generate_color() + rec.update({ + 'reserve_color': colors[0], + 'reserve_color_text': colors[1], + }) + rec.folio_id.color = colors[0] + + # hotel_reserv_obj = self.env['hotel.reservation'] + # if rec.splitted: + # master_reservation = rec.parent_reservation or rec + # splitted_reservs = hotel_reserv_obj.search([ + # ('splitted', '=', True), + # '|', ('parent_reservation', '=', master_reservation.id), + # ('id', '=', master_reservation.id), + # ('folio_id', '=', rec.folio_id.id), + # ('id', '!=', rec.id), + # ]) + # splitted_reservs.write({'reserve_color': rec.reserve_color}) + + """ + STATE WORKFLOW + """ + + @api.multi + def confirm(self): + ''' + @param self: object pointer + ''' + _logger.info('confirm') + hotel_folio_obj = self.env['hotel.folio'] + hotel_reserv_obj = self.env['hotel.reservation'] + for r in self: + vals = {} + if r.cardex_ids: + vals.update({'state': 'booking'}) + else: + vals.update({'state': 'confirm'}) + if r.checkin_is_today(): + vals.update({'is_checkin': True}) + folio = hotel_folio_obj.browse(r.folio_id.id) + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkin', '=', True)]) + r.write(vals) + + if r.splitted: + master_reservation = r.parent_reservation or r + splitted_reservs = hotel_reserv_obj.search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', r.folio_id.id), + ('id', '!=', r.id), + ('state', '!=', 'confirm') + ]) + splitted_reservs.confirm() + return True + + @api.multi + def button_done(self): + ''' + @param self: object pointer + ''' + for res in self: + res.action_reservation_checkout() + return True + + @api.multi + def action_cancel(self): + for record in self: + record.write({ + 'state': 'cancelled', + 'discount': 100.0, + }) + if record.checkin_is_today: + record.is_checkin = False + folio = self.env['hotel.folio'].browse(record.folio_id.id) + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), + ('is_checkin', '=', True) + ]) + + if record.splitted: + master_reservation = record.parent_reservation or record + splitted_reservs = self.env['hotel.reservation'].search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', record.folio_id.id), + ('id', '!=', record.id), + ('state', '!=', 'cancelled') + ]) + splitted_reservs.action_cancel() + record.folio_id.compute_amount() + + @api.multi + def draft(self): + for record in self: + record.write({'state': 'draft'}) + + if record.splitted: + master_reservation = record.parent_reservation or record + splitted_reservs = self.env['hotel.reservation'].search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', record.folio_id.id), + ('id', '!=', record.id), + ('state', '!=', 'draft') + ]) + splitted_reservs.draft() + + """ + PRICE PROCESS + """ + + @api.depends('reservation_line_ids.price', 'reservation_line_ids.discount', 'tax_id') + def _compute_amount_reservation(self): + """ + Compute the amounts of the reservation. + """ + for line in self: + amount_room = 0 + for day in line.reservation_line_ids: + amount_room += day.price + if amount_room > 0: + product = line.room_type_id.product_id + price = amount_room * (1 - (line.discount or 0.0) / 100.0) + taxes = line.tax_id.compute_all(price, line.currency_id, 1, product=product) + line.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'], + }) + + @api.multi + def prepare_reservation_lines(self, dfrom, days, + update_old_prices=False): + self.ensure_one() + total_price = 0.0 + cmds = [(5, False, False)] + #~ pricelist_id = self.env['ir.default'].sudo().get( + #~ 'res.config.settings', 'parity_pricelist_id') + pricelist_id = int(self.pricelist_id) + product_id = self.room_type_id.product_id + old_lines_days = self.mapped('reservation_line_ids.date') + for i in range(0, days): + idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) + if update_old_prices or (idate not in old_lines_days): + product = product_id.with_context( + lang=self.partner_id.lang, + partner=self.partner_id.id, + quantity=1, + date=idate, + pricelist=pricelist_id, + uom=self.room_type_id.product_id.uom_id.id) + line_price = self.env['account.tax']._fix_tax_included_price_company(product.price, product.taxes_id, self.tax_id, self.company_id) + else: + line = self.reservation_line_ids.filtered(lambda r: r.date == idate) + line_price = line.price + cmds.append((0, False, { + 'date': idate, + 'price': line_price + })) + total_price += line_price + return {'total_price': total_price, 'commands': cmds} @api.multi def action_pay_folio(self): @@ -540,6 +786,112 @@ class HotelReservation(models.Model): 'target': 'new', } + """ + AVAILABILTY PROCESS + """ + + @api.model + def get_reservations(self, dfrom, dto): + """ + @param self: The object pointer + @param dfrom: range date from + @param dto: range date to + @return: array with the reservations _confirmed_ between dfrom and dto + """ + domain = [('reservation_line_ids.date', '>=', dfrom), + ('reservation_line_ids.date', '<', dto), + ('state', '!=', 'cancelled'), + ('overbooking', '=', False)] + reservations = self.env['hotel.reservation'].search(domain) + return self.env['hotel.reservation'].search(domain) + + @api.model + def get_reservations_dates(self, dfrom, dto, room_type=False): + """ + @param self: The object pointer + @param dfrom: range date from + @param dto: range date to + @return: dictionary of lists with reservations (a hash of arrays!) + with the reservations dates between dfrom and dto + reservations_dates + {'2018-07-30': [hotel.reservation(29,), hotel.reservation(30,), + hotel.reservation(31,)], + '2018-07-31': [hotel.reservation(22,), hotel.reservation(35,), + hotel.reservation(36,)], + } + """ + domain = [('date', '>=', dfrom), + ('date', '<', dto)] + lines = self.env['hotel.reservation.line'].search(domain) + reservations_dates = {} + for record in lines: + # kumari.net/index.php/programming/programmingcat/22-python-making-a-dictionary-of-lists-a-hash-of-arrays + # reservations_dates.setdefault(record.date,[]).append(record.reservation_id.room_type_id) + reservations_dates.setdefault(record.date, []).append( + [record.reservation_id, record.reservation_id.room_type_id]) + return reservations_dates + + # TODO: Use default values on checkin /checkout is empty + @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') + def check_dates(self): + """ + 1.-When date_order is less then checkin date or + Checkout date should be greater than the checkin date. + 3.-Check the reservation dates are not occuped + """ + _logger.info('check_dates') + if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout): + raise ValidationError(_('Room line Check In Date Should be \ + less than the Check Out Date!')) + if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): + occupied = self.env['hotel.reservation'].get_reservations( + self.checkin, + self.checkout) + occupied = occupied.filtered( + lambda r: r.room_id.id == self.room_id.id + and r.id != self.id) + occupied_name = ','.join(str(x.room_id.name) for x in occupied) + if occupied: + warning_msg = _('You tried to change/confirm \ + reservation with room those already reserved in this \ + reservation period: %s ') % occupied_name + raise ValidationError(warning_msg) + + """ + CHECKIN/OUT PROCESS + """ + + @api.multi + def _compute_cardex_count(self): + _logger.info('_compute_cardex_count') + for res in self: + res.cardex_count = len(res.cardex_ids) + res.cardex_pending_num = (res.adults + res.children) \ + - len(res.cardex_ids) + if (res.adults + res.children - len(res.cardex_ids)) <= 0: + res.cardex_pending = False + else: + res.cardex_pending = True + + # 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): + recs = self.search([]).filtered(lambda x: x.cardex_pending is True) + if recs: + return [('id', 'in', [x.id for x in recs])] + + @api.multi + def action_reservation_checkout(self): + for record in self: + record.state = 'done' + if record.checkout_is_today(): + record.is_checkout = False + folio = self.env['hotel.folio'].browse(self.folio_id.id) + folio.checkouts_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), + ('is_checkout', '=', True) + ]) + @api.model def daily_plan(self): _logger.info('daily_plan') @@ -598,93 +950,21 @@ class HotelReservation(models.Model): return self.checkout == today @api.multi - def action_cancel(self): - for record in self: - record.write({ - 'state': 'cancelled', - 'discount': 100.0, - }) - if record.checkin_is_today: - record.is_checkin = False - folio = self.env['hotel.folio'].browse(record.folio_id.id) - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), - ('is_checkin', '=', True) - ]) - - if record.splitted: - master_reservation = record.parent_reservation or record - splitted_reservs = self.env['hotel.reservation'].search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', record.folio_id.id), - ('id', '!=', record.id), - ('state', '!=', 'cancelled') - ]) - splitted_reservs.action_cancel() - record.folio_id.compute_invoices_amount() - - @api.multi - def draft(self): - for record in self: - record.write({'state': 'draft'}) - - if record.splitted: - master_reservation = record.parent_reservation or record - splitted_reservs = self.env['hotel.reservation'].search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', record.folio_id.id), - ('id', '!=', record.id), - ('state', '!=', 'draft') - ]) - splitted_reservs.draft() - - @api.multi - def action_reservation_checkout(self): - for record in self: - record.state = 'done' - if record.checkout_is_today(): - record.is_checkout = False - folio = self.env['hotel.folio'].browse(self.folio_id.id) - folio.checkouts_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), - ('is_checkout', '=', True) - ]) - - @api.multi - def overbooking_button(self): + def action_checks(self): self.ensure_one() - return self.write({'overbooking': not self.overbooking}) + return { + 'name': _('Cardexs'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'cardex', + 'type': 'ir.actions.act_window', + 'domain': [('reservation_id', '=', self.id)], + 'target': 'new', + } - @api.multi - def open_master(self): - self.ensure_one() - if not self.parent_reservation: - raise ValidationError(_("This is the parent reservation")) - action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] - action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] - action['res_id'] = self.parent_reservation.id - return action - - @api.multi - def open_folio(self): - action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0] - if self.folio_id: - action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')] - action['res_id'] = self.folio_id.id - else: - action = {'type': 'ir.actions.act_window_close'} - return action - - @api.multi - def open_reservation_form(self): - action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] - action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] - action['res_id'] = self.id - return action + """ + RESERVATION SPLITTED + """ @api.multi def get_real_checkin_checkout(self): @@ -780,483 +1060,30 @@ class HotelReservation(models.Model): # This function generate a usable dictionary with reservation values # for copy purposes. # ''' + @api.multi - def generate_copy_values(self, checkin=False, checkout=False): + def open_master(self): self.ensure_one() - return { - 'name': self.name, - 'adults': self.adults, - 'children': self.children, - 'checkin': checkin or self.checkin, - 'checkout': checkout or self.checkout, - 'folio_id': self.folio_id.id, - # 'product_id': self.product_id.id, - 'parent_reservation': self.parent_reservation.id, - 'state': self.state, - 'overbooking': self.overbooking, - 'price_unit': self.price_unit, - 'splitted': self.splitted, - # 'virtual_room_id': self.virtual_room_id.id, - 'room_type_id': self.room_type_id.id, - } + if not self.parent_reservation: + raise ValidationError(_("This is the parent reservation")) + action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] + action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] + action['res_id'] = self.parent_reservation.id + return action - @api.model - def create(self, vals): - """ - Overrides orm create method. - @param self: The object pointer - @param vals: dictionary of fields value. - @return: new record set for hotel folio line. - """ - # import wdb; wdb.set_trace() - if not 'reservation_type' in vals or not vals.get('reservation_type'): - vals.update({'reservation_type': 'normal'}) - if 'folio_id' in vals: - folio = self.env["hotel.folio"].browse(vals['folio_id']) - vals.update({'channel_type': folio.channel_type}) - elif 'partner_id' in vals: - folio_vals = {'partner_id':int(vals.get('partner_id')), - 'channel_type': vals.get('channel_type')} - # Create the folio in case of need - folio = self.env["hotel.folio"].create(folio_vals) - vals.update({'folio_id': folio.id, - 'reservation_type': vals.get('reservation_type'), - 'channel_type': vals.get('channel_type')}) - user = self.env['res.users'].browse(self.env.uid) - if user.has_group('hotel.group_hotel_call'): - vals.update({'to_assign': True, - 'channel_type': 'call'}) - vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - }) - if folio: - record = super(HotelReservation, self).create(vals) - # TODO: Check Capacity should be done before creating the Folio - if record.adults > record.room_id.capacity: - raise ValidationError( - _("Reservation persons can't be higher than room capacity")) - if record.adults == 0: - raise ValidationError(_("Reservation has no adults")) - if (record.state == 'draft' and record.folio_id.state == 'sale') or \ - record.preconfirm: - record.confirm() - record._compute_color() - return record + """ + MAILING PROCESS + """ @api.multi - def write(self, vals): - for record in self: - if ('checkin' in vals and record.checkin != vals['checkin']) or \ - ('checkout' in vals and record.checkout != vals['checkout']) or \ - ('state' in vals and record.state != vals['state']) or \ - ('amount_discount' in vals and record.amount_discount != vals['amount_discount']): - vals.update({'to_send': True}) - - pricesChanged = ('checkin' in vals or \ - 'checkout' in vals or \ - 'discount' in vals) - # vals.update({ - # 'edit_room': False, - # }) - # if pricesChanged or 'state' in vals or 'virtual_room_id' in vals or 'to_assign' in vals: - if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals: - vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - }) - user = self.env['res.users'].browse(self.env.uid) - if user.has_group('hotel.group_hotel_call'): - vals.update({ - 'to_read': True, - 'to_assign': True, - }) - res = super(HotelReservation, self).write(vals) - if pricesChanged: - for record in self: - if record.reservation_type in ('staff', 'out'): - record.update({'price_unit': 0}) - record.folio_id.compute_invoices_amount() - checkin = vals.get('checkin', record.checkin) - checkout = vals.get('checkout', record.checkout) - # days_diff = date_utils.date_diff(checkin, - # checkout, hours=False) - days_diff = (fields.Date.from_string(checkout) - fields.Date.from_string(checkin)).days - rlines = record.prepare_reservation_lines(checkin, days_diff) - record.update({ - 'reservation_line_ids': rlines['commands'], - 'price_unit': rlines['total_price'], - }) - return res - - # FIXME add room.id to on change after removing inheritance - @api.onchange('adults', 'children') - def check_capacity(self): - if self.room_id: - persons = self.adults + self.children - if self.room_id.capacity < persons: - self.adults = self.room_id.capacity - self.children = 0 - raise UserError( - _('%s people do not fit in this room! ;)') % (persons)) - - @api.onchange('room_type_id') - def on_change_room_type_id(self): - # import wdb; wdb.set_trace() - # TODO: Remove this check once added as contrain - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # days_diff = date_utils.date_diff( - # self.checkin, self.checkout, hours=False) - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=True) - self.reservation_line_ids = rlines['commands'] - - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] - - @api.onchange('checkin', 'checkout', 'room_id', - 'reservation_type', 'room_type_id') - def on_change_checkin_checkout_product_id(self): - _logger.info('on_change_checkin_checkout_product_id') - # import wdb; wdb.set_trace() - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # WARNING Need a review - # if self.product_id: - # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] - # room = self.env['hotel.room'].search([ - # ('product_id', '=', self.product_id.id) - # ]) - # if self.adults == 0: - # self.adults = room.capacity - # if not self.virtual_room_id and room.price_virtual_room: - # self.virtual_room_id = room.price_virtual_room.id - if self.room_id: - # self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)] - if self.adults == 0: - self.adults = self.room_id.capacity - if not self.room_type_id: - self.room_type_id = self.room_id.room_type_id - self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] - - checkin_dt = fields.Date.from_string(self.checkin) - checkout_dt = fields.Date.from_string(self.checkout) - - if checkin_dt >= checkout_dt: - self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) - # ensure checkin and checkout are correct before changing the name - if self.room_type_id: - checkin_str = checkin_dt.strftime('%d/%m/%Y') - checkout_str = checkout_dt.strftime('%d/%m/%Y') - self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ - + checkout_str - - if self.state == 'confirm' and self.checkin_is_today(): - self.is_checkin = True - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkin', '=', True) - ]) - - if self.state == 'booking' and self.checkout_is_today(): - self.is_checkout = False - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkouts_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkout', '=', True) - ]) - - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=False) - self.reservation_line_ids = rlines['commands'] - - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] - - # FIXME add room.id to on change after removing inheritance - @api.model - def get_availability(self, checkin, checkout, dbchanged=True, - dtformat=DEFAULT_SERVER_DATE_FORMAT): - _logger.info('get_availability') - date_start = date_utils.get_datetime(checkin) - date_end = date_utils.get_datetime(checkout) - # Not count end day of the reservation - date_diff = date_utils.date_diff(date_start, date_end, hours=False) - - hotel_vroom_obj = self.env['hotel.room.type'] - # virtual_room_avail_obj = self.env['hotel.room.type.availability'] - - rooms_avail = [] - # FIXME con una relacion Many2one, cada habitacion está en un solo tipo - # por lo que la disponibilidad para la habitación se tiene que buscar - # directamente en ese tipo - # vrooms = hotel_vroom_obj.search([ - # ('room_ids.product_id', '=', self.room_id) - # ]) - # FIXME Si lo de arriba es cierto, este bucle sobra. Sólo hay un room_type_id - for vroom in self.room_type_id: - rdays = [] - for i in range(0, date_diff): - ndate_dt = date_start + timedelta(days=i) - ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) - avail = len(hotel_vroom_obj.check_availability_virtual_room( - ndate_str, - ndate_str, - room_type_id=vroom.id)) - if not dbchanged: - avail = avail - 1 - # Can be less than zero because 'avail' can not equal - # with the real 'avail' (ex. Online Limits) - avail = max(min(avail, vroom.total_rooms_count), 0) - rdays.append({ - 'date': ndate_dt.strftime(dtformat), - 'avail': avail, - }) - ravail = {'id': vroom.id, 'days': rdays} - rooms_avail.append(ravail) - - return rooms_avail + def send_reservation_mail(self): + return self.folio_id.send_reservation_mail() @api.multi - def prepare_reservation_lines(self, dfrom, days, - update_old_prices=False): - self.ensure_one() - total_price = 0.0 - cmds = [(5, False, False)] - # import wdb; - # wdb.set_trace() - # TO-DO: Redesign relation between hotel.reservation - # and sale.order.line to allow manage days by units in order - #~ if self.invoice_status == 'invoiced' and not self.splitted: - #~ raise ValidationError(_("This reservation is already invoiced. \ - #~ To expand it you must create a new reservation.")) - # hotel_tz = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'hotel_tz') - # start_date_utc_dt = date_utils.get_datetime(str_start_date_utc) - # start_date_dt = date_utils.dt_as_timezone(start_date_utc_dt, hotel_tz) - - # room = self.env['hotel.room'].search([ - # ('product_id', '=', self.product_id.id) - # ]) - # product_id = self.room_id.sale_price_type == 'vroom' and self.room_id.price_virtual_room.product_id - product_id = self.room_type_id.product_id - pricelist_id = self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_pricelist_id') - if pricelist_id: - pricelist_id = int(pricelist_id) - old_lines_days = self.mapped('reservation_line_ids.date') - for i in range(0, days): - idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) - if update_old_prices or idate not in old_lines_days: - # prod = product_id.with_context( - # lang=self.partner_id.lang, - # partner=self.partner_id.id, - # quantity=1, - # date=ndate_str, - # pricelist=pricelist_id, - # uom=self.product_uom.id) - # prod = product_id.with_context( - # lang=self.partner_id.lang, - # partner=self.partner_id.id, - # quantity=1, - # date=ndate_str, # AttributeError("'product.product' object has no attribute 'date'",) - # pricelist=pricelist_id) - # line_price = prod.price - line_price = product_id.list_price - else: - line = self.reservation_line_ids.filtered(lambda r: r.date == idate) - line_price = line.price - cmds.append((0, False, { - 'date': idate, - 'price': line_price - })) - total_price += line_price - return {'total_price': total_price, 'commands': cmds} - - @api.constrains('adults') - def _check_adults(self): - if self.adults == 0 and self.room_id and self.room_id.capacity > 0: - self.adults = self.room_id.capacity + def send_exit_mail(self): + return self.folio_id.send_exit_mail() @api.multi - # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? - @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') - def on_change_checkout(self): - ''' - When you change checkin or checkout it will checked it - and update the qty of hotel folio line - ----------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('on_change_checkout') - self.ensure_one() - if self.overbooking: - return + def send_cancel_mail(self): + return self.folio_id.send_cancel_mail() - occupied = self.env['hotel.reservation'].get_reservations( - self.checkin, - fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( - lambda r: r.id != self._origin.id) - rooms_occupied = occupied.mapped('room_id.id') - if self.room_id and self.room_id.id in rooms_occupied: - warning_msg = _('You tried to change \ - reservation with room those already reserved in this \ - reservation period') - raise ValidationError(warning_msg) - domain_rooms = [ - # ('isroom', '=', True), - ('id', 'not in', rooms_occupied) - ] - # if self.check_rooms: - # if self.room_type_id: - # domain_rooms.append( - # ('categ_id.id', '=', self.room_type_id.cat_id.id) - # ) - # if self.virtual_room_id: - # room_categories = self.virtual_room_id.room_type_ids.mapped( - # 'cat_id.id') - # link_virtual_rooms = self.virtual_room_id.room_ids\ - # | self.env['hotel.room'].search([ - # ('categ_id.id', 'in', room_categories)]) - # room_ids = link_virtual_rooms.mapped('room_id.id') - # domain_rooms.append(('id', 'in', room_ids)) - return {'domain': {'room_id': domain_rooms}} - - @api.multi - def confirm(self): - ''' - @param self: object pointer - ''' - _logger.info('confirm') - hotel_folio_obj = self.env['hotel.folio'] - hotel_reserv_obj = self.env['hotel.reservation'] - for r in self: - vals = {} - if r.cardex_ids: - vals.update({'state': 'booking'}) - else: - vals.update({'state': 'confirm'}) - if r.checkin_is_today(): - vals.update({'is_checkin': True}) - folio = hotel_folio_obj.browse(r.folio_id.id) - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkin', '=', True)]) - r.write(vals) - - if r.splitted: - master_reservation = r.parent_reservation or r - splitted_reservs = hotel_reserv_obj.search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', r.folio_id.id), - ('id', '!=', r.id), - ('state', '!=', 'confirm') - ]) - splitted_reservs.confirm() - return True - - @api.multi - def button_done(self): - ''' - @param self: object pointer - ''' - for res in self: - res.action_reservation_checkout() - return True - - @api.one - def copy_data(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - return False - # FIXME added for migration - # line_id = self.order_line_id.id - # sale_line_obj = self.env['sale.order.line'].browse(line_id) - # return sale_line_obj.copy_data(default=default) - - # TODO: Use default values on checkin /checkout is empty - @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') - def check_dates(self): - """ - 1.-When date_order is less then checkin date or - Checkout date should be greater than the checkin date. - 3.-Check the reservation dates are not occuped - """ - _logger.info('check_dates') - if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout): - raise ValidationError(_('Room line Check In Date Should be \ - less than the Check Out Date!')) - if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): - occupied = self.env['hotel.reservation'].get_reservations( - self.checkin, - self.checkout) - occupied = occupied.filtered( - lambda r: r.room_id.id == self.room_id.id - and r.id != self.id) - occupied_name = ','.join(str(x.room_id.name) for x in occupied) - if occupied: - warning_msg = _('You tried to change/confirm \ - reservation with room those already reserved in this \ - reservation period: %s ') % occupied_name - raise ValidationError(warning_msg) - - @api.model - def get_reservations(self, dfrom, dto): - """ - @param self: The object pointer - @param dfrom: range date from - @param dto: range date to - @return: array with the reservations _confirmed_ between dfrom and dto - """ - # QUESTION dto must be strictly < - domain = [('reservation_line_ids.date', '>=', dfrom), - ('reservation_line_ids.date', '<', dto), - ('state', '!=', 'cancelled'), - ('overbooking', '=', False)] - reservations = self.env['hotel.reservation'].search(domain) - return self.env['hotel.reservation'].search(domain) - - @api.model - def get_reservations_dates(self, dfrom, dto): - """ - @param self: The object pointer - @param dfrom: range date from - @param dto: range date to - @return: dictionary of lists with reservations (a hash of arrays!) - with the reservations dates between dfrom and dto - reservations_dates - {'2018-07-30': [[hotel.reservation(29,), hotel.room.type(1,)], - [hotel.reservation(30,), hotel.room.type(1,)], - [hotel.reservation(31,), hotel.room.type(3,)]], - '2018-07-31': [[hotel.reservation(30,), hotel.room.type(1,)], - [hotel.reservation(31,), hotel.room.type(3,)]]} - """ - domain = [('date', '>=', dfrom), - ('date', '<', dto)] - lines = self.env['hotel.reservation.line'].search(domain) - reservations_dates = {} - for record in lines: - # kumari.net/index.php/programming/programmingcat/22-python-making-a-dictionary-of-lists-a-hash-of-arrays - # reservations_dates.setdefault(record.date,[]).append(record.reservation_id.room_type_id) - reservations_dates.setdefault(record.date, []).append( - [record.reservation_id, record.reservation_id.room_type_id]) - return reservations_dates diff --git a/hotel/models/hotel_room.py b/hotel/models/hotel_room.py index 27d452841..a0d799cfa 100644 --- a/hotel/models/hotel_room.py +++ b/hotel/models/hotel_room.py @@ -11,18 +11,12 @@ class HotelRoom(models.Model): """ _name = 'hotel.room' _description = 'Hotel Room' - # The record's name - name = fields.Char('Room Name', required=True) - # Used for activate records - active = fields.Boolean('Active', default=True) - # Used for ordering - sequence = fields.Integer('Sequence', default=0) - _order = "sequence, room_type_id, name" - - # each room has only one type (Many2one) + + name = fields.Char('Room Name', required=True) + active = fields.Boolean('Active', default=True) + sequence = fields.Integer('Sequence', default=0) room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type') - floor_id = fields.Many2one('hotel.floor', 'Ubication', help='At which floor the room is located.') # TODO Q. Should the amenities be on the Room Type ? - @@ -30,16 +24,12 @@ class HotelRoom(models.Model): 'room_amenities', 'rcateg_id', string='Room Amenities', help='List of room amenities.') - # max number of adults and children per room max_adult = fields.Integer('Max Adult') max_child = fields.Integer('Max Child') - # maximum capacity of the room 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) - description_sale = fields.Text( 'Sale Description', translate=True, help="A description of the Product that you want to communicate to " diff --git a/hotel/models/inherit_account_payment.py b/hotel/models/inherit_account_payment.py index d111cdc6d..687ba0db7 100644 --- a/hotel/models/inherit_account_payment.py +++ b/hotel/models/inherit_account_payment.py @@ -90,5 +90,5 @@ class AccountPayment(models.Model): raise except_orm(_('Warning'), _('This pay is related with \ more than one Reservation.')) else: - fol.compute_invoices_amount() + fol.compute_amount() return res diff --git a/hotel/models/inherit_payment_return.py b/hotel/models/inherit_payment_return.py index b19d47260..d4188b7c7 100644 --- a/hotel/models/inherit_payment_return.py +++ b/hotel/models/inherit_payment_return.py @@ -36,4 +36,4 @@ class PaymentReturn(models.Model): payments = self.env['account.payment'].search([('move_line_ids','in',line.move_line_ids.ids)]) folio_ids += payments.mapped('folio_id.id') folios = self.env['hotel.folio'].browse(folio_ids) - folios.compute_invoices_amount() + folios.compute_amount() diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio.xml index 3ac843ffb..52f05db24 100644 --- a/hotel/views/hotel_folio.xml +++ b/hotel/views/hotel_folio.xml @@ -84,10 +84,10 @@ id="payment_smart_button" icon="fa-money" name="action_pay" - attrs="{'invisible': ['|',('invoices_amount','<=',0)]}"> + attrs="{'invisible': ['|',('pending_amount','<=',0)]}">
- Pending Payment @@ -221,8 +221,8 @@ - - + +
+ + - + - - - - - + +
@@ -265,7 +265,7 @@ - + @@ -415,10 +415,10 @@ /> Date: Tue, 28 Aug 2018 17:41:24 +0200 Subject: [PATCH 26/28] TEST --- hotel/models/hotel_folio.py | 76 ++--- hotel/models/hotel_reservation.py | 380 ++++++++++++------------- hotel/models/hotel_reservation_line.py | 11 + 3 files changed, 225 insertions(+), 242 deletions(-) diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 2169bc68e..528777354 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -342,56 +342,32 @@ class HotelFolio(models.Model): @api.multi @api.onchange('partner_id') def onchange_partner_id(self): - ''' - When you change partner_id it will update the partner_invoice_id, - partner_shipping_id and pricelist_id of the hotel folio as well - --------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('onchange_partner_id') - pass - # self.update({ - # 'currency_id': self.env.ref('base.main_company').currency_id, - # 'partner_invoice_id': self.partner_id and self.partner_id.id or False, - # 'partner_shipping_id': self.partner_id and self.partner_id.id or False, - # 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False, - # }) - # """ - # Warning messajes saved in partner form to folios - # """ - # if not self.partner_id: - # return - # warning = {} - # title = False - # message = False - # partner = self.partner_id - # - # # If partner has no warning, check its company - # if partner.sale_warn == 'no-message' and partner.parent_id: - # partner = partner.parent_id - # - # if partner.sale_warn != 'no-message': - # # Block if partner only has warning but parent company is blocked - # if partner.sale_warn != 'block' and partner.parent_id \ - # and partner.parent_id.sale_warn == 'block': - # partner = partner.parent_id - # title = _("Warning for %s") % partner.name - # message = partner.sale_warn_msg - # warning = { - # 'title': title, - # 'message': message, - # } - # if self.partner_id.sale_warn == 'block': - # self.update({ - # 'partner_id': False, - # 'partner_invoice_id': False, - # 'partner_shipping_id': False, - # 'pricelist_id': False - # }) - # return {'warning': warning} - # - # if warning: - # return {'warning': warning} + """ + Update the following fields when the partner is changed: + - Pricelist + - Invoice address + - user_id + """ + if not self.partner_id: + self.update({ + 'partner_invoice_id': False, + 'payment_term_id': False, + 'fiscal_position_id': False, + }) + return + + addr = self.partner_id.address_get(['invoice']) + values = { + 'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or False, + 'partner_invoice_id': addr['invoice'], + 'user_id': self.partner_id.user_id.id or self.env.uid + } + 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(lang=self.partner_id.lang).env.user.company_id.sale_note + + if self.partner_id.team_id: + values['team_id'] = self.partner_id.team_id.id + self.update(values) @api.multi def action_invoice_create(self, grouped=False, states=None): diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index bd17a56f4..c84fdb191 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -257,9 +257,9 @@ class HotelReservation(models.Model): # digits=dp.get_precision('Product Unit of Measure')) # qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True) - price_subtotal = fields.Monetary(compute='_compute_amount_reservation', string='Subtotal', readonly=True, store=True) - price_total = fields.Monetary(compute='_compute_amount_reservation', string='Total', readonly=True, store=True) - price_tax = fields.Float(compute='_compute_amount_reservation', string='Taxes', readonly=True, store=True) + price_subtotal = fields.Monetary(string='Subtotal', readonly=True, store=True) + price_total = fields.Monetary(string='Total', readonly=True, store=True) + price_tax = fields.Float(string='Taxes', readonly=True, store=True) currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True) # FIXME discount per night discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) @@ -268,49 +268,73 @@ class HotelReservation(models.Model): @api.model def create(self, vals): - if not 'reservation_type' in vals or not vals.get('reservation_type'): - vals.update({'reservation_type': 'normal'}) + vals.update(self._prepare_add_missing_fields(vals)) if 'folio_id' in vals: folio = self.env["hotel.folio"].browse(vals['folio_id']) vals.update({'channel_type': folio.channel_type}) elif 'partner_id' in vals: folio_vals = {'partner_id':int(vals.get('partner_id')), 'channel_type': vals.get('channel_type')} - # Create the folio in case of need (To Allow create direct reservations) + # Create the folio in case of need (To allow to create reservations direct) folio = self.env["hotel.folio"].create(folio_vals) vals.update({'folio_id': folio.id, 'reservation_type': vals.get('reservation_type'), 'channel_type': vals.get('channel_type')}) + #~ colors = self._generate_color() vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT), + #~ 'reserve_color': colors[0], + #~ 'reserve_color_text': colors[1], }) - if folio: - record = super(HotelReservation, self).create(vals) - if (record.state == 'draft' and record.folio_id.state == 'sale') or \ - record.preconfirm: - record.confirm() - record._compute_color() + if self.compute_price_out_vals(vals): + vals.update(self.env['hotel.reservation'].compute_amount_reservation(vals)) + record = super(HotelReservation, self).create(vals) + #~ if (record.state == 'draft' and record.folio_id.state == 'sale') or \ + #~ record.preconfirm: + #~ record.confirm() return record @api.multi def write(self, vals): - for record in self: - if ('checkin' in vals and record.checkin != vals['checkin']) or \ - ('checkout' in vals and record.checkout != vals['checkout']) or \ - ('state' in vals and record.state != vals['state']) : - vals.update({'to_send': True}) - - pricesChanged = ('checkin' in vals or \ - 'checkout' in vals or \ - 'discount' in vals) - import wdb; wdb.set_trace() - if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals: - vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - }) + #~ if self.notify_update(vals): + #~ vals.update({ + #~ 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + #~ }) + #~ for record in self: + #~ if record.compute_price_out_vals(vals): + #~ record.update(self.compute_amount_reservation(vals)) + #~ if ('checkin' in vals and record.checkin != vals['checkin']) or \ + #~ ('checkout' in vals and record.checkout != vals['checkout']) or \ + #~ ('state' in vals and record.state != vals['state']) : + #~ vals.update({'to_send': True}) res = super(HotelReservation, self).write(vals) return res + @api.model + def _prepare_add_missing_fields(self, values): + """ Deduce missing required fields from the onchange """ + res = {} + onchange_fields = ['room_type_id', 'pricelist_id', + 'reservation_type', 'currency_id'] + if values.get('partner_id') and values.get('room_type_id') and any(f not in values for f in onchange_fields): + line = self.new(values) + line.room_type_id_change() + for field in onchange_fields: + if field not in values: + res[field] = line._fields[field].convert_to_write(line[field], line) + return res + + @api.multi + def notify_update(self, vals): + if 'checkin' in vals or \ + 'checkout' in vals or \ + 'discount' in vals or \ + 'state' in vals or \ + 'room_type_id' in vals or \ + 'to_assign' in vals: + return True + return False + @api.multi def overbooking_button(self): self.ensure_one() @@ -353,14 +377,6 @@ class HotelReservation(models.Model): 'room_type_id': self.room_type_id.id, } - @api.onchange('adults', 'room_id') - def check_capacity(self): - if self.room_id: - if self.room_id.capacity < self.adults: - self.adults = self.room_id.capacity - raise UserError( - _('%s people do not fit in this room! ;)') % (persons)) - @api.constrains('adults') def _check_adults(self): for record in self: @@ -368,146 +384,84 @@ class HotelReservation(models.Model): raise ValidationError( _("Reservation persons can't be higher than room capacity")) if record.adults == 0: - raise ValidationError(_("Reservation has no adults")) - @api.onchange('room_type_id') - def on_change_room_type_id(self): - if self.room_type_id: - # TODO: Remove this check once added as contrain - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # days_diff = date_utils.date_diff( - # self.checkin, self.checkout, hours=False) - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=True) - self.reservation_line_ids = rlines['commands'] + """ + ONCHANGES ---------------------------------------------------------- + """ - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] - - @api.onchange('checkin', 'checkout', 'room_id', - 'reservation_type', 'room_type_id') - def on_change_checkin_checkout_product_id(self): - _logger.info('on_change_checkin_checkout_product_id') - # import wdb; wdb.set_trace() - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # WARNING Need a review - # if self.product_id: - # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] - # room = self.env['hotel.room'].search([ - # ('product_id', '=', self.product_id.id) - # ]) - # if self.adults == 0: - # self.adults = room.capacity - # if not self.virtual_room_id and room.price_virtual_room: - # self.virtual_room_id = room.price_virtual_room.id + @api.onchange('adults', 'room_id') + def onchange_check_capacity(self): if self.room_id: - # self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)] - if self.adults == 0: + if self.room_id.capacity < self.adults: self.adults = self.room_id.capacity - if not self.room_type_id: - self.room_type_id = self.room_id.room_type_id - self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] + raise UserError( + _('%s people do not fit in this room! ;)') % (persons)) + + @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 self.partner_id.property_product_pricelist.id or \ + self.env['ir.default'].sudo().get('hotel.config.settings', 'parity_pricelist_id'), + } + self.update(values) + + # When we need to overwrite the prices even if they were already established + @api.onchange('room_type_id', 'pricelist_id', 'reservation_type') + def onchange_overwrite_price_by_day(self): + self.update(self.compute_amount_reservation(update_old_prices=True)) + + # When we need to update prices respecting those that were already established + @api.onchange('checkin', 'checkout') + def onchange_check_dates(self): + if not self.checkin: + self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + if not self.checkout: + self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) checkin_dt = fields.Date.from_string(self.checkin) checkout_dt = fields.Date.from_string(self.checkout) - if checkin_dt >= checkout_dt: self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + self.update(self.compute_amount_reservation(update_old_prices=True)) + + - if self.state == 'confirm' and self.checkin_is_today(): - self.is_checkin = True - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkin', '=', True) - ]) - - if self.state == 'booking' and self.checkout_is_today(): - self.is_checkout = False - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkouts_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkout', '=', True) - ]) - - # ensure checkin and checkout are correct before changing the name - if self.room_type_id: + @api.onchange('checkin', 'checkout', 'room_type_id') + def onchange_compute_reservation_description(self): + if self.room_type_id and self.checkin and self.checkout: + checkin_dt = fields.Date.from_string(self.checkin) + checkout_dt = fields.Date.from_string(self.checkout) checkin_str = checkin_dt.strftime('%d/%m/%Y') checkout_str = checkout_dt.strftime('%d/%m/%Y') self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ + checkout_str - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=False) - self.reservation_line_ids = rlines['commands'] - - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] @api.multi - # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? - @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') - def on_change_checkout(self): - ''' - When you change checkin or checkout it will checked it - and update the qty of hotel folio line - ----------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('on_change_checkout') + @api.onchange('checkin', 'checkout', 'room_id') + def onchange_room_availabiltiy_domain(self): self.ensure_one() - if self.overbooking: - return - - occupied = self.env['hotel.reservation'].get_reservations( - self.checkin, - fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( - lambda r: r.id != self._origin.id) - rooms_occupied = occupied.mapped('room_id.id') - if self.room_id and self.room_id.id in rooms_occupied: - warning_msg = _('You tried to change \ - reservation with room those already reserved in this \ - reservation period') - raise ValidationError(warning_msg) - domain_rooms = [ - # ('isroom', '=', True), - ('id', 'not in', rooms_occupied) - ] - # if self.check_rooms: - # if self.room_type_id: - # domain_rooms.append( - # ('categ_id.id', '=', self.room_type_id.cat_id.id) - # ) - # if self.virtual_room_id: - # room_categories = self.virtual_room_id.room_type_ids.mapped( - # 'cat_id.id') - # link_virtual_rooms = self.virtual_room_id.room_ids\ - # | self.env['hotel.room'].search([ - # ('categ_id.id', 'in', room_categories)]) - # room_ids = link_virtual_rooms.mapped('room_id.id') - # domain_rooms.append(('id', 'in', room_ids)) - return {'domain': {'room_id': domain_rooms}} + if self.checkin and self.checkout: + if self.overbooking: + return + occupied = self.env['hotel.reservation'].get_reservations( + self.checkin, + fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( + lambda r: r.id != self._origin.id) + rooms_occupied = occupied.mapped('room_id.id') + if self.room_id and self.room_id.id in rooms_occupied: + warning_msg = _('You tried to change \ + reservation with room those already reserved in this \ + reservation period') + raise ValidationError(warning_msg) + domain_rooms = [ + ('id', 'not in', rooms_occupied) + ] + return {'domain': {'room_id': domain_rooms}} """ - COMPUTE RESERVE COLOR + COMPUTE RESERVE COLOR ---------------------------------------------- """ @api.multi @@ -609,7 +563,7 @@ class HotelReservation(models.Model): # splitted_reservs.write({'reserve_color': rec.reserve_color}) """ - STATE WORKFLOW + STATE WORKFLOW ----------------------------------------------------- """ @api.multi @@ -701,58 +655,100 @@ class HotelReservation(models.Model): splitted_reservs.draft() """ - PRICE PROCESS + PRICE PROCESS ------------------------------------------------------ """ - - @api.depends('reservation_line_ids.price', 'reservation_line_ids.discount', 'tax_id') - def _compute_amount_reservation(self): + @api.multi + def compute_price_out_vals(self, vals): + """ + Compute if It is necesary calc price in write/create + """ + if not vals: + vals = {} + if ('price_total' not in vals and \ + ('checkout' in vals or 'checkin' in vals or \ + 'room_type_id' in vals or 'pricelist_id' in vals)): + return True + return False + + @api.multi + def compute_amount_reservation(self, vals=False, update_old_prices=False): """ Compute the amounts of the reservation. + if vals: Then it is a new record and We must use pass in vals + if not vals: Then we calc the price based in self data """ - for line in self: - amount_room = 0 - for day in line.reservation_line_ids: - amount_room += day.price - if amount_room > 0: - product = line.room_type_id.product_id - price = amount_room * (1 - (line.discount or 0.0) / 100.0) - taxes = line.tax_id.compute_all(price, line.currency_id, 1, product=product) - line.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'], - }) + if not vals: + vals = {} + checkin = vals.get('checkin') or self.checkin + checkout = vals.get('checkout') or self.checkout + reservation_type = vals.get('reservation_type') or self.reservation_type + room_type = self.env['hotel.room.type'].browse(vals.get('room_type_id') or self.room_type_id.id) + discount = vals.get('discount') or self.discount + tax = self.env['account.tax'].browse(vals.get('tax_id') or self.tax_id.id) + currency = self.env['res.currency'].browse(vals.get('currency_id') or self.currency_id.id) + + days_diff = (fields.Date.from_string(checkout) - fields.Date.from_string(checkin)).days + rlines = self.prepare_reservation_lines( + checkin, + days_diff, + vals = vals, + update_old_prices = update_old_prices) + if reservation_type in ['staff', 'out']: + amount_room = 0.0 + else: + amount_room = rlines['total_price'] + product = room_type.product_id + price = amount_room * (1 - (discount or 0.0) / 100.0) + taxes = tax.compute_all(price, currency, 1, product=product) + return({ + 'reservation_line_ids': rlines['commands'], + 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), + 'price_total': taxes['total_included'], + 'price_subtotal': taxes['total_excluded'], + }) @api.multi - def prepare_reservation_lines(self, dfrom, days, + def prepare_reservation_lines(self, dfrom, days, vals=False, update_old_prices=False): - self.ensure_one() total_price = 0.0 - cmds = [(5, False, False)] + cmds = [] #~ pricelist_id = self.env['ir.default'].sudo().get( #~ 'res.config.settings', 'parity_pricelist_id') - pricelist_id = int(self.pricelist_id) - product_id = self.room_type_id.product_id + pricelist_id = vals.get('pricelist_id') or self.pricelist_id.id + product = self.env['hotel.room.type'].browse(vals.get('room_type_id') or self.room_type_id.id).product_id old_lines_days = self.mapped('reservation_line_ids.date') + partner = self.env['res.partner'].browse(vals.get('partner_id') or self.partner_id.id) + total_price = 0 for i in range(0, days): idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) - if update_old_prices or (idate not in old_lines_days): - product = product_id.with_context( - lang=self.partner_id.lang, - partner=self.partner_id.id, + old_line = self.reservation_line_ids.filtered(lambda r: r.date == idate) + if update_old_prices or (idate not in old_lines_days): + product = product.with_context( + lang=partner.lang, + partner=partner.id, quantity=1, date=idate, pricelist=pricelist_id, - uom=self.room_type_id.product_id.uom_id.id) + uom=product.uom_id.id) line_price = self.env['account.tax']._fix_tax_included_price_company(product.price, product.taxes_id, self.tax_id, self.company_id) + if old_line: + cmds.append((1, old_line.id, { + 'price': line_price + })) + else: + cmds.append((0, False, { + 'date': idate, + 'price': line_price + })) else: - line = self.reservation_line_ids.filtered(lambda r: r.date == idate) - line_price = line.price - cmds.append((0, False, { - 'date': idate, - 'price': line_price - })) + line_price = old_line.price total_price += line_price + #unlink old lines deleted + dto = (fields.Date.from_string(dfrom) + timedelta(days=days)).strftime(DEFAULT_SERVER_DATE_FORMAT) + old_deleted_days = self.reservation_line_ids.filtered( + lambda d: d.date < dfrom + or d.date >= dto) + old_deleted_days.unlink() return {'total_price': total_price, 'commands': cmds} @api.multi @@ -787,7 +783,7 @@ class HotelReservation(models.Model): } """ - AVAILABILTY PROCESS + AVAILABILTY PROCESS ------------------------------------------------ """ @api.model @@ -858,7 +854,7 @@ class HotelReservation(models.Model): raise ValidationError(warning_msg) """ - CHECKIN/OUT PROCESS + CHECKIN/OUT PROCESS ------------------------------------------------ """ @api.multi @@ -963,7 +959,7 @@ class HotelReservation(models.Model): } """ - RESERVATION SPLITTED + RESERVATION SPLITTED ----------------------------------------------- """ @api.multi diff --git a/hotel/models/hotel_reservation_line.py b/hotel/models/hotel_reservation_line.py index 256b44cad..68e012f4d 100644 --- a/hotel/models/hotel_reservation_line.py +++ b/hotel/models/hotel_reservation_line.py @@ -20,6 +20,7 @@ ############################################################################## from odoo import models, fields, api, _ from odoo.addons import decimal_precision as dp +from odoo.exceptions import except_orm, UserError, ValidationError class HotelReservationLine(models.Model): _name = "hotel.reservation.line" @@ -33,3 +34,13 @@ class HotelReservationLine(models.Model): discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) + + @api.model + def create(self, vals): + record = super(HotelReservation, self).create(vals) + return record + + @api.multi + def write(self, vals): + res = super(HotelReservation, self).write(vals) + return res From fef843a57e903e91beb83775d93330bd6b674765 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Thu, 30 Aug 2018 15:38:32 +0200 Subject: [PATCH 27/28] [WIP] Calcule Price based in vals and onchange refactoring --- hotel/models/hotel_reservation.py | 128 ++++++++++++------------- hotel/models/hotel_reservation_line.py | 10 -- 2 files changed, 62 insertions(+), 76 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index c84fdb191..24fcaead4 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -257,9 +257,9 @@ class HotelReservation(models.Model): # digits=dp.get_precision('Product Unit of Measure')) # qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True) - price_subtotal = fields.Monetary(string='Subtotal', readonly=True, store=True) - price_total = fields.Monetary(string='Total', readonly=True, store=True) - price_tax = fields.Float(string='Taxes', readonly=True, store=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') currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True) # FIXME discount per night discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) @@ -287,7 +287,7 @@ class HotelReservation(models.Model): #~ 'reserve_color_text': colors[1], }) if self.compute_price_out_vals(vals): - vals.update(self.env['hotel.reservation'].compute_amount_reservation(vals)) + vals.update(self.env['hotel.reservation'].prepare_reservation_lines(vals)) record = super(HotelReservation, self).create(vals) #~ if (record.state == 'draft' and record.folio_id.state == 'sale') or \ #~ record.preconfirm: @@ -296,17 +296,21 @@ class HotelReservation(models.Model): @api.multi def write(self, vals): - #~ if self.notify_update(vals): - #~ vals.update({ - #~ 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - #~ }) - #~ for record in self: - #~ if record.compute_price_out_vals(vals): - #~ record.update(self.compute_amount_reservation(vals)) - #~ if ('checkin' in vals and record.checkin != vals['checkin']) or \ - #~ ('checkout' in vals and record.checkout != vals['checkout']) or \ - #~ ('state' in vals and record.state != vals['state']) : - #~ vals.update({'to_send': True}) + if self.notify_update(vals): + vals.update({ + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + }) + for record in self: + if record.compute_price_out_vals(vals): + days_diff = (fields.Date.from_string(record.checkout) - fields.Date.from_string(record.checkin)).days + record.update(record.prepare_reservation_lines( + record.checkin, + days_diff, + vals = vals)) + if ('checkin' in vals and record.checkin != vals['checkin']) or \ + ('checkout' in vals and record.checkout != vals['checkout']) or \ + ('state' in vals and record.state != vals['state']) : + vals.update({'to_send': True}) res = super(HotelReservation, self).write(vals) return res @@ -314,11 +318,11 @@ class HotelReservation(models.Model): def _prepare_add_missing_fields(self, values): """ Deduce missing required fields from the onchange """ res = {} - onchange_fields = ['room_type_id', 'pricelist_id', + onchange_fields = ['room_id', 'pricelist_id', 'reservation_type', 'currency_id'] if values.get('partner_id') and values.get('room_type_id') and any(f not in values for f in onchange_fields): line = self.new(values) - line.room_type_id_change() + line.onchange_room_id() for field in onchange_fields: if field not in values: res[field] = line._fields[field].convert_to_write(line[field], line) @@ -391,12 +395,16 @@ class HotelReservation(models.Model): """ @api.onchange('adults', 'room_id') - def onchange_check_capacity(self): + def onchange_room_id(self): if self.room_id: if self.room_id.capacity < self.adults: self.adults = self.room_id.capacity raise UserError( _('%s people do not fit in this room! ;)') % (persons)) + if self.adults == 0: + self.adults = self.room_id.capacity + if not self.room_type_id: + self.room_type_id = self.room_id.room_type_id @api.onchange('partner_id') def onchange_partner_id(self): @@ -410,21 +418,30 @@ class HotelReservation(models.Model): # When we need to overwrite the prices even if they were already established @api.onchange('room_type_id', 'pricelist_id', 'reservation_type') def onchange_overwrite_price_by_day(self): - self.update(self.compute_amount_reservation(update_old_prices=True)) + if self.room_type_id and self.checkin and self.checkout: + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + self.update(self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices = True)) # When we need to update prices respecting those that were already established @api.onchange('checkin', 'checkout') - def onchange_check_dates(self): + def onchange_dates(self): if not self.checkin: self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if not self.checkout: self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - checkin_dt = fields.Date.from_string(self.checkin) checkout_dt = fields.Date.from_string(self.checkout) if checkin_dt >= checkout_dt: self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) - self.update(self.compute_amount_reservation(update_old_prices=True)) + if self.room_type_id: + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + self.update(self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices = False)) @@ -664,57 +681,41 @@ class HotelReservation(models.Model): """ if not vals: vals = {} - if ('price_total' not in vals and \ + if ('reservation_line_ids' not in vals and \ ('checkout' in vals or 'checkin' in vals or \ 'room_type_id' in vals or 'pricelist_id' in vals)): return True return False - @api.multi - def compute_amount_reservation(self, vals=False, update_old_prices=False): + @api.depends('reservation_line_ids', 'reservation_line_ids.discount', 'tax_id') + def _compute_amount_reservation(self): """ Compute the amounts of the reservation. - if vals: Then it is a new record and We must use pass in vals - if not vals: Then we calc the price based in self data """ - if not vals: - vals = {} - checkin = vals.get('checkin') or self.checkin - checkout = vals.get('checkout') or self.checkout - reservation_type = vals.get('reservation_type') or self.reservation_type - room_type = self.env['hotel.room.type'].browse(vals.get('room_type_id') or self.room_type_id.id) - discount = vals.get('discount') or self.discount - tax = self.env['account.tax'].browse(vals.get('tax_id') or self.tax_id.id) - currency = self.env['res.currency'].browse(vals.get('currency_id') or self.currency_id.id) - - days_diff = (fields.Date.from_string(checkout) - fields.Date.from_string(checkin)).days - rlines = self.prepare_reservation_lines( - checkin, - days_diff, - vals = vals, - update_old_prices = update_old_prices) - if reservation_type in ['staff', 'out']: - amount_room = 0.0 - else: - amount_room = rlines['total_price'] - product = room_type.product_id - price = amount_room * (1 - (discount or 0.0) / 100.0) - taxes = tax.compute_all(price, currency, 1, product=product) - return({ - 'reservation_line_ids': rlines['commands'], - 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), - 'price_total': taxes['total_included'], - 'price_subtotal': taxes['total_excluded'], - }) + for line in self: + amount_room = 0 + for day in line.reservation_line_ids: + amount_room += day.price + if amount_room > 0: + product = line.room_type_id.product_id + price = amount_room * (1 - (line.discount or 0.0) / 100.0) + taxes = line.tax_id.compute_all(price, line.currency_id, 1, product=product) + line.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'], + }) @api.multi def prepare_reservation_lines(self, dfrom, days, vals=False, update_old_prices=False): total_price = 0.0 cmds = [] - #~ pricelist_id = self.env['ir.default'].sudo().get( - #~ 'res.config.settings', 'parity_pricelist_id') - pricelist_id = vals.get('pricelist_id') or self.pricelist_id.id + if not vals: + vals = {} + pricelist_id = self.env['ir.default'].sudo().get( + 'res.config.settings', 'parity_pricelist_id') + #~ pricelist_id = vals.get('pricelist_id') or self.pricelist_id.id product = self.env['hotel.room.type'].browse(vals.get('room_type_id') or self.room_type_id.id).product_id old_lines_days = self.mapped('reservation_line_ids.date') partner = self.env['res.partner'].browse(vals.get('partner_id') or self.partner_id.id) @@ -742,14 +743,9 @@ class HotelReservation(models.Model): })) else: line_price = old_line.price + cmds.append((4, old_line.id)) total_price += line_price - #unlink old lines deleted - dto = (fields.Date.from_string(dfrom) + timedelta(days=days)).strftime(DEFAULT_SERVER_DATE_FORMAT) - old_deleted_days = self.reservation_line_ids.filtered( - lambda d: d.date < dfrom - or d.date >= dto) - old_deleted_days.unlink() - return {'total_price': total_price, 'commands': cmds} + return {'price_total': total_price, 'reservation_line_ids': cmds} @api.multi def action_pay_folio(self): diff --git a/hotel/models/hotel_reservation_line.py b/hotel/models/hotel_reservation_line.py index 68e012f4d..a1278bf53 100644 --- a/hotel/models/hotel_reservation_line.py +++ b/hotel/models/hotel_reservation_line.py @@ -34,13 +34,3 @@ class HotelReservationLine(models.Model): discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) - - @api.model - def create(self, vals): - record = super(HotelReservation, self).create(vals) - return record - - @api.multi - def write(self, vals): - res = super(HotelReservation, self).write(vals) - return res From a0a83a62caa42cd99cdcf713650c35f7284a6164 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Thu, 30 Aug 2018 17:41:11 +0200 Subject: [PATCH 28/28] [IMP] Refactoring --- hotel/models/hotel_reservation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 24fcaead4..225d01af1 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -260,7 +260,6 @@ class HotelReservation(models.Model): 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') - currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True) # FIXME discount per night discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) @@ -306,7 +305,7 @@ class HotelReservation(models.Model): record.update(record.prepare_reservation_lines( record.checkin, days_diff, - vals = vals)) + vals = vals)) #REVISAR el unlink if ('checkin' in vals and record.checkin != vals['checkin']) or \ ('checkout' in vals and record.checkout != vals['checkout']) or \ ('state' in vals and record.state != vals['state']) : @@ -396,6 +395,7 @@ class HotelReservation(models.Model): @api.onchange('adults', 'room_id') def onchange_room_id(self): + # TODO: Usar vals y write if self.room_id: if self.room_id.capacity < self.adults: self.adults = self.room_id.capacity @@ -403,7 +403,7 @@ class HotelReservation(models.Model): _('%s people do not fit in this room! ;)') % (persons)) if self.adults == 0: self.adults = self.room_id.capacity - if not self.room_type_id: + if not self.room_type_id: #Si el registro no existe, modificar room_type aunque ya esté establecido self.room_type_id = self.room_id.room_type_id @api.onchange('partner_id') @@ -413,7 +413,7 @@ class HotelReservation(models.Model): 'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or \ self.env['ir.default'].sudo().get('hotel.config.settings', 'parity_pricelist_id'), } - self.update(values) + self.update(values) # When we need to overwrite the prices even if they were already established @api.onchange('room_type_id', 'pricelist_id', 'reservation_type') @@ -745,7 +745,7 @@ class HotelReservation(models.Model): line_price = old_line.price cmds.append((4, old_line.id)) total_price += line_price - return {'price_total': total_price, 'reservation_line_ids': cmds} + return {'reservation_line_ids': cmds} @api.multi def action_pay_folio(self):