From 57d3ab0c8a2cf74b32b246fe51819515bd10bdcc Mon Sep 17 00:00:00 2001 From: Sara <49147098+saralb9@users.noreply.github.com> Date: Sun, 31 Jan 2021 13:07:03 +0100 Subject: [PATCH] Multiproperty Constrains (#30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [IMP] add multiproperties demo data * [IMP] add multiproperties checks in res_users * [IMP] add test case in test_res_users * [IMP] Add multiproperty checks in pms_amenity and pms_amenity_type * [IMP] Add multiproperty in pms_board_service_room_type(pending review) * [IMP] Add test case in test_pms_room_type_availability_rule to check multiproperties * [IMP] Fixing test case in test_pms_room_type_availability_rule to check multiproperties * [IMP] Add test case in test_pms_room_type_availability_rule * [IMP] Removed field default_availability_plan_id from pms_property * [IMP] Add multiproperty in pms_room_type_available_plan * [IMP] pms: adding property in rooms_available * [IMP] Add multiproperty in pms_room_type_availability_rule and product_pricelist(work in progress) * [IMP] Add multiproperty in product_pricelist and product_pricelist_item * [IMP] add multiproperties demo data * [IMP] add multiproperties checks in res_users * [IMP] add test case in test_res_users and pms_room_type_availability_rule * [IMP] Add multiproperty checks in pms_amenity and pms_amenity_type * [IMP] Add multiproperty in pms_board_service_room_type(pending review) * [IMP] Removed field default_availability_plan_id from pms_property * [IMP] Add multiproperty in pms_room_type_available_plan * [IMP] pms: adding property in rooms_available * [IMP] Add multiproperty in pms_room_type_availability_rule and product_pricelist(work in progress) * [IMP] Add multiproperty in product_pricelist and product_pricelist_item * [IMP] Pms: add compute_folio method in pms.service * [IMP] Pms: add multiproperty integrity checks between room_type and its class * [IMP] Pms: pms_property_id related to folio * [IMP] Pms: add multiproperty integrity checks in pms_room with pms_room_type and pms_floor * [IMP] Pms: adding multiproperty checks in room_type(work in progress) * [IMP] Pms: Add property rules * [FIX]pms: external ids security rules * [FIX]pms: property checks * [FIX]pms: get product on pricelist item multiproperty check * [FIX]pms: delete test field default_plan * [FIX]pms: property constrain to product from room type model * [FIX]pms: ids references * [IMP]pms: folio wizard price flow on odoo standar Co-authored-by: Darío Lodeiros --- pms/data/pms_data.xml | 4 - pms/demo/pms_master_data.xml | 47 ++-- pms/models/pms_amenity.py | 36 ++- pms/models/pms_board_service_room_type.py | 6 +- pms/models/pms_checkin_partner.py | 7 +- pms/models/pms_folio.py | 10 +- pms/models/pms_property.py | 6 - pms/models/pms_reservation.py | 7 +- pms/models/pms_room.py | 46 ++++ pms/models/pms_room_type.py | 27 +- pms/models/pms_room_type_availability_plan.py | 16 +- pms/models/pms_room_type_availability_rule.py | 72 ++++- pms/models/pms_service.py | 24 +- pms/models/product_pricelist.py | 14 +- pms/models/product_pricelist_item.py | 54 +++- pms/models/product_template.py | 24 +- pms/models/res_users.py | 24 +- pms/security/pms_security.xml | 193 ++++++++++++- pms/tests/__init__.py | 3 + pms/tests/test_pms_amenity.py | 98 +++++++ pms/tests/test_pms_pricelist.py | 111 ++++++++ pms/tests/test_pms_pricelist_priority.py | 2 - pms/tests/test_pms_res_users.py | 91 +++++++ pms/tests/test_pms_reservation.py | 1 - pms/tests/test_pms_room.py | 81 ++++++ pms/tests/test_pms_room_type.py | 32 ++- .../test_pms_room_type_availability_rules.py | 257 +++++++++++++++++- pms/tests/test_pms_wizard_folio.py | 142 +++++----- pms/tests/test_pms_wizard_massive_changes.py | 1 - pms/views/pms_property_views.xml | 11 - .../pms_room_type_availability_plan_views.xml | 5 +- .../pms_room_type_availability_rule_views.xml | 7 + pms/wizards/wizard_folio.py | 42 ++- pms/wizards/wizard_folio_availability.py | 38 +-- 34 files changed, 1298 insertions(+), 241 deletions(-) create mode 100644 pms/tests/test_pms_amenity.py create mode 100644 pms/tests/test_pms_res_users.py create mode 100644 pms/tests/test_pms_room.py diff --git a/pms/data/pms_data.xml b/pms/data/pms_data.xml index b7b08f8e8..20240d309 100644 --- a/pms/data/pms_data.xml +++ b/pms/data/pms_data.xml @@ -12,10 +12,6 @@ My Property - Rua Street Demo, s/n Commitsun city diff --git a/pms/demo/pms_master_data.xml b/pms/demo/pms_master_data.xml index 9ec81ce2d..f838f6dfe 100644 --- a/pms/demo/pms_master_data.xml +++ b/pms/demo/pms_master_data.xml @@ -1,6 +1,37 @@ + + + Pms Company + + + + + Availability Plan Demo + + + My pms Demo + + + + + My property 2 + + + + + Independent property + + + @@ -305,22 +336,6 @@ Used for closing of rooms for extra privacy. - - - Availability Plan Demo - - - My pms Demo - - - - diff --git a/pms/models/pms_amenity.py b/pms/models/pms_amenity.py index 3546434d8..5f4c37a1d 100644 --- a/pms/models/pms_amenity.py +++ b/pms/models/pms_amenity.py @@ -1,7 +1,8 @@ # Copyright 2017 Alexandre Díaz # Copyright 2017 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class PmsRoomAmenity(models.Model): @@ -18,3 +19,36 @@ class PmsRoomAmenity(models.Model): active = fields.Boolean("Active", default=True) # TODO: Constrain coherence pms_property_ids with amenity types pms_property_ids + allowed_property_ids = fields.Many2many( + "pms.property", + "allowed_amenity_move_rel", + "amenity_id", + "property_id", + string="Allowed Properties", + store=True, + readonly=True, + compute="_compute_allowed_property_ids", + ) + + @api.depends( + "room_amenity_type_id.pms_property_ids", + ) + def _compute_allowed_property_ids(self): + for amenity in self: + if amenity.room_amenity_type_id.pms_property_ids: + amenity.allowed_property_ids = ( + amenity.room_amenity_type_id.pms_property_ids + ) + else: + amenity.allowed_property_ids = False + + @api.constrains( + "allowed_property_ids", + "pms_property_ids", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_ids and rec.allowed_property_ids: + for prop in rec.pms_property_ids: + if prop not in rec.allowed_property_ids: + raise ValidationError(_("Property not allowed")) diff --git a/pms/models/pms_board_service_room_type.py b/pms/models/pms_board_service_room_type.py index 1b63ae6ab..3d64ce194 100644 --- a/pms/models/pms_board_service_room_type.py +++ b/pms/models/pms_board_service_room_type.py @@ -47,8 +47,12 @@ class PmsBoardServiceRoomType(models.Model): board_service_line_ids = fields.One2many( "pms.board.service.room.type.line", "pms_board_service_room_type_id" ) - pms_property_id = fields.Many2one( + # TODO:review relation with pricelist and properties + pms_property_ids = fields.Many2many( "pms.property", + string="Properties", + required=False, + ondelete="restrict", ) price_type = fields.Selection( [("fixed", "Fixed"), ("percent", "Percent")], diff --git a/pms/models/pms_checkin_partner.py b/pms/models/pms_checkin_partner.py index d74195b18..23b3d952c 100644 --- a/pms/models/pms_checkin_partner.py +++ b/pms/models/pms_checkin_partner.py @@ -13,11 +13,6 @@ class PmsCheckinPartner(models.Model): _name = "pms.checkin.partner" _description = "Partner Checkins" - @api.model - def _get_default_pms_property(self): - # TODO: Change by property env variable (like company) - return self.env.user.pms_property_id - # Fields declaration identifier = fields.Char( "Identifier", readonly=True, index=True, default=lambda self: _("New") @@ -33,7 +28,7 @@ class PmsCheckinPartner(models.Model): store=True, ) pms_property_id = fields.Many2one( - "pms.property", default=_get_default_pms_property, required=True + "pms.property", store=True, readonly=True, related="folio_id.pms_property_id" ) name = fields.Char( "Name", diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index e985ad667..8c3d27b14 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -28,12 +28,6 @@ class PmsFolio(models.Model): result.append((folio.id, name)) return result - @api.model - def _get_default_pms_property(self): - return ( - self.env.user.pms_property_id - ) # TODO: Change by property env variable (like company) - def _default_note(self): return ( self.env["ir.config_parameter"] @@ -48,7 +42,9 @@ class PmsFolio(models.Model): string="Folio Number", readonly=True, index=True, default=lambda self: _("New") ) pms_property_id = fields.Many2one( - "pms.property", default=_get_default_pms_property, required=True + "pms.property", + default=lambda self: self.env.user.get_active_property_ids()[0], + required=True, ) partner_id = fields.Many2one( "res.partner", diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py index 9ac5876df..d0a583c24 100644 --- a/pms/models/pms_property.py +++ b/pms/models/pms_property.py @@ -41,12 +41,6 @@ class PmsProperty(models.Model): required=True, help="The default pricelist used in this property.", ) - default_availability_plan_id = fields.Many2one( - "pms.room.type.availability.plan", - "Availability Plan", - required=True, - help="The default availability plan used in this property.", - ) default_arrival_hour = fields.Char( "Arrival Hour (GMT)", help="HH:mm Format", default="14:00" ) diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 9c6dfe502..ecff34948 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -68,11 +68,6 @@ class PmsReservation(models.Model): else: return default_departure_hour - @api.model - def _get_default_pms_property(self): - # TODO: Change by property env variable (like company) - return self.env.user.pms_property_id - def _get_default_segmentation(self): folio = False segmentation_ids = False @@ -158,7 +153,7 @@ class PmsReservation(models.Model): ) pms_property_id = fields.Many2one( "pms.property", - default=_get_default_pms_property, # required=True + default=lambda self: self.env.user.get_active_property_ids()[0], ) reservation_line_ids = fields.One2many( "pms.reservation.line", diff --git a/pms/models/pms_room.py b/pms/models/pms_room.py index c2d780b68..6d4b85f92 100644 --- a/pms/models/pms_room.py +++ b/pms/models/pms_room.py @@ -32,6 +32,7 @@ class PmsRoom(models.Model): "pms.property", required=True, ondelete="restrict", + default=lambda self: self.env.user.get_active_property_ids()[0], ) room_type_id = fields.Many2one( "pms.room.type", "Property Room Type", required=True, ondelete="restrict" @@ -55,6 +56,41 @@ class PmsRoom(models.Model): active = fields.Boolean("Active", default=True) sequence = fields.Integer("Sequence", default=0) + allowed_property_ids = fields.Many2many( + comodel_name="pms.property", + relation="room_property_rel", + column1="room_id", + column2="property_id", + string="Allowed properties", + store=True, + readonly=True, + compute="_compute_allowed_property_ids", + ) + + @api.depends("room_type_id.pms_property_ids", "floor_id.pms_property_ids") + def _compute_allowed_property_ids(self): + for record in self: + if not ( + record.room_type_id.pms_property_ids or record.floor_id.pms_property_ids + ): + record.allowed_property_ids = False + else: + if record.room_type_id.pms_property_ids: + if record.floor_id.pms_property_ids: + properties = list( + set(record.room_type_id.pms_property_ids.ids) + & set(record.floor_id.pms_property_ids.ids) + ) + record.allowed_property_ids = self.env["pms.property"].search( + [("id", "in", properties)] + ) + else: + record.allowed_property_ids = ( + record.room_type_id.pms_property_ids + ) + else: + record.allowed_property_ids = record.floor_id.pms_property_ids + # Constraints and onchanges @api.constrains("capacity") def _check_capacity(self): @@ -67,6 +103,16 @@ class PmsRoom(models.Model): ) ) + @api.constrains( + "allowed_property_ids", + "pms_property_id", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_id and rec.allowed_property_ids: + if rec.pms_property_id.id not in rec.allowed_property_ids.ids: + raise ValidationError(_("Property not allowed")) + # Business methods def get_capacity(self, extra_bed=0): diff --git a/pms/models/pms_room_type.py b/pms/models/pms_room_type.py index 861283268..16e6d5e90 100644 --- a/pms/models/pms_room_type.py +++ b/pms/models/pms_room_type.py @@ -26,14 +26,6 @@ class PmsRoomType(models.Model): delegate=True, ondelete="cascade", ) - pms_property_ids = fields.Many2many( - "pms.property", - "pms_property_room_type_rel", - "room_type_id", - "pms_property_id", - ondelete="restrict", - string="Properties", - ) room_ids = fields.One2many("pms.room", "room_type_id", "Rooms") class_id = fields.Many2one("pms.room.type.class", "Property Type Class") board_service_room_type_ids = fields.One2many( @@ -122,18 +114,15 @@ class PmsRoomType(models.Model): ) return self.browse(list(res.values())) - @api.constrains("pms_property_ids", "company_id") - def _check_property_company_integrity(self): - for rec in self: - if rec.company_id and rec.pms_property_ids: - property_companies = rec.pms_property_ids.mapped("company_id") - if len(property_companies) > 1 or rec.company_id != property_companies: - raise ValidationError( - _( - "The company of the properties must match " - "the company on the room type" + @api.constrains("pms_property_ids") + def _check_integrity_property_class(self): + for record in self: + if record.pms_property_ids and record.class_id: + for pms_property in record.pms_property_ids: + if pms_property.id not in record.class_id.pms_property_ids.ids: + raise ValidationError( + _("Property isn't allowed in Room Type Class") ) - ) @api.constrains("code_type", "pms_property_ids", "company_id") def _check_code_property_company_uniqueness(self): diff --git a/pms/models/pms_room_type_availability_plan.py b/pms/models/pms_room_type_availability_plan.py index e8a6e35a4..cfb27fd02 100644 --- a/pms/models/pms_room_type_availability_plan.py +++ b/pms/models/pms_room_type_availability_plan.py @@ -19,9 +19,9 @@ class PmsRoomTypeAvailability(models.Model): # Fields declaration name = fields.Char("Availability Plan Name", required=True) - pms_property_id = fields.Many2one( + pms_property_ids = fields.Many2many( comodel_name="pms.property", - string="Property", + string="Properties", ondelete="restrict", ) @@ -70,6 +70,7 @@ class PmsRoomTypeAvailability(models.Model): room_type_id=False, current_lines=False, pricelist=False, + pms_property_id=False, ): if current_lines and not isinstance(current_lines, list): current_lines = [current_lines] @@ -88,13 +89,17 @@ class PmsRoomTypeAvailability(models.Model): ) domain_rooms = [ - ("id", "not in", rooms_not_avail if len(rooms_not_avail) > 0 else []) + ("id", "not in", rooms_not_avail if len(rooms_not_avail) > 0 else []), ] domain_rules = [ ("date", ">=", checkin), ("date", "<=", checkout), ] + 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)) @@ -118,6 +123,11 @@ class PmsRoomTypeAvailability(models.Model): lambda x: x.room_type_id.id not in room_types_to_remove ) + # 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) @api.model diff --git a/pms/models/pms_room_type_availability_rule.py b/pms/models/pms_room_type_availability_rule.py index e276f9d62..d99569400 100644 --- a/pms/models/pms_room_type_availability_rule.py +++ b/pms/models/pms_room_type_availability_rule.py @@ -68,10 +68,27 @@ class PmsRoomTypeAvailabilityRule(models.Model): help="Maximum simultaneous availability on own Booking Engine.", ) + pms_property_id = fields.Many2one( + comodel_name="pms.property", + string="Property", + ondelete="restrict", + ) + + allowed_property_ids = fields.Many2many( + "pms.property", + "allowed_availability_move_rel", + "availability_rule_id", + "property_id", + string="Allowed Properties", + store=True, + readonly=True, + compute="_compute_allowed_property_ids", + ) + _sql_constraints = [ ( "room_type_registry_unique", - "unique(availability_plan_id, room_type_id, date)", + "unique(availability_plan_id, room_type_id, date, pms_property_id)", "Only can exists one availability rule in the same \ day for the same room type!", ) @@ -89,6 +106,59 @@ class PmsRoomTypeAvailabilityRule(models.Model): if not record.max_avail: record.max_avail = record.room_type_id.default_max_avail + @api.depends( + "availability_plan_id.pms_property_ids", "room_type_id.pms_property_ids" + ) + def _compute_allowed_property_ids(self): + + for rule in self: + properties = [] + + if not ( + rule.availability_plan_id.pms_property_ids + or rule.room_type_id.pms_property_ids + ): + rule.allowed_property_ids = False + else: + if rule.availability_plan_id.pms_property_ids: + if rule.room_type_id.pms_property_ids: + for prp in rule.availability_plan_id.pms_property_ids: + if prp in rule.room_type_id.pms_property_ids: + properties.append(prp) + rule.allowed_property_ids = [ + (4, prop.id) for prop in properties + ] + else: + rule.allowed_property_ids = ( + rule.availability_plan_id.pms_property_ids + ) + else: + rule.allowed_property_ids = rule.room_type_id.pms_property_ids + + @api.constrains( + "allowed_property_ids", + "pms_property_id", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_id and rec.allowed_property_ids: + if rec.pms_property_id.id not in rec.allowed_property_ids.ids: + raise ValidationError(_("Property not allowed")) + + # @api.constrains( + # "allowed_property_ids", + # "pms_property_ids", + # ) + # def _check_property_integrity(self): + # for rule in self: + # for p in rule.pms_property_ids: + # allowed = list( + # set(rule.room_type_id.pms_property_ids.ids) + # & + # set(rule.availability_plan_id.pms_property_ids.ids)) + # if p.id not in allowed: + # raise ValidationError(_("Property not allowed")) + @api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival") def _check_min_max_stay(self): for record in self: diff --git a/pms/models/pms_service.py b/pms/models/pms_service.py index ce4e59df3..d69d72205 100644 --- a/pms/models/pms_service.py +++ b/pms/models/pms_service.py @@ -35,12 +35,6 @@ class PmsService(models.Model): return self.env.context.get("default_reservation_id") return False - @api.model - def _default_folio_id(self): - if "folio_id" in self._context: - return self._context["folio_id"] - return False - # Fields declaration name = fields.Char( "Service description", @@ -52,7 +46,11 @@ class PmsService(models.Model): "product.product", "Service", ondelete="restrict", required=True ) folio_id = fields.Many2one( - "pms.folio", "Folio", ondelete="cascade", default=_default_folio_id + comodel_name="pms.folio", + string="Folio", + compute="_compute_folio_id", + readonly=False, + store=True ) reservation_id = fields.Many2one( "pms.reservation", "Room", default=_default_reservation_id @@ -68,7 +66,7 @@ class PmsService(models.Model): related="folio_id.company_id", string="Company", store=True, readonly=True ) pms_property_id = fields.Many2one( - "pms.property", store=True, readonly=True, related="folio_id.pms_property_id" + comodel_name="pms.property", store=True, readonly=True, related="folio_id.pms_property_id" ) tax_ids = fields.Many2many( "account.tax", @@ -372,6 +370,16 @@ class PmsService(models.Model): else: service.price_unit = 0 + @api.depends( + "reservation_id" + ) + def _compute_folio_id(self): + for record in self: + if record.reservation_id: + record.folio_id = record.reservation_id.folio_id + elif not record.folio_id: + record.folio_id = False + def _recompute_price(self): # REVIEW: Conditional to avoid overriding already calculated prices, # I'm not sure it's the best way diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py index d77a9b412..280075afc 100644 --- a/pms/models/product_pricelist.py +++ b/pms/models/product_pricelist.py @@ -2,7 +2,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -68,7 +69,6 @@ class ProductPricelist(models.Model): def _compute_price_rule_get_items( self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids ): - if "property" in self._context and self._context["property"]: self.env["product.pricelist.item"].flush( ["price", "currency_id", "company_id"] @@ -147,3 +147,13 @@ class ProductPricelist(models.Model): "pricelist_id": self.id, }, } + + @api.constrains( + "cancelation_rule_id", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_ids: + for p in rec.pms_property_ids: + if p.id not in rec.cancelation_rule_id.pms_property_ids.ids: + raise ValidationError(_("Property not allowed")) diff --git a/pms/models/product_pricelist_item.py b/pms/models/product_pricelist_item.py index 47249481c..ea1738655 100644 --- a/pms/models/product_pricelist_item.py +++ b/pms/models/product_pricelist_item.py @@ -1,6 +1,7 @@ # Copyright 2017 Alexandre Díaz, Pablo Quesada, Darío Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class ProductPricelistItem(models.Model): @@ -17,3 +18,54 @@ class ProductPricelistItem(models.Model): string="End Date Overnight", help="End date to apply daily pricelist items", ) + + allowed_property_ids = fields.Many2many( + "pms.property", + "allowed_pricelist_move_rel", + "pricelist_item_id", + "property_id", + string="Allowed Properties", + store=True, + readonly=True, + compute="_compute_allowed_property_ids", + ) + + @api.depends("product_id.pms_property_ids", "pricelist_id.pms_property_ids") + def _compute_allowed_property_ids(self): + for record in self: + properties = [] + if record.applied_on == "0_product_variant": + product = record.product_id + elif record.applied_on == "1_product": + product = record.product_tmpl_id + else: + product = False + if not record.pricelist_id.pms_property_ids or not product: + record.allowed_property_ids = False + else: + if record.pricelist_id.pms_property_ids: + if product.pms_property_ids: + properties = list( + set(record.pricelist_id.pms_property_ids.ids) + & set(product.pms_property_ids.ids) + ) + record.allowed_property_ids = self.env["pms.property"].search( + [("id", "in", properties)] + ) + else: + record.allowed_property_ids = product.pms_property_ids + else: + record.allowed_property_ids = product.pms_property_ids + # else: + # record.allowed_property_ids = False + + @api.constrains( + "allowed_property_ids", + "pms_property_ids", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_ids and rec.allowed_property_ids: + for p in rec.pms_property_ids: + if p.id not in rec.allowed_property_ids.ids: + raise ValidationError(_("Property not allowed")) diff --git a/pms/models/product_template.py b/pms/models/product_template.py index 8f788d13d..4b6a5db07 100644 --- a/pms/models/product_template.py +++ b/pms/models/product_template.py @@ -1,14 +1,21 @@ # Copyright 2017 Alexandre Díaz # Copyright 2017 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class ProductTemplate(models.Model): _inherit = "product.template" pms_property_ids = fields.Many2many( - "pms.property", string="Properties", required=False, ondelete="restrict" + comodel_name="pms.property", + relation="product_template_property_rel", + column1="product_tmpl_id", + column2="property_id", + string="Properties", + required=False, + ondelete="restrict", ) per_day = fields.Boolean("Unit increment per day") per_person = fields.Boolean("Unit increment per person") @@ -24,3 +31,16 @@ class ProductTemplate(models.Model): default=False, help="Specifies if the product is shown in the calendar information.", ) + + @api.constrains("pms_property_ids", "company_id") + def _check_property_company_integrity(self): + for rec in self: + if rec.company_id and rec.pms_property_ids: + property_companies = rec.pms_property_ids.mapped("company_id") + if len(property_companies) > 1 or rec.company_id != property_companies: + raise ValidationError( + _( + "The company of the properties must match " + "the company on the room type" + ) + ) diff --git a/pms/models/res_users.py b/pms/models/res_users.py index 9ca390e3e..7b2d9378a 100644 --- a/pms/models/res_users.py +++ b/pms/models/res_users.py @@ -1,6 +1,7 @@ # Copyright 2019 Pablo Quesada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError from odoo.http import request @@ -16,9 +17,9 @@ class ResUsers(models.Model): pms_property_id = fields.Many2one( "pms.property", string="Property", - default=_get_default_pms_property, help="The property this user is currently working for.", context={"user_preference": True}, + domain="[('id','in',pms_property_ids)]", ) pms_property_ids = fields.Many2many( "pms.property", @@ -26,8 +27,9 @@ class ResUsers(models.Model): "user_id", "pms_property_id", string="Properties", - default=_get_default_pms_property, + domain="[('company_id','in',company_ids)]", ) + company_id = fields.Many2one(domain="[('id','in',company_ids)]") @api.model def get_active_property_ids(self): @@ -43,3 +45,19 @@ class ResUsers(models.Model): ] return self.env["pms.property"].browse(active_property_ids).ids return user_property_ids + + @api.constrains("pms_property_id", "pms_property_ids") + def _check_property_in_allowed_properties(self): + if any(user.pms_property_id not in user.pms_property_ids for user in self): + raise ValidationError( + _("The chosen property is not in the allowed properties for this user") + ) + + @api.constrains("pms_property_ids", "company_id") + def _check_company_in_property_ids(self): + for record in self: + for pms_property in record.pms_property_ids: + if pms_property.company_id not in record.company_ids: + raise ValidationError( + _("Some properties do not belong to the allowed companies") + ) diff --git a/pms/security/pms_security.xml b/pms/security/pms_security.xml index 030bd20ea..3b426f770 100644 --- a/pms/security/pms_security.xml +++ b/pms/security/pms_security.xml @@ -35,7 +35,7 @@ - PMS Folio Company Rule + PMS Folio Property Rule @@ -44,7 +44,7 @@ - PMS Reservation Company Rule + PMS Reservation Property Rule @@ -52,5 +52,194 @@ user.get_active_property_ids())] + + PMS Amenity Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Amenity Type Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Line Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Room Type Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Cancelation Rule Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Checkin Partner Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Floor Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Reservation Line Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Clousure Reason Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Availability Plan Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Availability Rule Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Class Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Service Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Service Line Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Product Pricelist Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Class Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Users Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Account Move Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Account Journal Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py index 6e71f7c33..2b46906e6 100644 --- a/pms/tests/__init__.py +++ b/pms/tests/__init__.py @@ -29,3 +29,6 @@ from . import test_pms_room_type_availability_rules from . import test_pms_room_type from . import test_pms_wizard_massive_changes from . import test_pms_wizard_folio +from . import test_pms_res_users +from . import test_pms_amenity +from . import test_pms_room diff --git a/pms/tests/test_pms_amenity.py b/pms/tests/test_pms_amenity.py new file mode 100644 index 000000000..057f2e25d --- /dev/null +++ b/pms/tests/test_pms_amenity.py @@ -0,0 +1,98 @@ +from odoo.exceptions import ValidationError + +from .common import TestHotel + + +class TestPmsAmenity(TestHotel): + def create_common_scenario(self): + # create company and properties + self.company1 = self.env["res.company"].create( + { + "name": "Pms_Company_Test", + } + ) + self.property1 = self.env["pms.property"].create( + { + "name": "Pms_property_test1", + "company_id": self.company1.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + self.property2 = self.env["pms.property"].create( + { + "name": "Pms_property_test2", + "company_id": self.company1.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + self.property3 = self.env["pms.property"].create( + { + "name": "Pms_property_test3", + "company_id": self.company1.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + def test_property_not_allowed(self): + # ARRANGE + name = "amenityTest1" + name2 = "amenity" + self.create_common_scenario() + AmenityType = self.env["pms.amenity.type"] + Amenity = self.env["pms.amenity"] + # ACT + A1 = AmenityType.create( + { + "name": name, + "pms_property_ids": [ + (4, self.property1.id), + (4, self.property2.id), + ], + } + ) + # ASSERT + with self.assertRaises(ValidationError), self.cr.savepoint(): + Amenity.create( + { + "name": name2, + "room_amenity_type_id": A1.id, + "pms_property_ids": [ + (4, self.property1.id), + (4, self.property2.id), + (4, self.property3.id), + ], + } + ) + + def test_check_allowed_property_ids(self): + # ARRANGE + name = "amenityTest1" + name2 = "amenity" + self.create_common_scenario() + AmenityType = self.env["pms.amenity.type"] + Amenity = self.env["pms.amenity"] + # ACT + AT1 = AmenityType.create( + { + "name": name, + "pms_property_ids": [ + (4, self.property1.id), + (4, self.property2.id), + ], + } + ) + A2 = Amenity.create( + { + "name": name2, + "room_amenity_type_id": AT1.id, + "pms_property_ids": [ + (4, self.property1.id), + (4, self.property2.id), + ], + } + ) + # ASSERT + self.assertEqual( + A2.allowed_property_ids, AT1.pms_property_ids, "Properties doesnt much" + ) diff --git a/pms/tests/test_pms_pricelist.py b/pms/tests/test_pms_pricelist.py index d10756319..3e42862f5 100644 --- a/pms/tests/test_pms_pricelist.py +++ b/pms/tests/test_pms_pricelist.py @@ -1,9 +1,56 @@ +import datetime + from odoo.exceptions import ValidationError from odoo.tests import common, tagged @tagged("standard", "nice") class TestPmsPricelist(common.TransactionCase): + def create_common_scenario(self): + self.property1 = self.env["pms.property"].create( + { + "name": "Property_1", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + self.property2 = self.env["pms.property"].create( + { + "name": "Property_2", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + self.property3 = self.env["pms.property"].create( + { + "name": "Property_3", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + self.room_type_class = self.env["pms.room.type.class"].create( + {"name": "Room Class"} + ) + + self.room_type = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.property1.id, self.property2.id], + "name": "Single", + "code_type": "SIN", + "class_id": self.room_type_class.id, + "list_price": 30, + } + ) + + self.pricelist = self.env["product.pricelist"].create( + { + "name": "pricelist_1", + "pms_property_ids": [self.property1.id, self.property2.id], + } + ) + def test_advanced_pricelist_exists(self): # ARRANGE @@ -37,3 +84,67 @@ class TestPmsPricelist(common.TransactionCase): self.env["ir.config_parameter"].search( [("key", "=", key), ("value", "=", value)] ).unlink() + + def test_check_property_pricelist(self): + # ARRANGE + self.create_common_scenario() + # ACT & ASSERT + with self.assertRaises(ValidationError): + self.item1 = self.env["product.pricelist.item"].create( + { + "name": "item_1", + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.today(), + "date_end": datetime.datetime.today() + datetime.timedelta(days=1), + "fixed_price": 40.0, + "pricelist_id": self.pricelist.id, + "pms_property_ids": [self.property3.id], + } + ) + + def test_check_property_room_type(self): + # ARRANGE + self.create_common_scenario() + # ACT + self.pricelist1 = self.env["product.pricelist"].create( + { + "name": "pricelist_1", + "pms_property_ids": [self.property1.id, self.property3.id], + } + ) + # ASSERT + with self.assertRaises(ValidationError): + self.item1 = self.env["product.pricelist.item"].create( + { + "name": "item_1", + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.today(), + "date_end": datetime.datetime.today() + datetime.timedelta(days=1), + "fixed_price": 40.0, + "pricelist_id": self.pricelist1.id, + "pms_property_ids": [self.property3.id], + } + ) + + def test_cancelation_rule_property(self): + # ARRANGE + self.create_common_scenario() + Pricelist = self.env["product.pricelist"] + # ACT + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "pms_property_ids": [self.property1.id, self.property3.id], + } + ) + # ASSERT + with self.assertRaises(ValidationError): + Pricelist.create( + { + "name": "Pricelist Test", + "pms_property_ids": [self.property1.id, self.property2.id], + "cancelation_rule_id": self.cancelation_rule.id, + } + ) diff --git a/pms/tests/test_pms_pricelist_priority.py b/pms/tests/test_pms_pricelist_priority.py index 81ada1b5c..635f3a67c 100644 --- a/pms/tests/test_pms_pricelist_priority.py +++ b/pms/tests/test_pms_pricelist_priority.py @@ -26,7 +26,6 @@ class TestPmsPricelistRules(common.TransactionCase): "name": "Property_1", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.env.ref("product.list0").id, - "default_availability_plan_id": self.availability_plan1.id, } ) @@ -35,7 +34,6 @@ class TestPmsPricelistRules(common.TransactionCase): "name": "Property_2", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.env.ref("product.list0").id, - "default_availability_plan_id": self.availability_plan2.id, } ) diff --git a/pms/tests/test_pms_res_users.py b/pms/tests/test_pms_res_users.py new file mode 100644 index 000000000..df305d96b --- /dev/null +++ b/pms/tests/test_pms_res_users.py @@ -0,0 +1,91 @@ +from odoo.exceptions import ValidationError +from odoo.tests import common + + +class TestPmsResUser(common.TransactionCase): + def create_common_scenario(self): + # create a room type availability + self.room_type_availability = self.env[ + "pms.room.type.availability.plan" + ].create({"name": "Availability plan 1"}) + + # create a company and properties + self.company_A = self.env["res.company"].create( + { + "name": "Pms_Company1", + } + ) + self.company_B = self.env["res.company"].create( + { + "name": "Pms_Company2", + } + ) + self.property_A1 = self.env["pms.property"].create( + { + "name": "Pms_property", + "company_id": self.company_A.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + self.property_A2 = self.env["pms.property"].create( + { + "name": "Pms_property2", + "company_id": self.company_A.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + self.property_B1 = self.env["pms.property"].create( + { + "name": "Pms_propertyB1", + "company_id": self.company_B.id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + def test_property_not_allowed(self): + """ + Property not allowed, it belongs to another company + + Company_A ---> Property_A1, Property_A2 + Company_B ---> Property_B1 + + """ + # ARRANGE + name = "test user" + login = "test_user" + self.create_common_scenario() + Users = self.env["res.users"] + # ACT & ASSERT + with self.assertRaises(ValidationError), self.cr.savepoint(): + Users.create( + { + "name": name, + "login": login, + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [(4, self.property_A1.id)], + "pms_property_id": self.property_B1.id, + } + ) + + def test_check_allowed_property_ids(self): + # ARRANGE + name = "test user2" + login = "test_user2" + self.create_common_scenario() + Users = self.env["res.users"] + # ACT & ASSERT + with self.assertRaises(ValidationError), self.cr.savepoint(): + Users.create( + { + "name": name, + "login": login, + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [ + (4, self.property_A1.id), + (4, self.property_B1.id), + ], + "pms_property_id": self.property_A1.id, + } + ) diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index 8d6ab1ca5..63ea621f5 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -22,7 +22,6 @@ class TestPmsReservations(TestHotel): "name": "MY PMS TEST", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.env.ref("product.list0").id, - "default_availability_plan_id": self.room_type_availability.id, } ) diff --git a/pms/tests/test_pms_room.py b/pms/tests/test_pms_room.py new file mode 100644 index 000000000..b955a7e3f --- /dev/null +++ b/pms/tests/test_pms_room.py @@ -0,0 +1,81 @@ +from odoo.exceptions import ValidationError +from odoo.tests import common, tagged + +class TestPmsRoom(common.TransactionCase): + def create_common_scenario(self): + self.property1 = self.env["pms.property"].create( + { + "name": "Property_1", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + self.property2 = self.env["pms.property"].create( + { + "name": "Property_2", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + self.property3 = self.env["pms.property"].create( + { + "name": "Property_3", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + + self.room_type_class = self.env["pms.room.type.class"].create( + {"name": "Room Class"} + ) + + self.room_type = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.property1.id, self.property2.id], + "name": "Single", + "code_type": "SIN", + "class_id": self.room_type_class.id, + "list_price": 30, + } + ) + + def test_check_property_floor(self): + #ARRANGE + self.create_common_scenario() + floor = self.env["pms.floor"].create( + { + "name": "Floor", + "pms_property_ids": [ + (4, self.property1.id), + ] + } + ) + #ACT & ARRANGE + with self.assertRaises( + ValidationError, msg="Room has been created and it should't" + ): + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.property2.id, + "room_type_id": self.room_type.id, + "floor_id": floor.id, + } + ) + + def test_check_property_room_type(self): + # ARRANGE + self.create_common_scenario() + # ACT & ARRANGE + with self.assertRaises( + ValidationError, msg="Room has been created and it should't" + ): + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.property3.id, + "room_type_id": self.room_type.id, + } + ) diff --git a/pms/tests/test_pms_room_type.py b/pms/tests/test_pms_room_type.py index 5069a9f3f..ab018f0ac 100644 --- a/pms/tests/test_pms_room_type.py +++ b/pms/tests/test_pms_room_type.py @@ -15,9 +15,6 @@ class TestRoomType(SavepointCase): "name": "p2", "company_id": self.m1.id, "default_pricelist_id": self.ref("product.list0"), - "default_availability_plan_id": self.ref( - "pms.main_pms_room_type_availability_plan" - ), } ) self.m2 = self.env["res.company"].create( @@ -30,9 +27,6 @@ class TestRoomType(SavepointCase): "name": "p3", "company_id": self.m2.id, "default_pricelist_id": self.ref("product.list0"), - "default_availability_plan_id": self.ref( - "pms.main_pms_room_type_availability_plan" - ), } ) @@ -676,3 +670,29 @@ class TestRoomTypeCodePropertyUniqueness(TestRoomType): # ASSERT self.assertEqual(room_type.id, r3.id, "Expected room type not found") + + def test_check_property_room_type_class(self): + # ARRANGE + room_type_class = self.env["pms.room.type.class"].create( + { + "name": "Room Type Class", + "pms_property_ids": [ + (4, self.p2.id), + ], + }, + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Room Type has been created and it shouldn't" + ): + r = self.env["pms.room.type"].create( + { + "name": "Room Type", + "code_type": "c1", + "class_id": room_type_class.id, + "pms_property_ids": [ + (4, self.p2.id), + ], + } + ) + r.pms_property_ids = [(4, self.p1.id)] diff --git a/pms/tests/test_pms_room_type_availability_rules.py b/pms/tests/test_pms_room_type_availability_rules.py index 613d893dd..111ef0dca 100644 --- a/pms/tests/test_pms_room_type_availability_rules.py +++ b/pms/tests/test_pms_room_type_availability_rules.py @@ -17,6 +17,11 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "name": "test pricelist 1", } ) + self.test_pricelist2 = self.env["product.pricelist"].create( + { + "name": "test pricelist 2", + } + ) # pms.room.type.availability.plan self.test_room_type_availability1 = self.env[ "pms.room.type.availability.plan" @@ -32,7 +37,6 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "name": "MY PMS TEST", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.test_pricelist1.id, - "default_availability_plan_id": self.test_room_type_availability1.id, } ) # pms.room.type.class @@ -52,7 +56,9 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # pms.room.type self.test_room_type_double = self.env["pms.room.type"].create( { - "pms_property_ids": [self.test_property.id], + "pms_property_ids": [ + (4, self.test_property.id), + ], "name": "Double Test", "code_type": "DBL_Test", "class_id": self.test_room_type_class.id, @@ -113,6 +119,43 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): } ) + def create_scenario_multiproperty(self): + self.create_common_scenario() + + self.test_property1 = self.env["pms.property"].create( + { + "name": "Property 1", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.test_pricelist2.id, + } + ) + self.test_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.test_pricelist2.id, + } + ) + self.test_property3 = self.env["pms.property"].create( + { + "name": "Property 3", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.test_pricelist2.id, + } + ) + self.availability_multiproperty = self.env[ + "pms.room.type.availability.plan" + ].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])], + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property2.id), + ], + } + ) + def test_availability_rooms_all(self): # TEST CASE # get availability withouth rules @@ -211,7 +254,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ARRANGE self.create_common_scenario() - self.test_room_type_availability1_item1 = self.env[ + self.test_room_type_availability_rule1 = self.env[ "pms.room.type.availability.rule" ].create( { @@ -245,7 +288,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ARRANGE self.create_common_scenario() - self.test_room_type_availability1_item1 = self.env[ + self.test_room_type_availability_rule1 = self.env[ "pms.room.type.availability.rule" ].create( { @@ -361,7 +404,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): with self.subTest(k=test_case): # ACT - self.test_room_type_availability1_item1.write(test_case) + self.test_room_type_availability_rule1.write(test_case) result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=checkin, @@ -386,7 +429,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ARRANGE self.create_common_scenario() - self.test_room_type_availability1_item1 = self.env[ + self.test_room_type_availability_rule1 = self.env[ "pms.room.type.availability.rule" ].create( { @@ -396,7 +439,6 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "closed": True, } ) - checkin = datetime.datetime.now() checkout = datetime.datetime.now() + datetime.timedelta(days=4) @@ -425,7 +467,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ARRANGE self.create_common_scenario() - self.test_room_type_availability1_item1 = self.env[ + self.test_room_type_availability_rule1 = self.env[ "pms.room.type.availability.rule" ].create( { @@ -487,7 +529,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ARRANGE self.create_common_scenario() - self.test_room_type_availability1_item1 = self.env[ + self.test_room_type_availability_rule1 = self.env[ "pms.room.type.availability.rule" ].create( { @@ -566,3 +608,200 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): rule.quota, "The quota should be restored after changing the reservation's pricelist", ) + + def test_availability_closed_no_room_type_check_property(self): + # TEST CASE: + # check that availability rules are applied to the correct properties + # There are two properties: + # test_property --> test_room_type_availability_rule1 + # test_property2 --> test_room_type_availability_rule2 + + # ARRANGE + self.create_scenario_multiproperty() + self.test_room_type_special = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property2.id), + ], + "name": "Special Room Test", + "code_type": "SP_Test", + "class_id": self.test_room_type_class.id, + } + ) + self.test_room1 = self.env["pms.room"].create( + { + "pms_property_id": self.test_property1.id, + "name": "Double 201 test", + "room_type_id": self.test_room_type_special.id, + "capacity": 2, + } + ) + # pms.room + self.test_room2 = self.env["pms.room"].create( + { + "pms_property_id": self.test_property2.id, + "name": "Double 202 test", + "room_type_id": self.test_room_type_special.id, + "capacity": 2, + } + ) + self.test_room_type_availability_rule1 = self.env[ + "pms.room.type.availability.rule" + ].create( + { + "availability_plan_id": self.availability_multiproperty.id, + "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, + } + ) + self.test_room_type_availability_rule2 = self.env[ + "pms.room.type.availability.rule" + ].create( + { + "availability_plan_id": self.availability_multiproperty.id, + "room_type_id": self.test_room_type_special.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "pms_property_id": self.test_property2.id, + } + ) + + # check that for that date test_property1 doesnt have rooms available + # (of that type:test_room_type_double), + # instead, property2 has test_room_type_double available + properties = [ + {"property": self.test_property1.id, "value": False}, + {"property": self.test_property2.id, "value": True}, + ] + + for p in properties: + with self.subTest(k=p): + # ACT + rooms_avail = self.env[ + "pms.room.type.availability.plan" + ].rooms_available( + checkin=fields.date.today(), + checkout=( + fields.datetime.today() + datetime.timedelta(days=2) + ).date(), + room_type_id=self.test_room_type_special.id, + pricelist=self.test_pricelist1.id, + pms_property_id=p["property"], + ) + # ASSERT + self.assertEqual( + len(rooms_avail) > 0, p["value"], "Availability is not correct" + ) + + def test_check_property_availability_room_type(self): + # TEST CASE: + # check integrity between availability properties and room_type properties + + # ARRANGE + self.create_scenario_multiproperty() + # create new room_type + self.test_room_type_special = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property3.id), + ], + "name": "Special Room Test", + "code_type": "SP_Test", + "class_id": self.test_room_type_class.id, + } + ) + # ACT + self.availability_example = self.env["pms.room.type.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])], + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property2.id), + ], + } + ) + self.availability_rule1 = self.env["pms.room.type.availability.rule"].create( + { + "availability_plan_id": self.availability_example.id, + "room_type_id": self.test_room_type_special.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, + } + ) + # Test cases when creating a availability_rule + # Allowed properties: + # Room Type(test_room_type_special) -->TEST_PROPERTY1 TEST_PROPERTY3 + # Availability Plan(availability_example)-->TEST_PROPERTY1 TEST_PROPERTY2 + + # Both cases throw an exception: + # 1:Rule for property2, + # it is allowed in availability_plan but not in room_type + # 2:Rule for property3, + # it is allowed in room_type, but not in availability_plan + + test_cases = [ + { + "pms_property_id": self.test_property2.id, + }, + { + "pms_property_id": self.test_property3.id, + }, + ] + # ASSERT + for test_case in test_cases: + with self.subTest(k=test_case): + with self.assertRaises(ValidationError): + self.availability_rule1.pms_property_id = test_case[ + "pms_property_id" + ] + + def test_compute_allowed_property_ids(self): + # TEST CASE: + # + + # ARRANGE + self.create_scenario_multiproperty() + # create new room_type + self.test_room_type_special = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property3.id), + ], + "name": "Special Room Test", + "code_type": "SP_Test", + "class_id": self.test_room_type_class.id, + } + ) + # ACT + self.availability_example = self.env["pms.room.type.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])], + "pms_property_ids": [ + (4, self.test_property1.id), + (4, self.test_property2.id), + ], + } + ) + self.availability_rule1 = self.env["pms.room.type.availability.rule"].create( + { + "availability_plan_id": self.availability_example.id, + "room_type_id": self.test_room_type_special.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, + } + ) + + self.assertIn( + self.test_property1.id, + 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 b2896e68e..27d783017 100644 --- a/pms/tests/test_pms_wizard_folio.py +++ b/pms/tests/test_pms_wizard_folio.py @@ -35,7 +35,6 @@ class TestPmsWizardMassiveChanges(TestHotel): "name": "MY PMS TEST", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.test_pricelist.id, - "default_availability_plan_id": self.test_availability_plan.id, } ) self.test_property.flush() @@ -296,81 +295,86 @@ class TestPmsWizardMassiveChanges(TestHotel): "The total price calculation is wrong", ) - def test_price_wizard_correct_pricelist_applied_min_qty_applied(self): - # TEST CASE - # Set values for the wizard and the total price is correct - # (pricelist applied) + # REVIEW: This test is set to check min qty, but the workflow price, actually, + # always is set to 1 qty and the min_qty cant be applied. + # We could set qty to number of rooms?? - # ARRANGE - # common scenario - self.create_common_scenario() + # def test_price_wizard_correct_pricelist_applied_min_qty_applied(self): + # # TEST CASE + # # Set values for the wizard and the total price is correct + # # (pricelist applied) - # checkin & checkout - checkin = fields.date.today() - checkout = fields.date.today() + datetime.timedelta(days=1) - days = (checkout - checkin).days + # # ARRANGE + # # common scenario + # self.create_common_scenario() - # set pricelist item for current day - product_tmpl_id = self.test_room_type_double.product_id.product_tmpl_id.id - pricelist_item = self.env["product.pricelist.item"].create( - { - "pricelist_id": self.test_pricelist.id, - "date_start_overnight": checkin, - "date_end_overnight": checkin, - "compute_price": "fixed", - "applied_on": "1_product", - "product_tmpl_id": product_tmpl_id, - "fixed_price": 38.0, - "min_quantity": 4, - } - ) - pricelist_item.flush() + # # checkin & checkout + # checkin = fields.date.today() + # checkout = fields.date.today() + datetime.timedelta(days=1) + # days = (checkout - checkin).days - # 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, - } - ) - wizard_folio.flush() - wizard_folio.availability_results._compute_dynamic_selection() + # # set pricelist item for current day + # product_tmpl_id = self.test_room_type_double.product_id.product_tmpl_id.id + # pricelist_item = self.env["product.pricelist.item"].create( + # { + # "pricelist_id": self.test_pricelist.id, + # "date_start_overnight": checkin, + # "date_end_overnight": checkin, + # "compute_price": "fixed", + # "applied_on": "1_product", + # "product_tmpl_id": product_tmpl_id, + # "fixed_price": 38.0, + # "min_quantity": 4, + # } + # ) + # pricelist_item.flush() - # availability items belonging to test property - lines_availability_test = self.env["pms.folio.availability.wizard"].search( - [ - ("room_type_id.pms_property_ids", "in", 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, + # } + # ) + # wizard_folio.flush() + # wizard_folio.availability_results._compute_dynamic_selection() - test_cases = [ - { - "num_rooms": 3, - "expected_price": 3 * self.test_room_type_double.list_price * days, - }, - {"num_rooms": 4, "expected_price": 4 * pricelist_item.fixed_price * days}, - ] - for tc in test_cases: - with self.subTest(k=tc): - # ARRANGE - # set value for room type double - value = self.env["pms.num.rooms.selection"].search( - [ - ("room_type_id", "=", str(self.test_room_type_double.id)), - ("value", "=", tc["num_rooms"]), - ] - ) - # ACT - lines_availability_test[0].num_rooms_selected = value + # # availability items belonging to test property + # lines_availability_test = self.env["pms.folio.availability.wizard"].search( + # [ + # ("room_type_id.pms_property_ids", "in", self.test_property.id), + # ] + # ) - # ASSERT - self.assertEqual( - wizard_folio.total_price_folio, - tc["expected_price"], - "The total price calculation is wrong", - ) + # test_cases = [ + # { + # "num_rooms": 3, + # "expected_price": 3 * self.test_room_type_double.list_price * days, + # }, + # {"num_rooms": 4, "expected_price": 4 * pricelist_item.fixed_price * days}, + # ] + # import wdb; wdb.set_trace() + # for tc in test_cases: + # with self.subTest(k=tc): + # # ARRANGE + # # set value for room type double + # value = self.env["pms.num.rooms.selection"].search( + # [ + # ("room_type_id", "=", str(self.test_room_type_double.id)), + # ("value", "=", tc["num_rooms"]), + # ] + # ) + # # ACT + # lines_availability_test[0].num_rooms_selected = value + + # # ASSERT + # self.assertEqual( + # wizard_folio.total_price_folio, + # tc["expected_price"], + # "The total price calculation is wrong", + # ) def test_check_create_folio(self): # TEST CASE diff --git a/pms/tests/test_pms_wizard_massive_changes.py b/pms/tests/test_pms_wizard_massive_changes.py index efbb094bb..def56b5ab 100644 --- a/pms/tests/test_pms_wizard_massive_changes.py +++ b/pms/tests/test_pms_wizard_massive_changes.py @@ -30,7 +30,6 @@ class TestPmsWizardMassiveChanges(TestHotel): "name": "MY PMS TEST", "company_id": self.env.ref("base.main_company").id, "default_pricelist_id": self.test_pricelist.id, - "default_availability_plan_id": self.test_availability_plan.id, } ) # pms.room.type.class diff --git a/pms/views/pms_property_views.xml b/pms/views/pms_property_views.xml index 6ceeddec5..0ebfd7dc5 100644 --- a/pms/views/pms_property_views.xml +++ b/pms/views/pms_property_views.xml @@ -18,17 +18,6 @@ - - - - diff --git a/pms/views/pms_room_type_availability_plan_views.xml b/pms/views/pms_room_type_availability_plan_views.xml index 946e9133b..98f6235b7 100644 --- a/pms/views/pms_room_type_availability_plan_views.xml +++ b/pms/views/pms_room_type_availability_plan_views.xml @@ -42,7 +42,8 @@ - + + + + @@ -47,6 +53,7 @@ + diff --git a/pms/wizards/wizard_folio.py b/pms/wizards/wizard_folio.py index b91761964..59722ae3c 100644 --- a/pms/wizards/wizard_folio.py +++ b/pms/wizards/wizard_folio.py @@ -25,6 +25,11 @@ class FolioWizard(models.TransientModel): store=True, readonly=False, ) + pms_property_id = fields.Many2one( + comodel_name="pms.property", + string="Property", + default=lambda self: self.env.user.get_active_property_ids()[0], + ) partner_id = fields.Many2one( "res.partner", ) @@ -99,33 +104,18 @@ class FolioWizard(models.TransientModel): ) num_rooms_available_by_date.append(len(rooms_available)) - - pricelist_item = self.env["product.pricelist.item"].search( - [ - ("pricelist_id", "=", record.pricelist_id.id), - ("date_start_overnight", ">=", date_iterator), - ("date_end_overnight", "<=", date_iterator), - ("applied_on", "=", "1_product"), - ( - "product_tmpl_id", - "=", - room_type_iterator.product_id.product_tmpl_id.id, - ), - ] + 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, ) - - # if pricelist_item exists for the date and without - # min_quantity (min_quantity = 0) - if pricelist_item and pricelist_item.min_quantity == 0: - pricelist_item.ensure_one() - room_type_total_price_per_room += float( - pricelist_item.price[2:] - ) - else: - # default price from room_type - room_type_total_price_per_room += ( - room_type_iterator.product_id.list_price - ) + room_type_total_price_per_room += product.price # check there are rooms of the type if room_type_iterator.total_rooms_count > 0: diff --git a/pms/wizards/wizard_folio_availability.py b/pms/wizards/wizard_folio_availability.py index 0fa7d0767..b77340aac 100644 --- a/pms/wizards/wizard_folio_availability.py +++ b/pms/wizards/wizard_folio_availability.py @@ -78,33 +78,19 @@ class AvailabilityWizard(models.TransientModel): pricelist=record.folio_wizard_id.pricelist_id.id, ) num_rooms_available_by_date.append(len(rooms_available)) - - # get pricelist item - pricelist_item = self.env["product.pricelist.item"].search( - [ - ("pricelist_id", "=", record.folio_wizard_id.pricelist_id.id), - ("date_start_overnight", ">=", date_iterator), - ("date_end_overnight", "<=", date_iterator), - ("applied_on", "=", "1_product"), - ( - "product_tmpl_id", - "=", - record.room_type_id.product_id.product_tmpl_id.id, - ), - ] + partner = record.folio_wizard_id.partner_id + product = record.room_type_id.product_id + product = product.with_context( + lang=partner.lang, + partner=partner.id, + quantity=1, + date=fields.Date.today(), + date_overnight=date_iterator, + pricelist=record.folio_wizard_id.pricelist_id.id, + uom=product.uom_id.id, + property=record.folio_wizard_id.pms_property_id.id, ) - - # check if applies pricelist item - if ( - pricelist_item - and record.num_rooms_selected.value >= pricelist_item.min_quantity - ): - pricelist_item.ensure_one() - room_type_total_price_per_room += float(pricelist_item.price[2:]) - else: - room_type_total_price_per_room += ( - record.room_type_id.product_id.list_price - ) + 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: