From cf2241c4fb8b40c19d74eb0cfcb3fa6261df49c9 Mon Sep 17 00:00:00 2001 From: padomi Date: Tue, 10 Nov 2020 18:58:14 +0100 Subject: [PATCH 1/2] [IMP] pms: Compute rooms on splitted reservations --- pms/demo/pms_folio.xml | 25 +- pms/models/pms_reservation.py | 59 ++-- pms/models/pms_reservation_line.py | 118 +++++-- pms/tests/test_pms_reservation.py | 470 +++++++++++++++++++++++++++- pms/views/pms_reservation_views.xml | 13 +- 5 files changed, 604 insertions(+), 81 deletions(-) diff --git a/pms/demo/pms_folio.xml b/pms/demo/pms_folio.xml index 10a4c7108..be9214c38 100644 --- a/pms/demo/pms_folio.xml +++ b/pms/demo/pms_folio.xml @@ -213,7 +213,7 @@ name="reservation_ids" eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_1'), + 'preferred_room_id': ref('pms_room_1'), 'checkin': (DateTime.today() + timedelta(days=19)), 'checkout': (DateTime.today() + timedelta(days=20)), 'adults': 1, @@ -233,7 +233,6 @@ (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_2'), - 'room_id': ref('pms_room_4'), 'checkin': (DateTime.today() + timedelta(days=19)), 'checkout': (DateTime.today() + timedelta(days=20)), 'adults': 2, @@ -303,7 +302,7 @@ name="reservation_ids" eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_0'), + 'preferred_room_id': ref('pms_room_0'), 'checkin': (DateTime.today() + timedelta(days=19)), 'checkout': (DateTime.today() + timedelta(days=20)), 'adults': 1, @@ -319,7 +318,7 @@ name="reservation_ids" eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_7'), + 'preferred_room_id': ref('pms_room_7'), 'checkin': (DateTime.today() + timedelta(days=21)), 'checkout': (DateTime.today() + timedelta(days=22)), 'adults': 1, @@ -370,7 +369,6 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_2'), - 'room_id': ref('pms_room_4'), 'checkin': (DateTime.today() + timedelta(days=21)), 'checkout': (DateTime.today() + timedelta(days=22)), 'adults': 1, @@ -391,7 +389,7 @@ name="reservation_ids" eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_6'), + 'preferred_room_id': ref('pms_room_6'), 'checkin': (DateTime.today() + timedelta(days=20)), 'checkout': (DateTime.today() + timedelta(days=21)), 'adults': 3, @@ -408,7 +406,6 @@ (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_2'), - 'room_id': ref('pms_room_4'), 'checkin': (DateTime.today() + timedelta(days=23)), 'checkout': (DateTime.today() + timedelta(days=24)), 'adults': 1, @@ -526,7 +523,6 @@ }), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_5'), 'checkin': (DateTime.today() + timedelta(days=24)), 'checkout': (DateTime.today() + timedelta(days=25)), 'adults': 1, @@ -544,7 +540,7 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_7'), + 'preferred_room_id': ref('pms_room_7'), 'checkin': (DateTime.today() + timedelta(days=24)), 'checkout': (DateTime.today() + timedelta(days=25)), 'adults': 1, @@ -563,7 +559,7 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_0'), + 'preferred_room_id': ref('pms_room_0'), 'checkin': (DateTime.today() + timedelta(days=25)), 'checkout': (DateTime.today() + timedelta(days=32)), 'adults': 1, @@ -580,7 +576,7 @@ (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_1'), - 'room_id': ref('pms_room_1'), + 'preferred_room_id': ref('pms_room_1'), 'checkin': (DateTime.today() + timedelta(days=25)), 'checkout': (DateTime.today() + timedelta(days=32)), 'adults': 1, @@ -596,7 +592,7 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_2'), + 'preferred_room_id': ref('pms_room_2'), 'checkin': (DateTime.today() + timedelta(days=25)), 'checkout': (DateTime.today() + timedelta(days=32)), 'adults': 1, @@ -614,7 +610,6 @@ (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_2'), - 'room_id': ref('pms_room_5'), 'checkin': (DateTime.today() + timedelta(days=25)), 'checkout': (DateTime.today() + timedelta(days=31)), 'adults': 2, @@ -637,7 +632,7 @@ name="reservation_ids" eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), - 'room_id': ref('pms_room_7'), + 'preferred_room_id': ref('pms_room_7'), 'checkin': (DateTime.today() + timedelta(days=25)), 'checkout': (DateTime.today() + timedelta(days=26)), 'adults': 1, @@ -689,7 +684,7 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': ref('product.list0'), 'room_type_id': ref('pms_room_type_3'), - 'room_id': ref('pms_room_6'), + 'preferred_room_id': ref('pms_room_6'), 'checkin': (DateTime.today() + timedelta(days=29)), 'checkout': (DateTime.today() + timedelta(days=30)), 'adults': 1, diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 10b8dc92e..7c0709295 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -106,15 +106,11 @@ class PmsReservation(models.Model): # required=True, ) priority = fields.Integer(compute="_compute_priority", store="True", index=True) - room_id = fields.Many2one( + preferred_room_id = fields.Many2one( "pms.room", string="Room", tracking=True, ondelete="restrict", - compute="_compute_room_id", - store=True, - readonly=False, - # required=True, domain="[('id', 'in', allowed_room_ids)]", ) allowed_room_ids = fields.Many2many( @@ -258,6 +254,13 @@ class PmsReservation(models.Model): store=True, ) + rooms = fields.Char( + string="Room/s", + compute="_compute_rooms", + store=True + ) + + credit_card_details = fields.Text(related="folio_id.credit_card_details") cancelled_reason = fields.Selection( [("late", "Late"), ("intime", "In time"), ("noshow", "No Show")], @@ -415,22 +418,15 @@ class PmsReservation(models.Model): # TODO: Logic priority (100 by example) self.priority = 100 - @api.depends("reservation_line_ids", "reservation_line_ids.room_id") - def _compute_room_id(self): - for reservation in self.filtered( - lambda r: r.reservation_line_ids and not r.room_id - ): - reservation.room_id = reservation.reservation_line_ids[0].room_id - - @api.depends("room_id") + @api.depends("preferred_room_id") def _compute_room_type_id(self): for reservation in self: - if reservation.room_id and not reservation.room_type_id: - reservation.room_type_id = reservation.room_id.room_type_id.id + if reservation.preferred_room_id and not reservation.room_type_id: + reservation.room_type_id = reservation.preferred_room_id.room_type_id.id elif not reservation.room_type_id: reservation.room_type_id = False - @api.depends("reservation_line_ids.date", "overbooking", "state", "room_id") + @api.depends("reservation_line_ids.date", "overbooking", "state", "preferred_room_id") def _compute_allowed_room_ids(self): for reservation in self: if reservation.checkin and reservation.checkout: @@ -537,12 +533,12 @@ class PmsReservation(models.Model): reservation.pricelist_id = pricelist_id # REVIEW: Dont run with set room_type_id -> room_id(compute)-> No set adults¿? - @api.depends("room_id") + @api.depends("preferred_room_id") def _compute_adults(self): for reservation in self: - if reservation.room_id: + if reservation.preferred_room_id: if reservation.adults == 0: - reservation.adults = reservation.room_id.capacity + reservation.adults = reservation.preferred_room_id.capacity elif not reservation.adults: reservation.adults = 0 @@ -553,6 +549,8 @@ class PmsReservation(models.Model): reservation.splitted = True else: reservation.splitted = False + reservation.preferred_room_id = reservation.reservation_line_ids[0].room_id + @api.depends("state", "qty_to_invoice", "qty_invoiced") def _compute_invoice_status(self): @@ -687,7 +685,7 @@ class PmsReservation(models.Model): # TODO: Use default values on checkin /checkout is empty @api.constrains( - "checkin", "checkout", "state", "room_id", "overbooking", "reselling" + "checkin", "checkout", "state", "preferred_room_id", "overbooking", "reselling" ) def check_dates(self): """ @@ -796,7 +794,7 @@ class PmsReservation(models.Model): args += [ "|", ("folio_id.name", operator, name), - ("room_id.name", operator, name), + ("preferred_room_id.name", operator, name), ] return super(PmsReservation, self).name_search( name="", args=args, operator="ilike", limit=limit @@ -805,7 +803,7 @@ class PmsReservation(models.Model): def name_get(self): result = [] for res in self: - name = u"{} ({})".format(res.folio_id.name, res.room_id.name) + name = u"{} ({})".format(res.folio_id.name, res.rooms if res.rooms else 'No room') result.append((res.id, name)) return result @@ -907,7 +905,7 @@ class PmsReservation(models.Model): "price_subtotal": self.price_subtotal, "splitted": self.splitted, "room_type_id": self.room_type_id.id, - "room_id": self.room_id.id, + "preferred_room_id": self.preferred_room_id.id, } def confirm(self): @@ -996,8 +994,8 @@ class PmsReservation(models.Model): if record.reservation_type != "out": record.checkin_partner_count = len(record.checkin_partner_ids) record.checkin_partner_pending_count = ( - record.adults + record.children - ) - len(record.checkin_partner_ids) + record.adults + record.children + ) - len(record.checkin_partner_ids) else: record.checkin_partner_count = 0 record.checkin_partner_pending_count = 0 @@ -1054,3 +1052,14 @@ class PmsReservation(models.Model): record.tax_ids = product.taxes_id.filtered( lambda r: not record.company_id or r.company_id == folio.company_id ) + + @api.depends("reservation_line_ids", "reservation_line_ids.room_id") + def _compute_rooms(self): + self.rooms = False + + for reservation in self: + if reservation.splitted: + reservation.rooms = ", ".join([r for r in reservation.reservation_line_ids.mapped('room_id.name')]) + reservation.preferred_room_id = False + else: + reservation.rooms = reservation.preferred_room_id.name diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index a844d9f25..716700ecb 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -2,14 +2,14 @@ # Copyright 2017 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from datetime import timedelta +import datetime +import operator from odoo import _, api, fields, models from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) - class PmsReservationLine(models.Model): _name = "pms.reservation.line" _description = "Reservations by day" @@ -91,37 +91,97 @@ class PmsReservationLine(models.Model): ] # Compute and Search methods - @api.depends( - "reservation_id.adults", - "reservation_id.room_type_id", - ) + @api.depends("reservation_id.room_type_id") def _compute_room_id(self): - for line in self: - if line.reservation_id.room_type_id: - preferred_room = line.reservation_id.room_id - rooms_available = self.env[ - "pms.room.type.availability" - ].rooms_available( - checkin=line.date, - checkout=line.date + timedelta(1), - room_type_id=line.reservation_id.room_type_id.id or False, - current_lines=line._origin.id, + for line in self.sorted(key=lambda r: (r.reservation_id, r.date)): + + # if the reservation has a room type and no room id + if line.reservation_id.room_type_id and not line.room_id: + + # we get the rooms available for the entire stay + rooms_available = self.env[ "pms.room.type.availability"].rooms_available( + checkin=line.reservation_id.checkin, + checkout=line.reservation_id.checkout, + room_type_id=line.reservation_id.room_type_id.id, + current_lines=line._origin.reservation_id.reservation_line_ids.ids, ) + + # if there is availability for the entire stay if rooms_available: - if preferred_room.id in rooms_available.ids: - room_chosen = preferred_room + + # if the reservation has a preferred room + if line.reservation_id.preferred_room_id: + + # if the preferred room is available + if line.reservation_id.preferred_room_id in rooms_available: + line.room_id = line.reservation_id.preferred_room_id + + # if the preferred room is NOT available + else: + raise ValidationError(_("%s: No room available.")% (line.reservation_id.preferred_room_id.name)) + + # otherwise we assign the first of those available for the entire stay else: - room_chosen = rooms_available[0] - line.room_id = room_chosen + line.room_id = rooms_available[0] + line.reservation_id.preferred_room_id = line.room_id.id + + # if there is no availability for the entire stay without changing rooms (we assume a split reservation) else: - line.room_id = False - raise ValidationError( - _("%s: No rooms available") - % (line.reservation_id.room_type_id.name) - ) - line._check_adults() - else: - line.room_id = False + rooms_ranking = dict() + + # we go through the rooms of the type + for room in self.env['pms.room'].search([('room_type_id', '=', line.reservation_id.room_type_id.id)]): + + # we iterate the dates from the date of the line to the checkout + for date_iterator in \ + [line.date + datetime.timedelta(days=x) for x in range(0, (line.reservation_id.checkout - line.date).days)]: + + # if the room is already assigned for a date we go to the next room + if self.env['pms.reservation.line'].search_count([ + ('date', '=', date_iterator), + ('room_id', '=', room.id), + ('id', 'not in', line.reservation_id.reservation_line_ids.ids), + ("occupies_availability", "=", True), + ]) > 0: + break + + # if the room is not assigned for a date we add it to the ranking / update its ranking + else: + rooms_ranking[room.id] = 1 if room.id not in rooms_ranking else rooms_ranking[room.id] + 1 + if len(rooms_ranking) == 0: + raise ValidationError(_("%s: No room type available") % (line.reservation_id.room_type_id.name)) + else: + # we get the best score in the ranking + best = max(rooms_ranking.values()) + + # we keep the rooms with the best ranking + bests = {key: value for (key, value) in rooms_ranking.items() if value == best} + + # if there is a tie in the rankings + if len(bests) > 1: + + # we get the line from last night + date_last_night = line.date + datetime.timedelta(days=-1) + line_past_night = self.env['pms.reservation.line'].search([ + ('date', '=', date_last_night), + ('reservation_id', '=', line.reservation_id.id) + ]) + + # if there is the night before and if the room from the night before is in the ranking + if line_past_night and line_past_night.room_id.id in bests: + line.room_id = line_past_night.room_id.id + + # if the room from the night before is not in the ranking or there is no night before + else: + # At this point we set the room with the best ranking, no matter what it is + line.room_id = list(bests.keys())[0] + + # if there is no tie in the rankings + else: + # At this point we set the room with the best ranking, no matter what it is + line.room_id = list(bests.keys())[0] + + @api.depends( "reservation_id", @@ -158,7 +218,7 @@ class PmsReservationLine(models.Model): line.reservation_id.tax_ids, line.reservation_id.company_id, ) - _logger.info(line.price) + # _logger.info(line.price) # TODO: Out of service 0 amount else: line.price = line._origin.price diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index 0e568ff83..672d8db74 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -1,16 +1,79 @@ -from datetime import timedelta +import datetime +from _pytest.skipping import Skip +from freezegun import freeze_time from odoo import fields +from odoo.exceptions import ValidationError from .common import TestHotel - class TestPmsReservations(TestHotel): + + def create_common_scenario(self): + # create a room type restriction + self.room_type_restriction = self.env['pms.room.type.restriction'].create( + { + 'name': 'Restriction plan for TEST' + } + ) + + # create a property + self.property = self.env['pms.property'].create( + { + 'name': 'MY PMS TEST', + 'company_id': self.env.ref('base.main_company').id, + 'default_pricelist_id': self.env.ref('product.list0').id, + 'default_restriction_id': self.room_type_restriction.id, + }) + + # create room type class + self.room_type_class = self.env['pms.room.type.class'].create( + { + 'name': 'Room' + } + ) + + # create room type + self.room_type_double = self.env['pms.room.type'].create( + { + 'pms_property_ids': [self.property.id], + 'name': 'Double Test', + 'code_type': 'DBL_Test', + 'class_id': self.room_type_class.id + } + ) + + # create rooms + self.room1 = self.env['pms.room'].create( + { + 'pms_property_id': self.property.id, + 'name': 'Double 101', + 'room_type_id': self.room_type_double.id, + 'capacity': 2 + }) + + self.room2 = self.env['pms.room'].create( + { + 'pms_property_id': self.property.id, + 'name': 'Double 102', + 'room_type_id': self.room_type_double.id, + 'capacity': 2 + }) + + self.room3 = self.env['pms.room'].create( + { + 'pms_property_id': self.property.id, + 'name': 'Double 103', + 'room_type_id': self.room_type_double.id, + 'capacity': 2 + }) + self.demo_user = self.env.ref("base.user_admin") + + def test_create_reservation(self): today = fields.date.today() - checkin = today + timedelta(days=8) - checkout = checkin + timedelta(days=11) - demo_user = self.env.ref("base.user_demo") + checkin = today + datetime.timedelta(days=8) + checkout = checkin + datetime.timedelta(days=11) customer = self.env.ref("base.res_partner_12") reservation_vals = { "checkin": checkin, @@ -20,7 +83,7 @@ class TestPmsReservations(TestHotel): "pms_property_id": self.main_hotel_property.id, } reservation = ( - self.env["pms.reservation"].with_user(demo_user).create(reservation_vals) + self.env["pms.reservation"].create(reservation_vals) ) self.assertEqual( @@ -30,6 +93,399 @@ class TestPmsReservations(TestHotel): ) self.assertEqual( reservation.reservation_line_ids[-1].date, - checkout - timedelta(1), + checkout - datetime.timedelta(1), "Reservation lines don't end in the correct date", + ) + + @freeze_time("1980-11-01") + def test_split_reservation01(self): + """ + The reservation shouldn't be splitted + preferred_room_id with availability provided + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | test | test | test | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + + self.create_common_scenario() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id + } ) + r_test.flush() + obtained = all(elem.room_id.id == r_test.reservation_line_ids[0].room_id.id for elem in r_test.reservation_line_ids) + self.assertTrue(obtained, "The entire reservation should be allocated in the preferred room") + + @freeze_time("1980-11-01") + def test_split_reservation02(self): + """ + The reservation shouldn't be splitted + room_type_id with availability provided + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | test | test | test | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + self.create_common_scenario() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r_test.flush() + self.assertFalse(r_test.splitted, "The reservation shouldn't be splitted") + + @freeze_time("1980-11-01") + def test_split_reservation03(self): + """ + The reservation should be splitted in 2 rooms + (there is only one better option on day 02 and a draw the next day. + The night before should be prioritized) + +------------+------+------+------+------+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+------+----+----+ + | Double 101 | test | r3 | | | | | + | Double 102 | r1 | test | test | test | | | + | Double 103 | r2 | r4 | | | | | + +------------+------+------+------+------+----+----+ + """ + + self.create_common_scenario() + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room2.id + r1.flush() + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r2.reservation_line_ids[0].room_id = self.room3.id + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room1.id + r3.flush() + + r4 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r4.reservation_line_ids[0].room_id = self.room3.id + r4.flush() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r_test.flush() + changes = 0 + last_room = None + + for line in r_test.reservation_line_ids: + if last_room != line.room_id.id: + last_room = line.room_id.id + changes +=1 + + self.assertEqual(2, changes, "The reservation shouldn't have more than 2 changes") + + @freeze_time("1980-11-01") + def test_split_reservation04(self): + """ + The reservation should be splitted in 3 rooms + (there are 2 best options on day 03 and room of last night is not available) + +------------+------+------+------+------+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+------+----+----+ + | Double 101 | test | r3 | test | test | | | + | Double 102 | r1 | test | r5 | | | | + | Double 103 | r2 | r4 | | | | | + +------------+------+------+------+------+----+----+ + """ + + self.create_common_scenario() + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room2.id + r1.flush() + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r2.reservation_line_ids[0].room_id = self.room3.id + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room1.id + r3.flush() + + r4 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r4.reservation_line_ids[0].room_id = self.room3.id + r4.flush() + + r5 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=2), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r5.reservation_line_ids[0].room_id = self.room2.id + r5.flush() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r_test.flush() + + changes = 0 + last_room = None + for line in r_test.reservation_line_ids: + if line.room_id != last_room: + last_room = line.room_id + changes += 1 + + self.assertEqual(3, changes, "The reservation shouldn't be splitted in more than 3 roomss") + + @freeze_time("1980-11-01") + def test_split_reservation05(self): + """ + The preferred room_id is not available + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 |r1/tst| | | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + + self.create_common_scenario() + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.flush() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "preferred_room_id": self.room1.id + } + ) + + with self.assertRaises(ValidationError): + r_test.flush() + + @freeze_time("1980-11-01") + def test_split_reservation06(self): + """ + The preferred room_id is not available + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 |r1/tst| tst | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + + self.create_common_scenario() + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.reservation_line_ids[1].room_id = self.room1 + r1.flush() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id + } + ) + + with self.assertRaises(ValidationError): + r_test.flush() + + @freeze_time("1980-11-01") + def test_split_reservation07(self): + """ + The preferred room_type_id is not available + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + | Double 103 | r3 | r3 | r3 | | | | + +------------+------+------+------+----+----+----+ + """ + + self.create_common_scenario() + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.reservation_line_ids[1].room_id = self.room1 + r1.reservation_line_ids[2].room_id = self.room1 + r1.flush() + + + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + + } + ) + r2.reservation_line_ids[0].room_id = self.room2 + r2.reservation_line_ids[1].room_id = self.room2 + r2.reservation_line_ids[2].room_id = self.room2 + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room3 + r3.reservation_line_ids[1].room_id = self.room3 + r3.reservation_line_ids[2].room_id = self.room3 + r3.flush() + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + } + ) + + with self.assertRaises(ValidationError): + r_test.flush() diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index 6cfd976e7..c8f11b809 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -161,7 +161,7 @@

- + + pms.reservation.checkin.form @@ -596,7 +597,7 @@

pms.reservation.tree pms.reservation - + - + Date: Thu, 12 Nov 2020 19:12:22 +0100 Subject: [PATCH 2/2] [IMP] pms: wizard unify line room ids --- pms/__init__.py | 3 +-- pms/__manifest__.py | 1 + pms/models/pms_reservation.py | 19 +++++++++++++++++++ pms/models/pms_reservation_line.py | 1 - pms/security/ir.model.access.csv | 1 + pms/tests/test_pms_reservation.py | 4 ++-- pms/views/pms_reservation_views.xml | 11 ++++++----- pms/wizards/__init__.py | 1 + pms/wizards/wizard_reservation.py | 18 ++++++++++++++++++ pms/wizards/wizard_reservation.xml | 24 ++++++++++++++++++++++++ 10 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 pms/wizards/__init__.py create mode 100644 pms/wizards/wizard_reservation.py create mode 100644 pms/wizards/wizard_reservation.xml diff --git a/pms/__init__.py b/pms/__init__.py index 3f3f8679b..7588e52c8 100644 --- a/pms/__init__.py +++ b/pms/__init__.py @@ -1,5 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models - -# from . import wizard +from . import wizards diff --git a/pms/__manifest__.py b/pms/__manifest__.py index 965ad2ae1..5fc1b87f1 100644 --- a/pms/__manifest__.py +++ b/pms/__manifest__.py @@ -63,6 +63,7 @@ "views/product_template_views.xml", "views/webclient_templates.xml", "views/ir_sequence_views.xml", + "wizards/wizard_reservation.xml", ], "demo": [ "demo/pms_master_data.xml", diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 7c0709295..4cfb3fc8c 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -106,6 +106,7 @@ class PmsReservation(models.Model): # required=True, ) priority = fields.Integer(compute="_compute_priority", store="True", index=True) + preferred_room_id = fields.Many2one( "pms.room", string="Room", @@ -785,6 +786,24 @@ class PmsReservation(models.Model): "target": "new", } + def open_reservation_wizard(self): + rooms_available = self.env["pms.room.type.availability"].rooms_available( + checkin=self.checkin, + checkout=self.checkout, + current_lines=self.reservation_line_ids.ids, + ) + # REVIEW: check capacity room + return { + 'view_type': 'form', + 'view_mode': 'form', + 'name': 'Unify the reservation', + 'res_model': 'pms.reservation.wizard', + 'target': 'new', + 'type': 'ir.actions.act_window', + 'context': {'rooms_available': rooms_available.ids, + } + } + # ORM Overrides @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index 716700ecb..3787b7041 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -123,7 +123,6 @@ class PmsReservationLine(models.Model): # otherwise we assign the first of those available for the entire stay else: line.room_id = rooms_available[0] - line.reservation_id.preferred_room_id = line.room_id.id # if there is no availability for the entire stay without changing rooms (we assume a split reservation) else: diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv index 3530f26ab..02f4a382b 100644 --- a/pms/security/ir.model.access.csv +++ b/pms/security/ir.model.access.csv @@ -47,3 +47,4 @@ manager_access_pms_board_service_line,manager_access_pms_board_service_line,mode manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1 manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1 manager_access_availability,manager_access_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,1 +user_access_pms_reservation_wizard,user_access_pms_reservation_wizard,model_pms_reservation_wizard,pms.group_pms_user,1,1,1,1 diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index 672d8db74..80aa1e087 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -377,7 +377,7 @@ class TestPmsReservations(TestHotel): @freeze_time("1980-11-01") def test_split_reservation06(self): """ - The preferred room_id is not available + There's no availability in the preferred_room_id provided +------------+------+------+------+----+----+----+ | room/date | 01 | 02 | 03 | 04 | 05 | 06 | +------------+------+------+------+----+----+----+ @@ -419,7 +419,7 @@ class TestPmsReservations(TestHotel): @freeze_time("1980-11-01") def test_split_reservation07(self): """ - The preferred room_type_id is not available + There's no availability +------------+------+------+------+----+----+----+ | room/date | 01 | 02 | 03 | 04 | 05 | 06 | +------------+------+------+------+----+----+----+ diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index c8f11b809..7fe2bdcd1 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -66,14 +66,13 @@ style="margin-bottom:0px;" attrs="{'invisible': [('splitted','=',False)]}" > - This reservation is part of splitted reservation!, you can check it - in the + This reservation is part of splitted reservation, you can try to unify the reservation here