diff --git a/pms/demo/pms_master_data.xml b/pms/demo/pms_master_data.xml index a19bd690e..a8bbd9d17 100644 --- a/pms/demo/pms_master_data.xml +++ b/pms/demo/pms_master_data.xml @@ -201,7 +201,7 @@ Open Talk Away Room - 1 + 10 diff --git a/pms/models/__init__.py b/pms/models/__init__.py index 8bab3ddac..d0ba83c43 100644 --- a/pms/models/__init__.py +++ b/pms/models/__init__.py @@ -46,3 +46,4 @@ from . import folio_sale_line from . import account_bank_statement_line from . import account_bank_statement from . import account_journal +from . import pms_room_type_availability diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 061e7d72c..7af48e7a0 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -598,6 +598,8 @@ class PmsReservation(models.Model): @api.depends( "reservation_line_ids.date", + "reservation_line_ids.room_id", + "reservation_line_ids.occupies_availability", "overbooking", "state", "preferred_room_id", @@ -618,7 +620,8 @@ class PmsReservation(models.Model): checkout=reservation.checkout, room_type_id=False, # Allow chosen any available room current_lines=reservation.reservation_line_ids.ids, - pricelist=reservation.pricelist_id.id, + pricelist_id=reservation.pricelist_id.id, + pms_property_id=reservation.pms_property_id.id, ) reservation.allowed_room_ids = rooms_available @@ -1314,7 +1317,8 @@ class PmsReservation(models.Model): checkin=self.checkin, checkout=self.checkout, current_lines=self.reservation_line_ids.ids, - pricelist=self.pricelist_id.id, + pricelist_id=self.pricelist_id.id, + pms_property_id=self.pms_property_id.id, ) # REVIEW: check capacity room return { @@ -1358,7 +1362,8 @@ class PmsReservation(models.Model): self.checkin, self.checkout, room_type_id=self.room_type_id.id, - pricelist=self.pricelist_id.id, + pricelist_id=self.pricelist_id.id, + pms_property_id=self.pms_property_id.id, ) if self.preferred_room_id.id in rooms_available.ids: default["preferred_room_id"] = self.preferred_room_id.id @@ -1410,7 +1415,8 @@ class PmsReservation(models.Model): checkin=self.checkin, checkout=self.checkout, room_type_id=self.room_type_id.id or False, - pricelist=self.pricelist_id.id, + pricelist_id=self.pricelist_id.id, + pms_property_id=self.pms_property_id.id, ) if rooms_available: room_chosen = rooms_available[0] diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index 194c81f18..77dc99bad 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -78,6 +78,14 @@ class PmsReservationLine(models.Model): store=True, readonly=False, ) + avail_id = fields.Many2one( + string="Availability Day", + comodel_name="pms.room.type.availability", + ondelete="restrict", + compute="_compute_avail_id", + store=True, + ) + discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0) occupies_availability = fields.Boolean( string="Occupies", @@ -108,7 +116,7 @@ class PmsReservationLine(models.Model): key=lambda r: (r.reservation_id, r.date) ): reservation = line.reservation_id - if reservation.preferred_room_id or not line.room_id: + if reservation.preferred_room_id != line.room_id or not line.room_id: # If reservation has a preferred_room_id We can allow # select room_id regardless room_type_id selected on reservation free_room_select = True if reservation.preferred_room_id else False @@ -122,7 +130,8 @@ class PmsReservationLine(models.Model): if not free_room_select else False, current_lines=line.reservation_id.reservation_line_ids.ids, - pricelist=line.reservation_id.pricelist_id.id, + pricelist_id=line.reservation_id.pricelist_id.id, + pms_property_id=line.pms_property_id.id, ) # if there is availability for the entire stay if rooms_available: @@ -154,6 +163,7 @@ class PmsReservationLine(models.Model): room_type_id=line.reservation_id.room_type_id.id, current_lines=line._origin.reservation_id.reservation_line_ids.ids, pricelist=line.reservation_id.pricelist_id, + pms_property_id=line.pms_property_id.id, ): raise ValidationError( _("%s: No room type available") @@ -166,9 +176,11 @@ class PmsReservationLine(models.Model): # we go through the rooms of the type for room in self.env["pms.room"].search( - [("room_type_id", "=", reservation.room_type_id.id)] + [ + ("room_type_id", "=", reservation.room_type_id.id), + ("pms_property_id", "=", reservation.pms_property_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) @@ -391,6 +403,35 @@ class PmsReservationLine(models.Model): # else: # reservation.reservation_line_ids.update({"cancel_discount": 0}) + @api.depends("room_id", "pms_property_id", "date", "occupies_availability") + def _compute_avail_id(self): + for record in self: + if ( + record.room_id.room_type_id + and record.date + and record.pms_property_id + and record.occupies_availability + ): + avail = self.env["pms.room.type.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", record.room_id.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if avail: + record.avail_id = avail.id + else: + record.avail_id = self.env["pms.room.type.availability"].create( + { + "date": record.date, + "room_type_id": record.room_id.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + } + ) + else: + record.avail_id = False + # Constraints and onchanges @api.constrains("date") def constrains_duplicated_date(self): diff --git a/pms/models/pms_room_type.py b/pms/models/pms_room_type.py index 7489b35c1..a7f2e3d6a 100644 --- a/pms/models/pms_room_type.py +++ b/pms/models/pms_room_type.py @@ -18,6 +18,25 @@ class PmsRoomType(models.Model): _inherits = {"product.product": "product_id"} _order = "sequence, code_type, name" + # Defaults and Gets + def name_get(self): + result = [] + for room_type in self: + name = room_type.name + if self._context.get("checkin") and self._context.get("checkin"): + avail = self.env[ + "pms.room.type.availability.plan" + ].get_count_rooms_available( + checkin=self._context.get("checkin"), + checkout=self._context.get("checkout"), + room_type_id=room_type.id, + pms_property_id=self._context.get("pms_property_id") or False, + pricelist_id=self._context.get("pricelist_id") or False, + ) + name += " (%s)" % avail + result.append((room_type.id, name)) + return result + # Fields declaration product_id = fields.Many2one( "product.product", diff --git a/pms/models/pms_room_type_availability.py b/pms/models/pms_room_type_availability.py new file mode 100644 index 000000000..0e029a09c --- /dev/null +++ b/pms/models/pms_room_type_availability.py @@ -0,0 +1,88 @@ +# Copyright 2017 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsRoomTypeAvailability(models.Model): + _name = "pms.room.type.availability" + _description = "Room type availability per day" + + room_type_id = fields.Many2one( + comodel_name="pms.room.type", + string="Room Type", + required=True, + ondelete="cascade", + readonly=True, + ) + date = fields.Date( + string="Date", + required=True, + readonly=True, + ) + pms_property_id = fields.Many2one( + comodel_name="pms.property", + string="Property", + ondelete="restrict", + required=True, + readonly=True, + ) + reservation_line_ids = fields.One2many( + string="Reservation Lines", + comodel_name="pms.reservation.line", + inverse_name="avail_id", + readonly=True, + ) + real_avail = fields.Integer( + compute="_compute_real_avail", + store=True, + readonly=True, + ) + + _sql_constraints = [ + ( + "room_type_registry_unique", + "unique(room_type_id, date, pms_property_id)", + "Only can exists one availability in the same \ + day for the same room type!", + ) + ] + + @api.depends("reservation_line_ids.occupies_availability") + def _compute_real_avail(self): + for record in self: + Rooms = self.env["pms.room"] + RoomLines = self.env["pms.reservation.line"] + total_rooms = Rooms.search_count( + [ + ("room_type_id", "=", record.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + room_ids = record.room_type_id.mapped("room_ids.id") + rooms_not_avail = RoomLines.search_count( + [ + ("date", "=", record.date), + ("room_id", "in", room_ids), + ("pms_property_id", "=", record.pms_property_id.id), + ("occupies_availability", "=", True), + # ("id", "not in", current_lines if current_lines else []), + ] + ) + record.real_avail = total_rooms - rooms_not_avail + + @api.constrains( + "room_type_id", + "pms_property_id", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_id and rec.room_type_id: + if ( + rec.room_type_id.pms_property_ids.ids + and rec.pms_property_id.id + not in rec.room_type_id.pms_property_ids.ids + ): + raise ValidationError( + _("Property not allowed on availability day compute") + ) diff --git a/pms/models/pms_room_type_availability_plan.py b/pms/models/pms_room_type_availability_plan.py index cfb27fd02..2ea1b857a 100644 --- a/pms/models/pms_room_type_availability_plan.py +++ b/pms/models/pms_room_type_availability_plan.py @@ -2,7 +2,9 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import datetime -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT class PmsRoomTypeAvailability(models.Model): @@ -69,66 +71,157 @@ class PmsRoomTypeAvailability(models.Model): checkout, room_type_id=False, current_lines=False, - pricelist=False, + pricelist_id=False, pms_property_id=False, ): if current_lines and not isinstance(current_lines, list): current_lines = [current_lines] - - rooms_not_avail = ( - self.env["pms.reservation.line"] - .search( - [ - ("date", ">=", checkin), - ("date", "<=", checkout - datetime.timedelta(1)), - ("occupies_availability", "=", True), - ("id", "not in", current_lines if current_lines else []), - ] - ) - .mapped("room_id.id") + free_rooms = self.get_real_free_rooms( + checkin, checkout, room_type_id, current_lines, pms_property_id ) - - domain_rooms = [ - ("id", "not in", rooms_not_avail if len(rooms_not_avail) > 0 else []), - ] domain_rules = [ ("date", ">=", checkin), - ("date", "<=", checkout), + ( + "date", + "<=", + checkout, + ), # TODO: only closed_departure take account checkout date! ] - if pms_property_id: - domain_rooms.append(("pms_property_id", "=", pms_property_id)) domain_rules.append(("pms_property_id", "=", pms_property_id)) if room_type_id: - domain_rooms.append(("room_type_id", "=", room_type_id)) domain_rules.append(("room_type_id", "=", room_type_id)) + if pricelist_id: + pricelist = self.env["product.pricelist"].browse(pricelist_id) + if pricelist and pricelist.availability_plan_id: + domain_rules.append( + ("availability_plan_id", "=", pricelist.availability_plan_id.id) + ) + rule_items = self.env["pms.room.type.availability.rule"].search( + domain_rules + ) - free_rooms = self.env["pms.room"].search(domain_rooms) + if len(rule_items) > 0: + room_types_to_remove = [] + for item in rule_items: + if self.any_rule_applies(checkin, checkout, item): + room_types_to_remove.append(item.room_type_id.id) + free_rooms = free_rooms.filtered( + lambda x: x.room_type_id.id not in room_types_to_remove + ) + elif not pricelist: + raise ValidationError(_("Pricelist not found")) + return free_rooms.sorted(key=lambda r: r.sequence) - if pricelist: + def get_real_free_rooms( + self, + checkin, + checkout, + room_type_id=False, + current_lines=False, + pms_property_id=False, + ): + Avail = self.env["pms.room.type.availability"] + if isinstance(checkin, str): + checkin = datetime.datetime.strptime( + checkin, DEFAULT_SERVER_DATE_FORMAT + ).date() + if isinstance(checkout, str): + checkout = datetime.datetime.strptime( + checkout, DEFAULT_SERVER_DATE_FORMAT + ).date() + domain = [ + ("date", ">=", checkin), + ("date", "<=", checkout - datetime.timedelta(1)), + ] + rooms_not_avail = ( + Avail.search(domain) + .reservation_line_ids.filtered( + lambda l: l.id not in current_lines if current_lines else [] + ) + .room_id.ids + ) + domain_rooms = [ + ("id", "not in", rooms_not_avail if rooms_not_avail else []), + ] + if pms_property_id: + domain_rooms.append(("pms_property_id", "=", pms_property_id)) + if room_type_id: + domain_rooms.append(("room_type_id", "=", room_type_id)) + return self.env["pms.room"].search(domain_rooms) + + @api.model + def get_count_rooms_available( + self, + checkin, + checkout, + room_type_id, + pms_property_id, + current_lines=False, + pricelist_id=False, + ): + if current_lines and not isinstance(current_lines, list): + current_lines = [current_lines] + + avail = self.get_count_real_free_rooms( + checkin, checkout, room_type_id, pms_property_id, current_lines + ) + domain_rules = [ + ("date", ">=", checkin), + ( + "date", + "<=", + checkout, + ), # TODO: only closed_departure take account checkout date! + ("room_type_id", "=", room_type_id), + ("pms_property_id", "=", pms_property_id), + ] + if pricelist_id: + pricelist = self.env["product.pricelist"].browse(pricelist_id) + if pricelist and pricelist.availability_plan_id: domain_rules.append( - ("availability_plan_id.pms_pricelist_ids", "=", pricelist) + ("availability_plan_id", "=", pricelist.availability_plan_id.id) ) rule_items = self.env["pms.room.type.availability.rule"].search( domain_rules ) - if len(rule_items) > 0: - room_types_to_remove = [] for item in rule_items: if self.any_rule_applies(checkin, checkout, item): - room_types_to_remove.append(item.room_type_id.id) - free_rooms = free_rooms.filtered( - lambda x: x.room_type_id.id not in room_types_to_remove - ) + return 0 + avail = min(rule_items.mapped("plan_avail")) + return avail - # if pms_property_id: - # free_rooms = free_rooms.filtered( - # lambda x: x.pms_property_id = pms_property_id - # ) - - return free_rooms.sorted(key=lambda r: r.sequence) + def get_count_real_free_rooms( + self, + checkin, + checkout, + room_type_id, + pms_property_id, + current_lines=False, + ): + Avail = self.env["pms.room.type.availability"] + count_free_rooms = len(self.env["pms.room.type"].browse(room_type_id).room_ids) + if isinstance(checkin, str): + checkin = datetime.datetime.strptime( + checkin, DEFAULT_SERVER_DATE_FORMAT + ).date() + if isinstance(checkout, str): + checkout = datetime.datetime.strptime( + checkout, DEFAULT_SERVER_DATE_FORMAT + ).date() + for avail in Avail.search( + [ + ("date", ">=", checkin), + ("date", "<=", checkout - datetime.timedelta(1)), + ("room_type_id", "=", room_type_id), + ("pms_property_id", "=", pms_property_id), + ] + ): + if avail.real_avail < count_free_rooms: + count_free_rooms = avail.real_avail + return count_free_rooms @api.model def splitted_availability( @@ -138,7 +231,16 @@ class PmsRoomTypeAvailability(models.Model): room_type_id=False, current_lines=False, pricelist=False, + pms_property_id=False, ): + if isinstance(checkin, str): + checkin = datetime.datetime.strptime( + checkin, DEFAULT_SERVER_DATE_FORMAT + ).date() + if isinstance(checkout, str): + checkout = datetime.datetime.strptime( + checkout, DEFAULT_SERVER_DATE_FORMAT + ).date() for date_iterator in [ checkin + datetime.timedelta(days=x) for x in range(0, (checkout - checkin).days) @@ -148,7 +250,8 @@ class PmsRoomTypeAvailability(models.Model): checkout=date_iterator + datetime.timedelta(1), room_type_id=room_type_id, current_lines=current_lines, - pricelist=pricelist.id, + pricelist_id=pricelist.id, + pms_property_id=pms_property_id, ) if len(rooms_avail) < 1: return False diff --git a/pms/models/pms_room_type_availability_rule.py b/pms/models/pms_room_type_availability_rule.py index d99569400..cb816737b 100644 --- a/pms/models/pms_room_type_availability_rule.py +++ b/pms/models/pms_room_type_availability_rule.py @@ -59,7 +59,6 @@ class PmsRoomTypeAvailabilityRule(models.Model): compute="_compute_quota", help="Generic Quota assigned.", ) - max_avail = fields.Integer( string="Max. Availability", store=True, @@ -67,13 +66,12 @@ class PmsRoomTypeAvailabilityRule(models.Model): compute="_compute_max_avail", help="Maximum simultaneous availability on own Booking Engine.", ) - pms_property_id = fields.Many2one( comodel_name="pms.property", string="Property", ondelete="restrict", + required=True, ) - allowed_property_ids = fields.Many2many( "pms.property", "allowed_availability_move_rel", @@ -84,6 +82,22 @@ class PmsRoomTypeAvailabilityRule(models.Model): readonly=True, compute="_compute_allowed_property_ids", ) + avail_id = fields.Many2one( + string="Avail record", + comodel_name="pms.room.type.availability", + compute="_compute_avail_id", + store=True, + readonly=False, + ondelete="restrict", + ) + real_avail = fields.Integer( + related="avail_id.real_avail", + store="True", + ) + plan_avail = fields.Integer( + compute="_compute_plan_avail", + store="True", + ) _sql_constraints = [ ( @@ -94,6 +108,44 @@ class PmsRoomTypeAvailabilityRule(models.Model): ) ] + @api.depends("room_type_id", "date", "pms_property_id") + def _compute_avail_id(self): + for record in self: + if record.room_type_id and record.pms_property_id and record.date: + avail = self.env["pms.room.type.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", record.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if avail: + record.avail_id = avail.id + else: + record.avail_id = self.env["pms.room.type.availability"].create( + { + "date": record.date, + "room_type_id": record.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + } + ) + else: + record.avail_id = False + + @api.depends("quota", "max_avail", "real_avail") + def _compute_plan_avail(self): + for record in self.filtered("real_avail"): + real_avail = record.real_avail + plan_avail = min( + [ + record.max_avail if record.max_avail >= 0 else real_avail, + record.quota if record.quota >= 0 else real_avail, + real_avail, + ] + ) + if not record.plan_avail or record.plan_avail != plan_avail: + record.plan_avail = plan_avail + @api.depends("room_type_id") def _compute_quota(self): for record in self: diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv index 023c5fab7..2587b5b9b 100644 --- a/pms/security/ir.model.access.csv +++ b/pms/security/ir.model.access.csv @@ -12,6 +12,7 @@ user_access_pms_room_type_class,user_access_pms_room_type_class,model_pms_room_t user_access_pms_room,user_access_pms_room,model_pms_room,pms.group_pms_user,1,0,0,0 user_access_shared_pms_room,user_access_pms_shared_room,model_pms_shared_room,pms.group_pms_user,1,0,0,0 user_access_pms_room_type_availability_rule,user_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_user,1,0,0,0 +user_access_pms_room_type_availability,user_access_pms_room_type_availability,model_pms_room_type_availability,pms.group_pms_user,1,1,1,0 user_access_pms_reservation,user_access_pms_reservation,model_pms_reservation,pms.group_pms_user,1,1,1,1 user_access_pms_folio,user_access_pms_folio,model_pms_folio,pms.group_pms_user,1,1,1,1 user_access_pms_room_type,user_access_pms_room_type,model_pms_room_type,pms.group_pms_user,1,0,0,0 @@ -38,6 +39,7 @@ manager_access_pms_room,manager_access_pms_room,model_pms_room,pms.group_pms_man manager_access_pms_shared_room,manager_access_pms_shared_room,model_pms_shared_room,pms.group_pms_manager,1,1,1,1 manager_access_pms_room_type_availability_rule,manager_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_manager,1,1,1,1 manager_access_pms_reservation,manager_access_pms_reservation,model_pms_reservation,pms.group_pms_manager,1,1,1,1 +manager_access_pms_room_type_availability,manager_access_pms_room_type_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,0 manager_access_pms_folio,manager_access_pms_folio,model_pms_folio,pms.group_pms_manager,1,1,1,1 manager_access_pms_room_type,manager_access_pms_room_type,model_pms_room_type,pms.group_pms_manager,1,1,1,1 manager_access_pms_board_service_room_type,manager_access_pms_board_service_room_type,model_pms_board_service_room_type,pms.group_pms_manager,1,1,1,1 diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index f0674cafd..cb294011c 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -271,7 +271,6 @@ class TestPmsReservations(TestHotel): } ) r_test.flush() - # ASSERT self.assertEqual( expected_num_changes, diff --git a/pms/tests/test_pms_room_type_availability_rules.py b/pms/tests/test_pms_room_type_availability_rules.py index 60e92a600..d7400765a 100644 --- a/pms/tests/test_pms_room_type_availability_rules.py +++ b/pms/tests/test_pms_room_type_availability_rules.py @@ -262,6 +262,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_double.id, "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), "closed": True, # <- (1/2) + "pms_property_id": self.test_property.id, } ) # ACT @@ -269,7 +270,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): checkin=fields.date.today(), checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(), # room_type_id=False, # <- (2/2) - pricelist=self.test_pricelist1.id, + pricelist_id=self.test_pricelist1.id, ) # ASSERT self.assertNotIn( @@ -295,6 +296,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "availability_plan_id": self.test_room_type_availability1.id, "room_type_id": self.test_room_type_double.id, "date": (fields.datetime.today() + datetime.timedelta(days=0)).date(), + "pms_property_id": self.test_property.id, } ) @@ -410,7 +412,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): checkin=checkin, checkout=checkout, room_type_id=self.test_room_type_double.id, - pricelist=self.test_pricelist1.id, + pricelist_id=self.test_pricelist1.id, ) # ASSERT @@ -437,6 +439,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_double.id, "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), "closed": True, + "pms_property_id": self.test_property.id, } ) checkin = datetime.datetime.now() @@ -475,6 +478,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_double.id, "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), "closed": True, + "pms_property_id": self.test_property.id, } ) @@ -537,6 +541,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_double.id, "date": datetime.date.today(), "quota": 1, + "pms_property_id": self.test_property.id, } ) r1 = self.env["pms.reservation"].create( @@ -587,6 +592,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_double.id, "date": datetime.date.today(), "quota": test_quota, + "pms_property_id": self.test_property.id, } ) reservation = self.env["pms.reservation"].create( @@ -687,7 +693,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): fields.datetime.today() + datetime.timedelta(days=2) ).date(), room_type_id=self.test_room_type_special.id, - pricelist=self.test_pricelist1.id, + pricelist_id=self.test_pricelist1.id, pms_property_id=p["property"], ) # ASSERT @@ -730,6 +736,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_special.id, "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), "closed": True, + "pms_property_id": self.test_property1.id, } ) # Test cases when creating a availability_rule @@ -794,6 +801,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "room_type_id": self.test_room_type_special.id, "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), "closed": True, + "pms_property_id": self.test_property1.id, } ) @@ -802,6 +810,3 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): self.availability_rule1.allowed_property_ids.mapped("id"), "error", ) - - # plan 1 property 01 | rule property: False - # plan 1 property 02 | rule property: False diff --git a/pms/tests/test_pms_wizard_folio.py b/pms/tests/test_pms_wizard_folio.py index 6fb60a273..30742edf2 100644 --- a/pms/tests/test_pms_wizard_folio.py +++ b/pms/tests/test_pms_wizard_folio.py @@ -150,7 +150,6 @@ class TestPmsWizardMassiveChanges(TestHotel): # Set values for the wizard and the total price is correct # Also check the discount is correctly applied to get # the total folio price - # (no pricelist applied) # ARRANGE # common scenario @@ -183,6 +182,8 @@ class TestPmsWizardMassiveChanges(TestHotel): "start_date": checkin, "end_date": checkout, "partner_id": self.partner_id.id, + "pms_property_id": self.test_property.id, + "pricelist_id": self.test_pricelist.id, } ) @@ -206,7 +207,6 @@ class TestPmsWizardMassiveChanges(TestHotel): ) lines_availability_test[0].num_rooms_selected = value - for discount in discounts: with self.subTest(k=discount): # ACT @@ -265,6 +265,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "end_date": checkout, "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -395,6 +396,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "end_date": checkout, "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -444,6 +446,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "end_date": checkout, "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -495,6 +498,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "end_date": checkout, "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -552,6 +556,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "end_date": checkout, "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -585,6 +590,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "room_type_id": self.test_room_type_double, "partner_id": self.partner_id.id, "pricelist_id": folio.pricelist_id.id, + "pms_property_id": self.test_property.id, } # ASSERT @@ -593,7 +599,8 @@ class TestPmsWizardMassiveChanges(TestHotel): with self.subTest(k=key): self.assertEqual( reservation[key].id - if key in ["folio_id", "partner_id", "pricelist_id"] + if key + in ["folio_id", "partner_id", "pricelist_id", "pms_property_id"] else reservation[key], vals[key], "The value of " + key + " is not correctly established", @@ -620,6 +627,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "partner_id": self.partner_id.id, "pricelist_id": self.test_pricelist.id, "discount": discount, + "pms_property_id": self.test_property.id, } ) wizard_folio.flush() @@ -655,3 +663,87 @@ class TestPmsWizardMassiveChanges(TestHotel): discount * 100, "The discount is not correctly established", ) + + def test_check_quota_avail(self): + # TEST CASE + # Check avail on room type with quota + + # ARRANGE + # common scenario + self.create_common_scenario() + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + self.env["pms.room.type.availability.rule"].create( + { + "quota": 1, + "room_type_id": self.test_room_type_double.id, + "availability_plan_id": self.test_availability_plan.id, + "date": fields.date.today(), + "pms_property_id": self.test_property.id, + } + ) + + # create folio wizard with partner id => pricelist & start-end dates + wizard_folio = self.env["pms.folio.wizard"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, + } + ) + wizard_folio.flush() + + room_type_plan_avail = wizard_folio.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ).num_rooms_available + + # ASSERT + + self.assertEqual(room_type_plan_avail, 1, "Quota not applied in Wizard Folio") + + def test_check_min_stay_avail(self): + # TEST CASE + # Check avail on room type with quota + + # ARRANGE + # common scenario + self.create_common_scenario() + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + self.env["pms.room.type.availability.rule"].create( + { + "min_stay": 3, + "room_type_id": self.test_room_type_double.id, + "availability_plan_id": self.test_availability_plan.id, + "date": fields.date.today(), + "pms_property_id": self.test_property.id, + } + ) + + # create folio wizard with partner id => pricelist & start-end dates + wizard_folio = self.env["pms.folio.wizard"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.test_pricelist.id, + "pms_property_id": self.test_property.id, + } + ) + wizard_folio.flush() + + room_type_plan_avail = wizard_folio.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ).num_rooms_available + + # ASSERT + + self.assertEqual(room_type_plan_avail, 0, "Quota not applied in Wizard Folio") diff --git a/pms/tests/test_pms_wizard_massive_changes.py b/pms/tests/test_pms_wizard_massive_changes.py index bc947b20e..d5b91f047 100644 --- a/pms/tests/test_pms_wizard_massive_changes.py +++ b/pms/tests/test_pms_wizard_massive_changes.py @@ -78,6 +78,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "start_date": fields.date.today(), "end_date": fields.date.today() + datetime.timedelta(days=days), "room_type_id": self.test_room_type_double.id, + "pms_property_ids": [self.test_property.id], } ).apply_massive_changes() @@ -98,7 +99,13 @@ class TestPmsWizardMassiveChanges(TestHotel): date_from = fields.date.today() date_to = fields.date.today() + datetime.timedelta(days=3) - num_room_types = self.env["pms.room.type"].search_count([]) + num_room_types = self.env["pms.room.type"].search_count( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", self.test_property.id), + ] + ) num_exp_rules_to_create = ((date_to - date_from).days + 1) * num_room_types # ACT @@ -108,6 +115,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "availability_plan_id": self.test_availability_plan.id, "start_date": date_from, "end_date": date_to, + "pms_property_ids": [self.test_property.id], } ).apply_massive_changes() @@ -144,6 +152,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "closed": True, "closed_arrival": True, "closed_departure": True, + "pms_property_ids": [self.test_property.id], } # ACT @@ -155,6 +164,7 @@ class TestPmsWizardMassiveChanges(TestHotel): del vals["start_date"] del vals["end_date"] del vals["room_type_id"] + del vals["pms_property_ids"] for key in vals: with self.subTest(k=key): self.assertEqual( @@ -190,6 +200,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "room_type_id": self.test_room_type_double.id, "start_date": date_from, "end_date": date_to, + "pms_property_ids": [self.test_property.id], } ) @@ -243,6 +254,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "start_date": fields.date.today(), "end_date": fields.date.today() + datetime.timedelta(days=days), "room_type_id": self.test_room_type_double.id, + "pms_property_ids": [self.test_property.id], } ).apply_massive_changes() # ASSERT @@ -266,7 +278,13 @@ class TestPmsWizardMassiveChanges(TestHotel): self.create_common_scenario() date_from = fields.date.today() date_to = fields.date.today() + datetime.timedelta(days=3) - num_room_types = self.env["pms.room.type"].search_count([]) + num_room_types = self.env["pms.room.type"].search_count( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", self.test_property.id), + ] + ) num_exp_items_to_create = ((date_to - date_from).days + 1) * num_room_types # ACT @@ -276,6 +294,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "pricelist_id": self.test_pricelist.id, "start_date": date_from, "end_date": date_to, + "pms_property_ids": [self.test_property.id], } ).apply_massive_changes() @@ -305,8 +324,8 @@ class TestPmsWizardMassiveChanges(TestHotel): "date_start": date_from, "date_end": date_to, "compute_price": "fixed", - "applied_on": "1_product", - "product_tmpl_id": self.test_room_type_double.product_id.product_tmpl_id, + "applied_on": "0_product_variant", + "product_id": self.test_room_type_double.product_id, "fixed_price": price, "min_quantity": min_quantity, } @@ -321,6 +340,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "room_type_id": self.test_room_type_double.id, "price": price, "min_quantity": min_quantity, + "pms_property_ids": [self.test_property.id], } ).apply_massive_changes() vals["date_start_overnight"] = date_from @@ -362,6 +382,7 @@ class TestPmsWizardMassiveChanges(TestHotel): "room_type_id": self.test_room_type_double.id, "start_date": date_from, "end_date": date_to, + "pms_property_ids": [self.test_property.id], } ) for index, test_case in enumerate(test_case_week_days): diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index 606c795ab..352a17ca6 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -178,6 +178,7 @@ placeholder="Room Type" on_change="1" nolabel="1" + context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}" options="{'no_create': True,'no_open': True}" attrs="{'readonly':[('state','not in',('draft'))]}" style="margin-right: 30px;" diff --git a/pms/wizards/wizard_folio.py b/pms/wizards/wizard_folio.py index 59722ae3c..030dd464d 100644 --- a/pms/wizards/wizard_folio.py +++ b/pms/wizards/wizard_folio.py @@ -85,61 +85,35 @@ class FolioWizard(models.TransientModel): cmds = [(5, 0, 0)] - for room_type_iterator in self.env["pms.room.type"].search([]): - - num_rooms_available_by_date = [] - room_type_total_price_per_room = 0 - - for date_iterator in [ - record.start_date + datetime.timedelta(days=x) - for x in range(0, (record.end_date - record.start_date).days) - ]: - rooms_available = self.env[ - "pms.room.type.availability.plan" - ].rooms_available( - date_iterator, - date_iterator + datetime.timedelta(days=1), - room_type_id=room_type_iterator.id, - pricelist=record.pricelist_id.id, - ) - - num_rooms_available_by_date.append(len(rooms_available)) - product = room_type_iterator - product = product.with_context( - lang=record.partner_id.lang, - partner=record.partner_id.id, - quantity=1, - date=fields.Date.today(), - date_overnight=date_iterator, - pricelist=record.pricelist_id.id, - uom=product.uom_id.id, - property=record.pms_property_id.id, - ) - room_type_total_price_per_room += product.price - - # check there are rooms of the type - if room_type_iterator.total_rooms_count > 0: - - # get min availability between start date & end date - num_rooms_available = min(num_rooms_available_by_date) - - cmds.append( - ( - 0, - 0, - { - "folio_wizard_id": record.id, - "checkin": record.start_date, - "checkout": record.end_date, - "room_type_id": room_type_iterator.id, - "num_rooms_available": num_rooms_available, - "price_per_room": room_type_total_price_per_room - if num_rooms_available - > 0 # not showing price if there's no availability - else 0, - }, - ) + for room_type_iterator in self.env["pms.room.type"].search( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", record.pms_property_id.id), + ] + ): + num_rooms_available = self.env[ + "pms.room.type.availability.plan" + ].get_count_rooms_available( + checkin=record.start_date, + checkout=record.end_date, + room_type_id=room_type_iterator.id, + pricelist_id=record.pricelist_id.id, + pms_property_id=record.pms_property_id.id, + ) + cmds.append( + ( + 0, + 0, + { + "folio_wizard_id": record.id, + "checkin": record.start_date, + "checkout": record.end_date, + "room_type_id": room_type_iterator.id, + "num_rooms_available": num_rooms_available, + }, ) + ) # remove old items old_lines = record.availability_results.mapped("id") for old_line in old_lines: @@ -158,6 +132,7 @@ class FolioWizard(models.TransientModel): { "pricelist_id": record.pricelist_id.id, "partner_id": record.partner_id.id, + "pms_property_id": record.pms_property_id.id, } ) for line in record.availability_results: @@ -170,6 +145,7 @@ class FolioWizard(models.TransientModel): "room_type_id": line.room_type_id.id, "partner_id": folio.partner_id.id, "pricelist_id": folio.pricelist_id.id, + "pms_property_id": record.pms_property_id.id, } ) res.reservation_line_ids.discount = record.discount * 100 diff --git a/pms/wizards/wizard_folio.xml b/pms/wizards/wizard_folio.xml index 055bf7391..4a55e9842 100644 --- a/pms/wizards/wizard_folio.xml +++ b/pms/wizards/wizard_folio.xml @@ -23,11 +23,13 @@
- +
diff --git a/pms/wizards/wizard_folio_availability.py b/pms/wizards/wizard_folio_availability.py index b77340aac..1d6bfad77 100644 --- a/pms/wizards/wizard_folio_availability.py +++ b/pms/wizards/wizard_folio_availability.py @@ -33,7 +33,8 @@ class AvailabilityWizard(models.TransientModel): num_rooms_available = fields.Integer( string="Available rooms", - default=0, + compute="_compute_num_rooms_available", + store="true", ) price_per_room = fields.Float( string="Price per room", @@ -53,6 +54,10 @@ class AvailabilityWizard(models.TransientModel): price_total = fields.Float( string="Total price", default=0, compute="_compute_price_total" ) + pms_property_id = fields.Many2one( + related="folio_wizard_id.pms_property_id", + string="Property", + ) @api.depends("num_rooms_selected", "checkin", "checkout") def _compute_price_total(self): @@ -62,22 +67,13 @@ class AvailabilityWizard(models.TransientModel): # this field refresh is just to update it and take into account @ xml record.value_num_rooms_selected = record.num_rooms_selected.value - num_rooms_available_by_date = [] room_type_total_price_per_room = 0 for date_iterator in [ record.checkin + datetime.timedelta(days=x) for x in range(0, (record.checkout - record.checkin).days) ]: - rooms_available = self.env[ - "pms.room.type.availability.plan" - ].rooms_available( - date_iterator, - date_iterator + datetime.timedelta(days=1), - room_type_id=record.room_type_id.id, - pricelist=record.folio_wizard_id.pricelist_id.id, - ) - num_rooms_available_by_date.append(len(rooms_available)) + partner = record.folio_wizard_id.partner_id product = record.room_type_id.product_id product = product.with_context( @@ -92,10 +88,6 @@ class AvailabilityWizard(models.TransientModel): ) room_type_total_price_per_room += product.price - # get the availability for the entire stay (min of all dates) - if num_rooms_available_by_date: - record.num_rooms_available = min(num_rooms_available_by_date) - # udpate the price per room record.price_per_room = room_type_total_price_per_room @@ -117,6 +109,19 @@ class AvailabilityWizard(models.TransientModel): record.price_total = record.price_per_room * record.num_rooms_selected.value + @api.depends("room_type_id", "checkin", "checkout") + def _compute_num_rooms_available(self): + for record in self: + record.num_rooms_available = self.env[ + "pms.room.type.availability.plan" + ].get_count_rooms_available( + record.checkin, + record.checkout, + room_type_id=record.room_type_id.id, + pricelist_id=record.folio_wizard_id.pricelist_id.id, + pms_property_id=record.folio_wizard_id.pms_property_id.id, + ) + def _compute_dynamic_selection(self): for record in self: for elem_to_insert in range(0, record.num_rooms_available + 1): diff --git a/pms/wizards/wizard_massive_changes.py b/pms/wizards/wizard_massive_changes.py index 171408497..60e9eed7b 100644 --- a/pms/wizards/wizard_massive_changes.py +++ b/pms/wizards/wizard_massive_changes.py @@ -15,6 +15,13 @@ class AvailabilityWizard(models.TransientModel): return True if self._context.get("pricelist_id") else False # Fields declaration + pms_property_ids = fields.Many2many( + comodel_name="pms.property", + string="Property", + default=lambda self: self.env["pms.property"].browse( + self.env.user.get_active_property_ids()[0] + ), + ) massive_changes_on = fields.Selection( [("pricelist", "Pricelist"), ("availability_plan", "Availability Plan")], string="Massive changes on", @@ -236,6 +243,9 @@ class AvailabilityWizard(models.TransientModel): if record.pricelist_id: domain = [ ("pricelist_id", "=", record.pricelist_id.id), + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", record.pms_property_ids.ids), ] if record.start_date: @@ -327,41 +337,50 @@ class AvailabilityWizard(models.TransientModel): continue if not record.room_type_id: - rooms = self.env["pms.room.type"].search([]) + room_types = self.env["pms.room.type"].search( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", record.pms_property_ids.ids), + ] + ) else: - rooms = [record.room_type_id] - for room in rooms: - if record.massive_changes_on == "pricelist": - - self.env["product.pricelist.item"].create( - { - "pricelist_id": record.pricelist_id.id, - "date_start_overnight": date, - "date_end_overnight": date, - "compute_price": "fixed", - "applied_on": "1_product", - "product_tmpl_id": room.product_id.product_tmpl_id.id, - "fixed_price": record.price, - "min_quantity": record.min_quantity, - } - ) - else: - self.env["pms.room.type.availability.rule"].create( - { - "availability_plan_id": record.availability_plan_id.id, - "date": date, - "room_type_id": room.id, - "quota": record.quota, - "max_avail": record.max_avail, - "min_stay": record.min_stay, - "min_stay_arrival": record.min_stay_arrival, - "max_stay": record.max_stay, - "max_stay_arrival": record.max_stay_arrival, - "closed": record.closed, - "closed_arrival": record.closed_arrival, - "closed_departure": record.closed_departure, - } - ) + room_types = [record.room_type_id] + for room_type in room_types: + for pms_property in record.pms_property_ids: + if record.massive_changes_on == "pricelist": + self.env["product.pricelist.item"].create( + { + "pricelist_id": record.pricelist_id.id, + "date_start_overnight": date, + "date_end_overnight": date, + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": room_type.product_id.id, + "fixed_price": record.price, + "min_quantity": record.min_quantity, + "pms_property_ids": [pms_property.id], + } + ) + else: + avail_plan_id = record.availability_plan_id.id + self.env["pms.room.type.availability.rule"].create( + { + "availability_plan_id": avail_plan_id, + "date": date, + "room_type_id": room_type.id, + "quota": record.quota, + "max_avail": record.max_avail, + "min_stay": record.min_stay, + "min_stay_arrival": record.min_stay_arrival, + "max_stay": record.max_stay, + "max_stay_arrival": record.max_stay_arrival, + "closed": record.closed, + "closed_arrival": record.closed_arrival, + "closed_departure": record.closed_departure, + "pms_property_id": pms_property.id, + } + ) if ( record.massive_changes_on == "pricelist" and not record.pricelist_readonly diff --git a/pms/wizards/wizard_massive_changes.xml b/pms/wizards/wizard_massive_changes.xml index 63ac046c8..4671dd5e9 100644 --- a/pms/wizards/wizard_massive_changes.xml +++ b/pms/wizards/wizard_massive_changes.xml @@ -26,6 +26,7 @@
+ + -