diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index 5f3d9f94c0..47b21cbd98 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -894,13 +894,21 @@ def _compute_pricelist_id(self): folio.pricelist_id = False elif len(folio.reservation_ids.pricelist_id) == 1: folio.pricelist_id = folio.reservation_ids.pricelist_id - elif is_new and folio.agency_id and folio.agency_id.apply_pricelist: + elif ( + is_new + and folio.agency_id + and folio.agency_id.apply_pricelist + and folio.agency_id.property_product_pricelist._is_pms_usable( + folio.pms_property_id + ) + ): folio.pricelist_id = folio.agency_id.property_product_pricelist elif ( is_new and folio.partner_id - and folio.partner_id.property_product_pricelist - and folio.partner_id.property_product_pricelist.is_pms_available + and folio.partner_id.property_product_pricelist._is_pms_usable( + folio.pms_property_id + ) ): folio.pricelist_id = folio.partner_id.property_product_pricelist elif not folio.pricelist_id: diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 2cef114a50..0dc346ac2c 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -992,17 +992,21 @@ def _compute_pricelist_id(self): is_new and reservation.agency_id and reservation.agency_id.apply_pricelist + and reservation.agency_id.property_product_pricelist._is_pms_usable( + reservation.pms_property_id + ) ): reservation.pricelist_id = ( reservation.agency_id.property_product_pricelist ) # only change de pricelist if the reservation is not yet saved - # and the partner has a pricelist default + # and the partner has a pricelist default usable in its property elif ( is_new and reservation.partner_id - and reservation.partner_id.property_product_pricelist - and reservation.partner_id.property_product_pricelist.is_pms_available + and reservation.partner_id.property_product_pricelist._is_pms_usable( + reservation.pms_property_id + ) ): reservation.pricelist_id = ( reservation.partner_id.property_product_pricelist diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py index b78c346163..c92845b90a 100644 --- a/pms/models/product_pricelist.py +++ b/pms/models/product_pricelist.py @@ -158,6 +158,23 @@ def _compute_price_rule(self, products, qty, uom=None, date=False, **kwargs): return results + def _is_pms_usable(self, pms_property): + """Return whether the pricelist can be used in the given property. + + It must be available in the PMS and not restricted to other + properties. Without a property (e.g. not chosen yet in a form) + only the PMS availability is checked. An empty recordset is + never usable. + """ + return bool( + self.is_pms_available + and ( + not pms_property + or not self.pms_property_ids + or pms_property in self.pms_property_ids + ) + ) + @api.constrains("is_pms_available", "availability_plan_id") def _check_is_pms_available(self): for record in self: diff --git a/pms/tests/common.py b/pms/tests/common.py index 3909870aa4..93c308342e 100644 --- a/pms/tests/common.py +++ b/pms/tests/common.py @@ -47,3 +47,61 @@ def setUpClass(cls): if not pricelist.availability_plan_id: pricelist.availability_plan_id = cls.availability_plan1.id pricelist.is_pms_available = True + + def create_pricelist_by_property_scenario(self): + """ + - create pms_property2 in company1, with room_type_property2 + and one room of it + - create pricelist_property1 and pricelist_property2, each one + restricted to its property; pricelist_property2 is the default + pricelist of pms_property2 + - create partner_pricelist_property1 with pricelist_property1 + as its pricelist + """ + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.pricelist_property1 = self.env["product.pricelist"].create( + { + "name": "Pricelist Property 1", + "is_pms_available": True, + "availability_plan_id": self.availability_plan1.id, + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + self.pricelist_property2 = self.env["product.pricelist"].create( + { + "name": "Pricelist Property 2", + "is_pms_available": True, + "availability_plan_id": self.availability_plan1.id, + "pms_property_ids": [(6, 0, [self.pms_property2.id])], + } + ) + self.pms_property2.default_pricelist_id = self.pricelist_property2 + self.partner_pricelist_property1 = self.env["res.partner"].create( + { + "name": "Partner Pricelist Property 1", + "property_product_pricelist": self.pricelist_property1.id, + } + ) + self.room_type_property2 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property2.id], + "name": "Double Property 2", + "default_code": "DBL_P2", + "class_id": self.room_type_class1.id, + "list_price": 25, + } + ) + self.env["pms.room"].create( + { + "pms_property_id": self.pms_property2.id, + "name": "Double 201", + "room_type_id": self.room_type_property2.id, + "capacity": 2, + } + ) diff --git a/pms/tests/test_pms_booking_engine.py b/pms/tests/test_pms_booking_engine.py index d3250803c9..36b4ca6a01 100644 --- a/pms/tests/test_pms_booking_engine.py +++ b/pms/tests/test_pms_booking_engine.py @@ -839,6 +839,31 @@ def _test_board_service_discount(self): "The discount is not correctly established", ) + def test_partner_pricelist_from_another_property(self): + """ + Check that the booking engine does not propose the partner + pricelist when that pricelist is restricted to another property. + ------------- + Open the booking engine in pms_property2 for a partner whose + pricelist only belongs to pms_property1. The proposed pricelist + must be the default pricelist of pms_property2. + """ + self.create_pricelist_by_property_scenario() + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": fields.date.today(), + "end_date": fields.date.today() + datetime.timedelta(days=1), + "partner_id": self.partner_pricelist_property1.id, + "pms_property_id": self.pms_property2.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + self.assertEqual( + booking_engine.pricelist_id, + self.pricelist_property2, + "The booking engine proposed a pricelist of another property", + ) + def _test_adding_board_services_are_saved_on_lines(self): checkin = fields.date.today() checkout = fields.date.today() + datetime.timedelta(days=1) diff --git a/pms/tests/test_pms_folio.py b/pms/tests/test_pms_folio.py index cf3a35c296..3a5d17f987 100644 --- a/pms/tests/test_pms_folio.py +++ b/pms/tests/test_pms_folio.py @@ -1550,3 +1550,59 @@ def test_pms_folio_priority_fiscal_position_property(self): "The fiscal_position_id of the folio was not set " "correctly from the property", ) + + def test_folio_partner_pricelist_from_another_property(self): + """ + Check that a new folio does not take the partner pricelist + when that pricelist is restricted to another property. + ------------- + Create a folio without reservations in pms_property2 for a + partner whose pricelist only belongs to pms_property1. The + folio must be created with the default pricelist of + pms_property2 instead of failing on the property check. + """ + self.create_pricelist_by_property_scenario() + folio = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property2.id, + "partner_id": self.partner_pricelist_property1.id, + } + ) + self.assertEqual( + folio.pricelist_id, + self.pricelist_property2, + "The folio took a partner pricelist of another property", + ) + + def test_folio_partner_pricelist_from_same_property(self): + """ + Check that a new folio still takes the partner pricelist + when that pricelist belongs to the folio property. + """ + self.create_pricelist_by_property_scenario() + folio = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner_pricelist_property1.id, + } + ) + self.assertEqual( + folio.pricelist_id, + self.pricelist_property1, + "The folio did not take the partner pricelist", + ) + + def test_folio_form_partner_pricelist_before_property(self): + """ + Check that the folio form keeps the partner pricelist restricted + to a property when the partner is chosen before that property. + """ + self.create_pricelist_by_property_scenario() + folio_form = Form(self.env["pms.folio"]) + folio_form.partner_id = self.partner_pricelist_property1 + folio_form.pms_property_id = self.pms_property1 + self.assertEqual( + folio_form.pricelist_id, + self.pricelist_property1, + "The folio form lost the partner pricelist", + ) diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index 247d92574c..cf18101423 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -1206,6 +1206,86 @@ def test_agency_pricelist(self): "Rervation pricelist doesn't match with Agency pricelist", ) + def test_partner_pricelist_from_another_property(self): + """ + Check that a reservation does not take the partner pricelist + when that pricelist is restricted to another property. + ------------- + Create a reservation without pricelist in pms_property2 for a + partner whose pricelist only belongs to pms_property1. The + reservation and its folio must take the default pricelist of + pms_property2 instead of failing on the property check. + """ + self.create_pricelist_by_property_scenario() + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property2.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "partner_id": self.partner_pricelist_property1.id, + "room_type_id": self.room_type_property2.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertEqual( + reservation.pricelist_id, + self.pricelist_property2, + "The reservation took a partner pricelist of another property", + ) + self.assertEqual( + reservation.folio_id.pricelist_id, + self.pricelist_property2, + "The folio took a partner pricelist of another property", + ) + + def test_agency_pricelist_from_another_property(self): + """ + Check that a reservation does not take the agency pricelist + when that pricelist is restricted to another property. + ------------- + Create a reservation without partner in pms_property2 for an + agency that applies its pricelist, which only belongs to + pms_property1. The reservation and its folio must take the + default pricelist of pms_property2 instead of failing on the + property check. + """ + self.create_pricelist_by_property_scenario() + sale_channel1 = self.env["pms.sale.channel"].create( + { + "name": "Test Indirect", + "channel_type": "indirect", + } + ) + agency = self.env["res.partner"].create( + { + "firstname": "partner1", + "is_agency": True, + "sale_channel_id": sale_channel1.id, + "apply_pricelist": True, + "property_product_pricelist": self.pricelist_property1.id, + } + ) + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property2.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "agency_id": agency.id, + "room_type_id": self.room_type_property2.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertEqual( + reservation.pricelist_id, + self.pricelist_property2, + "The reservation took an agency pricelist of another property", + ) + self.assertEqual( + reservation.folio_id.pricelist_id, + self.pricelist_property2, + "The folio took an agency pricelist of another property", + ) + def test_compute_access_url(self): """ Check that the access_url field of the reservation is diff --git a/pms/wizards/pms_booking_engine.py b/pms/wizards/pms_booking_engine.py index ec20f0b497..090b2c432e 100644 --- a/pms/wizards/pms_booking_engine.py +++ b/pms/wizards/pms_booking_engine.py @@ -131,8 +131,10 @@ def _compute_pricelist_id(self): for record in self: record.pricelist_id = ( record.partner_id.property_product_pricelist.id - if record.partner_id.property_product_pricelist.is_pms_available - else self.pms_property_id.default_pricelist_id.id + if record.partner_id.property_product_pricelist._is_pms_usable( + record.pms_property_id + ) + else record.pms_property_id.default_pricelist_id.id ) @api.depends("agency_id")