This module adds housekeeping feature to property management system (PMS).
diff --git a/pms_housekeeping/tests/__init__.py b/pms_housekeeping/tests/__init__.py new file mode 100644 index 000000000..b90277bc3 --- /dev/null +++ b/pms_housekeeping/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_pms_housekeeping_task +from . import test_pms_hr_employee diff --git a/pms_housekeeping/tests/common.py b/pms_housekeeping/tests/common.py new file mode 100644 index 000000000..2563d9d14 --- /dev/null +++ b/pms_housekeeping/tests/common.py @@ -0,0 +1,63 @@ +from odoo.tests import common + + +class TestPms(common.SavepointCase): + def setUp(self): + super().setUp() + # delete all previous pms.housekeeping.task.type records (only for the test purpose) + self.env["pms.housekeeping.task.type"].search([]).unlink() + # create a sale channel + self.sale_channel1 = self.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + self.availability_plan1 = self.env["pms.availability.plan"].create( + { + "name": "Availability Plan 1", + } + ) + self.pricelist1 = self.env["product.pricelist"].create( + { + "name": "Pricelist 1", + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.pms_property1 = self.env["pms.property"].create( + { + "name": "Property 1", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.room_type_class1 = self.env["pms.room.type.class"].create( + { + "name": "Room Type Class 1", + "default_code": "RTC1", + } + ) + self.room_type1 = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + + self.room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + # create partner + self.partner1 = self.env["res.partner"].create({"name": "Ana"}) diff --git a/pms_housekeeping/tests/test_pms_housekeeping_task.py b/pms_housekeeping/tests/test_pms_housekeeping_task.py new file mode 100644 index 000000000..089ad86f8 --- /dev/null +++ b/pms_housekeeping/tests/test_pms_housekeeping_task.py @@ -0,0 +1,466 @@ +from datetime import datetime, timedelta + +from freezegun import freeze_time + +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsHousekeepingTask(TestPms): + def setUp(self): + super().setUp() + + @freeze_time("2000-01-04") + def test_no_create_overnight_task_when_it_shouldnt_when_no_overnight(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_overnight": True, + "days_after_clean_overnight": 2, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertFalse(housekeeping_task, "Housekeeping task shouldn't be created") + + @freeze_time("2000-01-10") + def test_create_overnight_task_when_it_should_be_created_with_different_dates(self): + # ARRANGE + # create reservation with checkin today + self.env["pms.reservation"].create( + { + "checkin": datetime.today(), + "checkout": datetime.today() + timedelta(days=7), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_overnight": True, + "days_after_clean_overnight": 2, + } + ) + # Define a list of dates to iterate over + test_dates = [ + "2000-01-12", + "2000-01-14", + "2000-01-16", + ] + for test_date in test_dates: + with self.subTest(test_date=test_date): + # Freeze time to the current test date + with freeze_time(test_date): + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id), ("task_date", "=", test_date)] + ) + # Verify that the housekeeping task is created + self.assertTrue( + housekeeping_task, "Housekeeping task should be created" + ) + + @freeze_time("2000-01-10") + def test_create_overnight_task_when_it_shouldnt_be_created_with_different_dates( + self, + ): + # ARRANGE + # create reservation with checkin today + self.env["pms.reservation"].create( + { + "checkin": datetime.today(), + "checkout": datetime.today() + timedelta(days=7), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_overnight": True, + "days_after_clean_overnight": 2, + } + ) + # Define a list of dates to iterate over + test_dates = [ + "2000-01-11", + "2000-01-13", + "2000-01-15", + ] + for test_date in test_dates: + with self.subTest(test_date=test_date): + # Freeze time to the current test date + with freeze_time(test_date): + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id), ("task_date", "=", test_date)] + ) + # Verify that the housekeeping task is created + self.assertFalse( + housekeeping_task, "Housekeeping task shouldn't be created" + ) + + ################### + @freeze_time("2000-01-04") + def test_no_create_empty_task_when_it_shouldnt_when_no_empty(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_empty": True, + "days_after_clean_empty": 2, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertFalse(housekeeping_task, "Housekeeping task shouldn't be created") + + @freeze_time("2000-02-11") + def test_create_empty_task_when_it_should_be_created_with_different_dates(self): + # ARRANGE + # create reservation with checkout today - 10 days + self.env["pms.reservation"].create( + { + "checkin": datetime.today() + timedelta(days=-20), + "checkout": datetime.today() + timedelta(days=-10), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + # create task type + self.env["pms.housekeeping.task.type"].create( + {"name": "Task Type 1", "is_empty": True, "days_after_clean_empty": 2} + ) + # Define a list of dates to iterate over + test_dates = [ + "2000-02-03", + "2000-02-05", + "2000-02-07", + ] + for test_date in test_dates: + with self.subTest(test_date=test_date): + # Freeze time to the current test date + with freeze_time(test_date): + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id), ("task_date", "=", test_date)] + ) + # Verify that the housekeeping task is created + self.assertTrue( + housekeeping_task, "Housekeeping task should be created" + ) + + @freeze_time("2000-02-11") + def test_create_empty_task_when_it_shouldnt_be_created_with_different_dates(self): + # ARRANGE + # create reservation with checkout today - 10 days + self.env["pms.reservation"].create( + { + "checkin": datetime.today() + timedelta(days=-20), + "checkout": datetime.today() + timedelta(days=-10), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + # create task type + self.env["pms.housekeeping.task.type"].create( + {"name": "Task Type 1", "is_empty": True, "days_after_clean_empty": 2} + ) + # Define a list of dates to iterate over + test_dates = [ + "2000-02-02", + "2000-02-04", + "2000-02-06", + ] + for test_date in test_dates: + with self.subTest(test_date=test_date): + # Freeze time to the current test date + with freeze_time(test_date): + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id), ("task_date", "=", test_date)] + ) + # Verify that the housekeeping task is created + self.assertFalse( + housekeeping_task, "Housekeeping task should be created" + ) + + @freeze_time("2000-01-04") + def test_create_checkin_task_when_it_should_when_checkin(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_checkin": True, + } + ) + # create reservation with checkin today + self.env["pms.reservation"].create( + { + "checkin": datetime.today(), + "checkout": datetime.today() + timedelta(days=3), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertTrue(housekeeping_task, "Housekeeping task should be created") + + @freeze_time("2000-01-04") + def test_no_create_checkin_task_when_it_shouldnt_when_no_checkin(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_checkin": True, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertFalse(housekeeping_task, "Housekeeping task shouldn't be created") + + @freeze_time("2000-01-04") + def test_create_checkout_task_when_it_should_when_checkout(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_checkout": True, + } + ) + # create reservation with checkout today + self.env["pms.reservation"].create( + { + "checkin": datetime.today() + timedelta(days=-3), + "checkout": datetime.today(), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertTrue(housekeeping_task, "Housekeeping task should be created") + + @freeze_time("2000-01-04") + def test_no_create_checkout_task_when_it_shouldnt_when_no_checkout(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_checkout": True, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task is not created + self.assertFalse(housekeeping_task, "Housekeeping task shouldn't be created") + + @freeze_time("2000-01-04") + def test_create_task_type_childs(self): + # ARRANGE + # create task type + parent_task_type = self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type Parent", + "is_checkout": True, + } + ) + child_task_type = self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type Child", + "is_checkout": True, + "parent_id": parent_task_type.id, + } + ) + # create reservation with checkout today + self.env["pms.reservation"].create( + { + "checkin": datetime.today() + timedelta(days=-3), + "checkout": datetime.today(), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id), ("task_type_id", "=", child_task_type.id)] + ) + # Verify that the housekeeping task is not created + self.assertTrue(housekeeping_task, "Child housekeeping task should be created") + + def test_no_create_task_type_childs(self): + # ARRANGE + # create task type + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type Parent", + "is_checkout": True, + } + ) + + # create reservation with checkout today + self.env["pms.reservation"].create( + { + "checkin": datetime.today() + timedelta(days=-3), + "checkout": datetime.today(), + "room_type_id": self.room_type1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel1.id, + } + ) + + # ACT + # call method to create task + self.env["pms.housekeeping.task"].generate_tasks(self.pms_property1) + + # ASSERT + # search for the task + housekeeping_task = self.env["pms.housekeeping.task"].search( + [("room_id", "=", self.room1.id)] + ) + # Verify that the housekeeping task childs is not created + self.assertFalse( + housekeeping_task.child_ids, "Child housekeeping task shouldn´t be created" + ) + + def test_days_after_clean_overnight_constraint(self): + # ARRANGE, ACT & ASSERT + # create task type and verify that the constraint is raised + with self.assertRaises( + ValidationError, msg="Days After Clean Overnight should be greater than 0" + ): + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_overnight": True, + "days_after_clean_overnight": 0, + } + ) + + def test_days_after_clean_empty_constraint(self): + # ARRANGE, ACT & ASSERT + # create task type and verify that the constraint is raised + with self.assertRaises( + ValidationError, msg="Days After Clean Overnight should be greater than 0" + ): + self.env["pms.housekeeping.task.type"].create( + { + "name": "Task Type 1", + "is_empty": True, + "days_after_clean_empty": 0, + } + ) diff --git a/pms_housekeeping/tests/test_pms_hr_employee.py b/pms_housekeeping/tests/test_pms_hr_employee.py new file mode 100644 index 000000000..7440779aa --- /dev/null +++ b/pms_housekeeping/tests/test_pms_hr_employee.py @@ -0,0 +1,92 @@ +from odoo.exceptions import ValidationError +from .common import TestPms + + +class TestPmsHrEmployee(TestPms): + def setUp(self): + super().setUp() + + def test_employee_pre_assigned_room_inconsistent(self): + # ARRANGE + + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.room2 = self.env["pms.room"].create( + { + "name": "Room 202", + "pms_property_id": self.pms_property2.id, + "room_type_id": self.room_type1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room should belong to the employee's property." + ): + self.hr_employee = self.env["hr.employee"].create( + { + "name": "Test Employee", + "company_id": self.company1.id, + "job_id": self.env.ref("pms_housekeeping.housekeeping_job_id").id, + "property_ids": [(6, 0, [self.pms_property1.id])], + "pre_assigned_room_ids": [(6, 0, [self.room2.id])], + } + ) + + def test_employee_pre_assigned_room_consistent_with_property(self): + # ARRANGE + self.hr_employee = self.env["hr.employee"].create( + { + "name": "Test Employee", + "company_id": self.company1.id, + "job_id": self.env.ref("pms_housekeeping.housekeeping_job_id").id, + "property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + + # ACT + self.hr_employee.pre_assigned_room_ids = [(6, 0, [self.room1.id])] + + # ASSERT + self.assertTrue( + self.hr_employee.pre_assigned_room_ids, "Pre assigned room is not consistent with property" + ) + + def test_employee_pre_assigned_room_consistent_without_properties(self): + # ARRANGE + self.hr_employee = self.env["hr.employee"].create( + { + "name": "Test Employee", + "company_id": self.company1.id, + "job_id": self.env.ref("pms_housekeeping.housekeeping_job_id").id, + } + ) + + # ACT + self.hr_employee.pre_assigned_room_ids = [(6, 0, [self.room1.id])] + + # ASSERT + self.assertTrue( + self.hr_employee.pre_assigned_room_ids, "Pre assigned room is not consistent without properties" + ) + + def test_not_pre_assigned_room_no_housekeeper_employee(self): + # ARRANGE + self.hr_employee = self.env["hr.employee"].create( + { + "name": "Test Employee", + "company_id": self.company1.id, + "job_id": self.env.ref("hr.job_trainee").id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The job position should be Housekeeper." + ): + self.hr_employee.pre_assigned_room_ids = [(6, 0, [self.room1.id])] diff --git a/pms_housekeeping/views/hr_employee_views.xml b/pms_housekeeping/views/hr_employee_views.xml index d3159d825..ad0c7e79f 100644 --- a/pms_housekeeping/views/hr_employee_views.xml +++ b/pms_housekeeping/views/hr_employee_views.xml @@ -9,8 +9,10 @@ name="pre_assigned_room_ids" widget="many2many_tags" attrs="{'invisible': [('job_name', '!=', 'Housekeeper')]}" + domain="[('id', 'in', allowed_pre_assigned_room_ids)]" />