diff --git a/pms/__manifest__.py b/pms/__manifest__.py index ebc5cdc43..6392593f1 100644 --- a/pms/__manifest__.py +++ b/pms/__manifest__.py @@ -52,7 +52,7 @@ "views/account_move_views.xml", "views/res_users_views.xml", "views/pms_room_type_class_views.xml", - "views/pms_room_type_availability_views.xml", + "views/pms_room_type_availability_plan_views.xml", "views/pms_room_type_availability_rule_views.xml", "views/pms_service_views.xml", "views/pms_service_line_views.xml", @@ -65,6 +65,7 @@ "views/webclient_templates.xml", "views/ir_sequence_views.xml", "wizards/wizard_reservation.xml", + "wizards/wizard_massive_changes.xml", ], "demo": [ "demo/pms_master_data.xml", diff --git a/pms/data/pms_data.xml b/pms/data/pms_data.xml index 2f5ce7824..16f222d59 100644 --- a/pms/data/pms_data.xml +++ b/pms/data/pms_data.xml @@ -2,7 +2,10 @@ - + Availability Plan @@ -10,8 +13,8 @@ Rua Street Demo, s/n Commitsun city diff --git a/pms/demo/pms_master_data.xml b/pms/demo/pms_master_data.xml index 3c4199e69..0d118bf2f 100644 --- a/pms/demo/pms_master_data.xml +++ b/pms/demo/pms_master_data.xml @@ -298,7 +298,10 @@ - + Availability Plan Demo @@ -306,8 +309,8 @@ diff --git a/pms/models/__init__.py b/pms/models/__init__.py index c24fbd560..7904ab766 100644 --- a/pms/models/__init__.py +++ b/pms/models/__init__.py @@ -23,7 +23,7 @@ from . import account_move from . import product_template from . import res_company from . import account_payment -from . import pms_room_type_availability +from . import pms_room_type_availability_plan from . import pms_room_type_availability_rule from . import pms_reservation_line from . import pms_checkin_partner diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py index 30b5ed88f..2fd0e3e76 100644 --- a/pms/models/pms_property.py +++ b/pms/models/pms_property.py @@ -41,8 +41,8 @@ class PmsProperty(models.Model): required=True, help="The default pricelist used in this property.", ) - default_availability_id = fields.Many2one( - "pms.room.type.availability", + default_availability_plan_id = fields.Many2one( + "pms.room.type.availability.plan", "Availability Plan", required=True, help="The default availability plan used in this property.", diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 25d5686d5..4f539aa35 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -522,7 +522,7 @@ class PmsReservation(models.Model): ) return rooms_available = self.env[ - "pms.room.type.availability" + "pms.room.type.availability.plan" ].rooms_available( checkin=reservation.checkin, checkout=reservation.checkout, @@ -1103,7 +1103,7 @@ class PmsReservation(models.Model): } def open_reservation_wizard(self): - rooms_available = self.env["pms.room.type.availability"].rooms_available( + rooms_available = self.env["pms.room.type.availability.plan"].rooms_available( checkin=self.checkin, checkout=self.checkout, current_lines=self.reservation_line_ids.ids, @@ -1186,7 +1186,7 @@ class PmsReservation(models.Model): def _autoassign(self): self.ensure_one() room_chosen = False - rooms_available = self.env["pms.room.type.availability"].rooms_available( + rooms_available = self.env["pms.room.type.availability.plan"].rooms_available( checkin=self.checkin, checkout=self.checkout, room_type_id=self.room_type_id.id or False, diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index 522d446ca..c10fa2da5 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -109,7 +109,7 @@ class PmsReservationLine(models.Model): free_room_select = True if reservation.preferred_room_id else False # we get the rooms available for the entire stay rooms_available = self.env[ - "pms.room.type.availability" + "pms.room.type.availability.plan" ].rooms_available( checkin=line.reservation_id.checkin, checkout=line.reservation_id.checkout, @@ -141,7 +141,9 @@ class PmsReservationLine(models.Model): else: line.room_id = rooms_available[0] # check that the reservation cannot be allocated even by dividing it - elif not self.env["pms.room.type.availability"].splitted_availability( + elif not self.env[ + "pms.room.type.availability.plan" + ].splitted_availability( checkin=line.reservation_id.checkin, checkout=line.reservation_id.checkout, room_type_id=line.reservation_id.room_type_id.id, @@ -235,7 +237,9 @@ class PmsReservationLine(models.Model): def _compute_impact_quota(self): for line in self: reservation = line.reservation_id - line.impacts_quota = self.env["pms.room.type.availability"].update_quota( + line.impacts_quota = self.env[ + "pms.room.type.availability.plan" + ].update_quota( pricelist_id=reservation.pricelist_id, room_type_id=reservation.room_type_id, date=line.date, diff --git a/pms/models/pms_room_type_availability.py b/pms/models/pms_room_type_availability_plan.py similarity index 86% rename from pms/models/pms_room_type_availability.py rename to pms/models/pms_room_type_availability_plan.py index 2b47ac596..e8a6e35a4 100644 --- a/pms/models/pms_room_type_availability.py +++ b/pms/models/pms_room_type_availability_plan.py @@ -9,7 +9,7 @@ class PmsRoomTypeAvailability(models.Model): """The room type availability is used as a daily availability plan for room types and therefore is related only with one property.""" - _name = "pms.room.type.availability" + _name = "pms.room.type.availability.plan" _description = "Reservation availability plan" # Default methods @@ -27,17 +27,15 @@ class PmsRoomTypeAvailability(models.Model): pms_pricelist_ids = fields.One2many( comodel_name="product.pricelist", - inverse_name="availability_id", + inverse_name="availability_plan_id", string="Pricelists", required=False, - ondelete="restrict", ) - item_ids = fields.One2many( + rule_ids = fields.One2many( comodel_name="pms.room.type.availability.rule", - inverse_name="availability_id", - string="Rule Items", - copy=True, + inverse_name="availability_plan_id", + string="Availability Rules", ) active = fields.Boolean( @@ -104,7 +102,9 @@ class PmsRoomTypeAvailability(models.Model): free_rooms = self.env["pms.room"].search(domain_rooms) if pricelist: - domain_rules.append(("availability_id.pms_pricelist_ids", "=", pricelist)) + domain_rules.append( + ("availability_plan_id.pms_pricelist_ids", "=", pricelist) + ) rule_items = self.env["pms.room.type.availability.rule"].search( domain_rules ) @@ -149,7 +149,7 @@ class PmsRoomTypeAvailability(models.Model): if pricelist_id and room_type_id and date: rule = self.env["pms.room.type.availability.rule"].search( [ - ("availability_id.pms_pricelist_ids", "=", pricelist_id.id), + ("availability_plan_id.pms_pricelist_ids", "=", pricelist_id.id), ("room_type_id", "=", room_type_id.id), ("date", "=", date), ] @@ -191,3 +191,19 @@ class PmsRoomTypeAvailability(models.Model): old_rule.quota += 1 return False + + # Action methods + def open_massive_changes_wizard(self): + + if self.ensure_one(): + return { + "view_type": "form", + "view_mode": "form", + "name": "Massive changes on Availability Plan: " + self.name, + "res_model": "pms.massive.changes.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": { + "availability_plan_id": self.id, + }, + } diff --git a/pms/models/pms_room_type_availability_rule.py b/pms/models/pms_room_type_availability_rule.py index d2a59f842..e276f9d62 100644 --- a/pms/models/pms_room_type_availability_rule.py +++ b/pms/models/pms_room_type_availability_rule.py @@ -10,8 +10,8 @@ class PmsRoomTypeAvailabilityRule(models.Model): # Field Declarations - availability_id = fields.Many2one( - comodel_name="pms.room.type.availability", + availability_plan_id = fields.Many2one( + comodel_name="pms.room.type.availability.plan", string="Availability Plan", ondelete="cascade", index=True, @@ -71,7 +71,7 @@ class PmsRoomTypeAvailabilityRule(models.Model): _sql_constraints = [ ( "room_type_registry_unique", - "unique(availability_id, room_type_id, date)", + "unique(availability_plan_id, room_type_id, date)", "Only can exists one availability rule in the same \ day for the same room type!", ) diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py index fa9772844..db09d03b1 100644 --- a/pms/models/product_pricelist.py +++ b/pms/models/product_pricelist.py @@ -22,8 +22,8 @@ class ProductPricelist(models.Model): [("daily", "Daily Plan")], string="Pricelist Type", default="daily" ) - availability_id = fields.Many2one( - comodel_name="pms.room.type.availability", + availability_plan_id = fields.Many2one( + comodel_name="pms.room.type.availability.plan", string="Availability Plan", ondelete="restrict", ) @@ -79,3 +79,19 @@ class ProductPricelist(models.Model): ) ) return items + + # Action methods + def open_massive_changes_wizard(self): + + if self.ensure_one(): + return { + "view_type": "form", + "view_mode": "form", + "name": "Massive changes on Pricelist: " + self.name, + "res_model": "pms.massive.changes.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": { + "pricelist_id": self.id, + }, + } diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv index 2305368a7..58e684ecc 100644 --- a/pms/security/ir.model.access.csv +++ b/pms/security/ir.model.access.csv @@ -22,7 +22,7 @@ user_access_account_partial_reconcile,user_access_account_partial_reconcile,acco user_access_pms_cancelation_rule,user_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_user,1,0,0,0 user_access_account_full_reconcile,user_access_account_full_reconcile,account.model_account_full_reconcile,pms.group_pms_user,1,1,1,1 user_access_property,user_access_property,model_pms_property,pms.group_pms_user,1,0,0,0 -user_access_availability,user_access_availability,model_pms_room_type_availability,pms.group_pms_user,1,0,0,0 +user_access_availability,user_access_availability,model_pms_room_type_availability_plan,pms.group_pms_user,1,0,0,0 user_access_pms_sale_channel,user_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_user,1,0,0,0 manager_access_pms_floor,manager_access_pms_floor,model_pms_floor,pms.group_pms_manager,1,1,1,1 manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1 @@ -45,6 +45,7 @@ manager_access_pms_board_service_room_type_line,manager_access_pms_board_service manager_access_pms_board_service_line,manager_access_pms_board_service_line,model_pms_board_service_line,pms.group_pms_manager,1,1,1,1 manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1 manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1 -manager_access_availability,manager_access_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,1 +manager_access_availability,manager_access_availability,model_pms_room_type_availability_plan,pms.group_pms_manager,1,1,1,1 manager_access_pms_sale_channel,manager_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_manager,1,1,1,1 user_access_pms_reservation_wizard,user_access_pms_reservation_wizard,model_pms_reservation_wizard,pms.group_pms_user,1,1,1,1 +user_access_pms_massive_changes_wizard,user_access_pms_massive_changes_wizard,model_pms_massive_changes_wizard,pms.group_pms_user,1,1,1,1 diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py index be9caec85..4b81ec9a1 100644 --- a/pms/tests/__init__.py +++ b/pms/tests/__init__.py @@ -26,4 +26,5 @@ from . import test_pms_checkin_partner from . import test_pms_sale_channel from . import test_pms_folio from . import test_pms_room_type_availability_rules -from . import test_pms_room_type \ No newline at end of file +from . import test_pms_room_type +from . import test_pms_wizard_massive_changes diff --git a/pms/tests/test_pms_pricelist_priority.py b/pms/tests/test_pms_pricelist_priority.py index 58daf1380..fa0ef6236 100644 --- a/pms/tests/test_pms_pricelist_priority.py +++ b/pms/tests/test_pms_pricelist_priority.py @@ -15,11 +15,11 @@ class TestPmsPricelistRules(common.TransactionCase): {"name": "Category1"} ) - self.availability = self.env["pms.room.type.availability"].create( + self.availability_plan1 = self.env["pms.room.type.availability.plan"].create( {"name": "Availability 1"} ) - self.availability2 = self.env["pms.room.type.availability"].create( + self.availability_plan2 = self.env["pms.room.type.availability.plan"].create( {"name": "Availability"} ) self.property1 = self.env["pms.property"].create( @@ -27,7 +27,7 @@ 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_id": self.availability.id, + "default_availability_plan_id": self.availability_plan1.id, } ) @@ -36,7 +36,7 @@ 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_id": self.availability2.id, + "default_availability_plan_id": self.availability_plan2.id, } ) diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index e49887565..8d6ab1ca5 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -12,9 +12,9 @@ from .common import TestHotel class TestPmsReservations(TestHotel): def create_common_scenario(self): # create a room type availability - self.room_type_availability = self.env["pms.room.type.availability"].create( - {"name": "Availability plan for TEST"} - ) + self.room_type_availability = self.env[ + "pms.room.type.availability.plan" + ].create({"name": "Availability plan for TEST"}) # create a property self.property = self.env["pms.property"].create( @@ -22,7 +22,7 @@ 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_id": self.room_type_availability.id, + "default_availability_plan_id": self.room_type_availability.id, } ) diff --git a/pms/tests/test_pms_room_type.py b/pms/tests/test_pms_room_type.py index 06085090b..afaf581d7 100644 --- a/pms/tests/test_pms_room_type.py +++ b/pms/tests/test_pms_room_type.py @@ -15,8 +15,8 @@ class TestRoomType(SavepointCase): "name": "p2", "company_id": self.m1.id, "default_pricelist_id": self.ref("product.list0"), - "default_availability_id": self.ref( - "pms.main_pms_room_type_availability" + "default_availability_plan_id": self.ref( + "pms.main_pms_room_type_availability_plan" ), } ) @@ -30,8 +30,8 @@ class TestRoomType(SavepointCase): "name": "p3", "company_id": self.m2.id, "default_pricelist_id": self.ref("product.list0"), - "default_availability_id": self.ref( - "pms.main_pms_room_type_availability" + "default_availability_plan_id": self.ref( + "pms.main_pms_room_type_availability_plan" ), } ) diff --git a/pms/tests/test_pms_room_type_availability_rules.py b/pms/tests/test_pms_room_type_availability_rules.py index 1ee28b80b..613d893dd 100644 --- a/pms/tests/test_pms_room_type_availability_rules.py +++ b/pms/tests/test_pms_room_type_availability_rules.py @@ -17,9 +17,9 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "name": "test pricelist 1", } ) - # pms.room.type.availability + # pms.room.type.availability.plan self.test_room_type_availability1 = self.env[ - "pms.room.type.availability" + "pms.room.type.availability.plan" ].create( { "name": "Availability plan for TEST", @@ -32,7 +32,7 @@ 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_id": self.test_room_type_availability1.id, + "default_availability_plan_id": self.test_room_type_availability1.id, } ) # pms.room.type.class @@ -127,7 +127,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): ) # ACT - result = self.env["pms.room.type.availability"].rooms_available( + result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=checkin, checkout=checkout, ) @@ -160,7 +160,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): ) # ACT - result = self.env["pms.room.type.availability"].rooms_available( + result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=checkin, checkout=checkout, current_lines=test_reservation.reservation_line_ids.ids, @@ -188,7 +188,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): ) # ACT - result = self.env["pms.room.type.availability"].rooms_available( + result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=fields.date.today(), checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(), room_type_id=self.test_room_type_double.id, @@ -215,14 +215,14 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "pms.room.type.availability.rule" ].create( { - "availability_id": self.test_room_type_availability1.id, + "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=2)).date(), "closed": True, # <- (1/2) } ) # ACT - result = self.env["pms.room.type.availability"].rooms_available( + result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=fields.date.today(), checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(), # room_type_id=False, # <- (2/2) @@ -249,7 +249,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "pms.room.type.availability.rule" ].create( { - "availability_id": self.test_room_type_availability1.id, + "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(), } @@ -363,7 +363,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): # ACT self.test_room_type_availability1_item1.write(test_case) - result = self.env["pms.room.type.availability"].rooms_available( + result = self.env["pms.room.type.availability.plan"].rooms_available( checkin=checkin, checkout=checkout, room_type_id=self.test_room_type_double.id, @@ -390,7 +390,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "pms.room.type.availability.rule" ].create( { - "availability_id": self.test_room_type_availability1.id, + "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=2)).date(), "closed": True, @@ -429,7 +429,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "pms.room.type.availability.rule" ].create( { - "availability_id": self.test_room_type_availability1.id, + "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=2)).date(), "closed": True, @@ -491,7 +491,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): "pms.room.type.availability.rule" ].create( { - "availability_id": self.test_room_type_availability1.id, + "availability_plan_id": self.test_room_type_availability1.id, "room_type_id": self.test_room_type_double.id, "date": datetime.date.today(), "quota": 1, @@ -541,7 +541,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel): ) rule = self.env["pms.room.type.availability.rule"].create( { - "availability_id": self.test_room_type_availability1.id, + "availability_plan_id": self.test_room_type_availability1.id, "room_type_id": self.test_room_type_double.id, "date": datetime.date.today(), "quota": test_quota, diff --git a/pms/tests/test_pms_wizard_massive_changes.py b/pms/tests/test_pms_wizard_massive_changes.py new file mode 100644 index 000000000..80eb43f0d --- /dev/null +++ b/pms/tests/test_pms_wizard_massive_changes.py @@ -0,0 +1,414 @@ +import datetime + +import pytz +from freezegun import freeze_time + +from odoo import fields + +from .common import TestHotel + + +class TestPmsWizardMassiveChanges(TestHotel): + def create_common_scenario(self): + # product.pricelist + self.test_pricelist = self.env["product.pricelist"].create( + { + "name": "test pricelist 1", + } + ) + # pms.room.type.availability.plan + self.test_availability_plan = self.env[ + "pms.room.type.availability.plan" + ].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.test_pricelist.id])], + } + ) + # pms.property + self.test_property = self.env["pms.property"].create( + { + "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 + self.test_room_type_class = self.env["pms.room.type.class"].create( + {"name": "Room"} + ) + + # pms.room.type + self.test_room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.test_property.id], + "name": "Single Test", + "code_type": "SNG_Test", + "class_id": self.test_room_type_class.id, + } + ) + # pms.room.type + self.test_room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.test_property.id], + "name": "Double Test", + "code_type": "DBL_Test", + "class_id": self.test_room_type_class.id, + } + ) + + # MASSIVE CHANGE WIZARD TESTS ON AVAILABILITY RULES + + @freeze_time("1980-12-01") + def test_num_availability_rules_create(self): + + # TEST CASE + # rules should be created consistently for 1,2,3,4 days + + # ARRANGE + self.create_common_scenario() + + for days in [0, 1, 2, 3]: + with self.subTest(k=days): + num_exp_rules_to_create = days + 1 + + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_id": self.test_availability_plan.id, + "start_date": fields.date.today(), + "end_date": fields.date.today() + datetime.timedelta(days=days), + "room_type_id": self.test_room_type_double.id, + } + ).apply_massive_changes() + + self.assertEqual( + len(self.test_availability_plan.rule_ids), + num_exp_rules_to_create, + "the number of rules created by should contains all the " + "days between start and finish (both included)", + ) + + @freeze_time("1980-12-01") + def test_num_availability_rules_create_no_room_type(self): + # TEST CASE + # (days x room_types) rules should be created + + # ARRANGE + 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_exp_rules_to_create = ((date_to - date_from).days + 1) * num_room_types + + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_id": self.test_availability_plan.id, + "start_date": date_from, + "end_date": date_to, + } + ).apply_massive_changes() + + # ASSERT + self.assertEqual( + len(self.test_availability_plan.rule_ids), + num_exp_rules_to_create, + "the number of rules created by the wizard should consider all " + "room types", + ) + + @freeze_time("1980-12-01") + def test_value_availability_rules_create(self): + # TEST CASE + # Rule values should be set correctly + + # ARRANGE + self.create_common_scenario() + date_from = fields.date.today() + date_to = fields.date.today() + + vals = { + "massive_changes_on": "availability_plan", + "availability_plan_id": self.test_availability_plan.id, + "start_date": date_from, + "end_date": date_to, + "room_type_id": self.test_room_type_double.id, + "quota": 50, + "max_avail": 5, + "min_stay": 10, + "min_stay_arrival": 10, + "max_stay": 10, + "max_stay_arrival": 10, + "closed": True, + "closed_arrival": True, + "closed_departure": True, + } + + # ACT + self.env["pms.massive.changes.wizard"].create(vals).apply_massive_changes() + + # ASSERT + del vals["massive_changes_on"] + del vals["availability_plan_id"] + del vals["start_date"] + del vals["end_date"] + del vals["room_type_id"] + for key in vals: + with self.subTest(k=key): + self.assertEqual( + self.test_availability_plan.rule_ids[0][key], + vals[key], + "The value of " + key + " is not correctly established", + ) + + @freeze_time("1980-12-01") + def test_day_of_week_availability_rules_create(self): + # TEST CASE + # rules for each day of week should be created + + # ARRANGE + self.create_common_scenario() + test_case_week_days = [ + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1], + ] + + date_from = fields.date.today() + date_to = fields.date.today() + datetime.timedelta(days=6) + + wizard = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_id": self.test_availability_plan.id, + "room_type_id": self.test_room_type_double.id, + "start_date": date_from, + "end_date": date_to, + } + ) + + for index, test_case in enumerate(test_case_week_days): + with self.subTest(k=test_case): + # ARRANGE + wizard.write( + { + "apply_on_monday": test_case[0], + "apply_on_tuesday": test_case[1], + "apply_on_wednesday": test_case[2], + "apply_on_thursday": test_case[3], + "apply_on_friday": test_case[4], + "apply_on_saturday": test_case[5], + "apply_on_sunday": test_case[6], + } + ) + # ACT + wizard.apply_massive_changes() + availability_rules = self.test_availability_plan.rule_ids.sorted( + key=lambda s: s.date + ) + # ASSERT + self.assertTrue( + availability_rules[index].date.timetuple()[6] == index + and test_case[index], + "Rule not created on correct day of week", + ) + + # MASSIVE CHANGE WIZARD TESTS ON PRICELIST ITEMS + + @freeze_time("1980-12-01") + def test_pricelist_items_create(self): + # TEST CASE + # items should be created consistently for 1,2,3,4 days + + # ARRANGE + self.create_common_scenario() + for days in [0, 1, 2, 3]: + with self.subTest(k=days): + + # ARRANGE + num_exp_items_to_create = days + 1 + self.test_pricelist.item_ids = False + + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_id": self.test_pricelist.id, + "start_date": fields.date.today(), + "end_date": fields.date.today() + datetime.timedelta(days=days), + "room_type_id": self.test_room_type_double.id, + } + ).apply_massive_changes() + # ASSERT + self.assertEqual( + len( + self.test_pricelist.item_ids + if self.test_pricelist.item_ids + else [] + ), + num_exp_items_to_create, + "the number of rules created by the wizard should include all the " + "days between start and finish (both included)", + ) + + @freeze_time("1980-12-01") + def test_num_pricelist_items_create_no_room_type(self): + # TEST CASE + # (days x room_types) items should be created + + # ARRANGE + 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_exp_items_to_create = ((date_to - date_from).days + 1) * num_room_types + + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_id": self.test_pricelist.id, + "start_date": date_from, + "end_date": date_to, + } + ).apply_massive_changes() + + # ASSERT + self.assertEqual( + len(self.test_pricelist.item_ids), + num_exp_items_to_create, + "the number of rules created by the wizard should consider all " + "room types when one is not applied", + ) + + @freeze_time("1980-12-01") + def test_value_pricelist_items_create(self): + # TEST CASE + # Item values should be set correctly + + # ARRANGE + self.create_common_scenario() + date_from = fields.date.today() + date_to = fields.date.today() + + price = 20 + min_quantity = 3 + vals = { + "pricelist_id": self.test_pricelist, + "date_start": datetime.datetime.combine( + date_from, + datetime.time.min, + ), + "date_end": datetime.datetime.combine( + date_to, + datetime.time.max, + ), + "compute_price": "fixed", + "applied_on": "1_product", + "product_tmpl_id": self.test_room_type_double.product_id.product_tmpl_id, + "fixed_price": price, + "min_quantity": min_quantity, + } + + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_id": self.test_pricelist.id, + "start_date": date_from, + "end_date": date_to, + "room_type_id": self.test_room_type_double.id, + "price": price, + "min_quantity": min_quantity, + } + ).apply_massive_changes() + vals["date_start"] = pytz.timezone("Europe/Madrid").localize(vals["date_start"]) + vals["date_end"] = pytz.timezone("Europe/Madrid").localize(vals["date_end"]) + # ASSERT + for key in vals: + with self.subTest(k=key): + if key == "date_start" or key == "date_end": + self.assertEqual( + fields.Datetime.context_timestamp( + self.test_pricelist.item_ids[0], + self.test_pricelist.item_ids[0][key], + ), + vals[key], + "The value of " + key + " is not correctly established", + ) + else: + self.assertEqual( + self.test_pricelist.item_ids[0][key], + vals[key], + "The value of " + key + " is not correctly established", + ) + + @freeze_time("1980-12-01") + def test_day_of_week_pricelist_items_create(self): + # TEST CASE + # items for each day of week should be created + # ARRANGE + self.create_common_scenario() + test_case_week_days = [ + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1], + ] + date_from = fields.date.today() + date_to = date_from + datetime.timedelta(days=6) + wizard = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_id": self.test_pricelist.id, + "room_type_id": self.test_room_type_double.id, + "start_date": date_from, + "end_date": date_to, + } + ) + for index, test_case in enumerate(test_case_week_days): + with self.subTest(k=test_case): + # ARRANGE + wizard.write( + { + "apply_on_monday": test_case[0], + "apply_on_tuesday": test_case[1], + "apply_on_wednesday": test_case[2], + "apply_on_thursday": test_case[3], + "apply_on_friday": test_case[4], + "apply_on_saturday": test_case[5], + "apply_on_sunday": test_case[6], + } + ) + self.test_pricelist.item_ids = False + + # ACT + wizard.apply_massive_changes() + + # ASSERT + pricelist_items = self.test_pricelist.item_ids.sorted( + key=lambda s: s.date_start + ) + + # ASSERT + self.assertTrue( + ( + fields.Datetime.context_timestamp( + pricelist_items[index], pricelist_items[index].date_start + ) + ).timetuple()[6] + == index + and test_case[index], + "Rule not created on correct day of week", + ) diff --git a/pms/views/pms_property_views.xml b/pms/views/pms_property_views.xml index 3d52280f3..6ceeddec5 100644 --- a/pms/views/pms_property_views.xml +++ b/pms/views/pms_property_views.xml @@ -24,7 +24,10 @@ string="Price and Availability Plans" > - + diff --git a/pms/views/pms_room_type_availability_views.xml b/pms/views/pms_room_type_availability_plan_views.xml similarity index 66% rename from pms/views/pms_room_type_availability_views.xml rename to pms/views/pms_room_type_availability_plan_views.xml index 1bf9b7c85..946e9133b 100644 --- a/pms/views/pms_room_type_availability_views.xml +++ b/pms/views/pms_room_type_availability_plan_views.xml @@ -1,12 +1,24 @@ - pms.room.type.availability.form - pms.room.type.availability + pms.room.type.availability.plan.form + pms.room.type.availability.plan
-
+
+ +
-
- - - - - - + - pms.room.type.availability.tree - pms.room.type.availability + pms.room.type.availability.plan.tree + pms.room.type.availability.plan + @@ -67,7 +77,7 @@ Reservation Availability Plans - pms.room.type.availability + pms.room.type.availability.plan tree,form diff --git a/pms/views/pms_room_type_availability_rule_views.xml b/pms/views/pms_room_type_availability_rule_views.xml index 7e2f1b64d..3e2281a4d 100644 --- a/pms/views/pms_room_type_availability_rule_views.xml +++ b/pms/views/pms_room_type_availability_rule_views.xml @@ -36,10 +36,17 @@ pms.room.type.availability.rule - + + + + + + + + diff --git a/pms/views/product_pricelist_views.xml b/pms/views/product_pricelist_views.xml index 56fffd5d2..79c86cd4f 100644 --- a/pms/views/product_pricelist_views.xml +++ b/pms/views/product_pricelist_views.xml @@ -12,8 +12,7 @@ /> - - + + + + +
+ +
+ + + =", record.start_date)) + if record.end_date: + domain.append(("date", "<=", record.end_date)) + + week_days_to_apply = ( + record.apply_on_monday, + record.apply_on_tuesday, + record.apply_on_wednesday, + record.apply_on_thursday, + record.apply_on_friday, + record.apply_on_saturday, + record.apply_on_sunday, + ) + if record.start_date and record.end_date: + rules = self.env["pms.room.type.availability.rule"].search(domain) + if ( + not record.apply_on_all_week + and record.start_date + and record.end_date + ): + record.rules_to_overwrite = rules.filtered( + lambda x: week_days_to_apply[x.date.timetuple()[6]] + ) + else: + record.rules_to_overwrite = rules + else: + record.rules_to_overwrite = False + else: + record.rules_to_overwrite = False + + @api.depends( + "start_date", + "end_date", + "room_type_id", + "apply_on_monday", + "apply_on_tuesday", + "apply_on_wednesday", + "apply_on_thursday", + "apply_on_friday", + "apply_on_saturday", + "apply_on_sunday", + "apply_on_all_week", + "pricelist_id", + ) + def _compute_pricelist_items_to_overwrite(self): + for record in self: + + if not record.pricelist_id and self._context.get("pricelist_id"): + record.pricelist_id = self._context.get("pricelist_id") + record.massive_changes_on = "pricelist" + + if record.pricelist_id: + domain = [ + ("pricelist_id", "=", record.pricelist_id.id), + ] + + if record.start_date: + domain.append(("date_start", ">=", record.start_date)) + + if record.end_date: + domain.append(("date_end", "<=", record.end_date)) + if record.room_type_id: + domain.append( + ( + "product_tmpl_id", + "=", + record.room_type_id.product_id.product_tmpl_id.id, + ) + ) + + week_days_to_apply = ( + record.apply_on_monday, + record.apply_on_tuesday, + record.apply_on_wednesday, + record.apply_on_thursday, + record.apply_on_friday, + record.apply_on_saturday, + record.apply_on_sunday, + ) + + if record.start_date and record.end_date: + items = self.env["product.pricelist.item"].search(domain) + if ( + not record.apply_on_all_week + and record.start_date + and record.end_date + ): + record.pricelist_items_to_overwrite = items.filtered( + lambda x: week_days_to_apply[x.date_start.timetuple()[6]] + ) + else: + record.pricelist_items_to_overwrite = items + else: + record.pricelist_items_to_overwrite = False + else: + record.pricelist_items_to_overwrite = False + + @api.depends( + "rules_to_overwrite", + ) + def _compute_num_rules_to_overwrite(self): + for record in self: + self.num_rules_to_overwrite = len(record.rules_to_overwrite) + + @api.depends( + "pricelist_items_to_overwrite", + ) + def _compute_num_pricelist_items_to_overwrite(self): + for record in self: + self.num_pricelist_items_to_overwrite = len( + record.pricelist_items_to_overwrite + ) + + def _compute_avail_readonly(self): + for record in self: + record.avail_readonly = ( + True if self._context.get("availability_plan_id") else False + ) + + def _compute_pricelist_readonly(self): + for record in self: + record.pricelist_readonly = ( + True if self._context.get("pricelist_id") else False + ) + + # actions + def apply_massive_changes(self): + tz = "Europe/Madrid" + for record in self: + # remove old rules + record.rules_to_overwrite.unlink() + record.pricelist_items_to_overwrite.unlink() + week_days_to_apply = ( + record.apply_on_monday, + record.apply_on_tuesday, + record.apply_on_wednesday, + record.apply_on_thursday, + record.apply_on_friday, + record.apply_on_saturday, + record.apply_on_sunday, + ) + + # dates between start and end (both included) + for date in [ + record.start_date + datetime.timedelta(days=x) + for x in range(0, (record.end_date - record.start_date).days + 1) + ]: + + if ( + not record.apply_on_all_week + and not week_days_to_apply[date.timetuple()[6]] + ): + continue + + if not record.room_type_id: + rooms = self.env["pms.room.type"].search([]) + else: + rooms = [record.room_type_id] + for room in rooms: + # REVIEW -> maybe would be more efficient creating a list + # and write all data in 1 operation + if record.massive_changes_on == "pricelist": + + dt_from = datetime.datetime.combine( + date, + datetime.time.min, + ) + dt_to = datetime.datetime.combine( + date, + datetime.time.max, + ) + + dt_from = pytz.timezone(tz).localize(dt_from) + dt_to = pytz.timezone(tz).localize(dt_to) + + dt_from = dt_from.astimezone(pytz.utc) + dt_to = dt_to.astimezone(pytz.utc) + + dt_from = dt_from.replace(tzinfo=None) + dt_to = dt_to.replace(tzinfo=None) + + self.env["product.pricelist.item"].create( + { + "pricelist_id": record.pricelist_id.id, + "date_start": dt_from, + "date_end": dt_to, + "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, + } + ) + + # return {} diff --git a/pms/wizards/wizard_massive_changes.xml b/pms/wizards/wizard_massive_changes.xml new file mode 100644 index 000000000..3cd82cbcb --- /dev/null +++ b/pms/wizards/wizard_massive_changes.xml @@ -0,0 +1,249 @@ + + + + Availability Wizard + pms.massive.changes.wizard + +
+ + + + +
+
+ + + + +
+
+ + + + + + + + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
All daysSundayMondayTuesdayWednesdayThursdayFridaySaturday
+ + + + + + + + + + + + + + + +
+
+
+
+ + Rules to apply: + + +
+ +
+ + + + +
+ +
+ + + + +
+
+ + + + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+
+ + + + + availability rules + will be overwritten: + + + + + + + pricelist items + will be overwritten: + + + + + + +
+
+ +
+
+ + Massive changes on Pricelist & Availability Plans + ir.actions.act_window + pms.massive.changes.wizard + + form + new + + +