diff --git a/pms/__manifest__.py b/pms/__manifest__.py
index 1268e03ba..2d76a791b 100644
--- a/pms/__manifest__.py
+++ b/pms/__manifest__.py
@@ -71,7 +71,7 @@
"views/account_journal_views.xml",
"views/folio_portal_templates.xml",
"views/reservation_portal_templates.xml",
- "wizards/wizard_reservation.xml",
+ "wizards/wizard_split_join_swap_reservation.xml",
"wizards/wizard_massive_changes.xml",
"wizards/wizard_advanced_filters.xml",
],
diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py
index bb5c900ce..ffa72d376 100644
--- a/pms/models/pms_reservation.py
+++ b/pms/models/pms_reservation.py
@@ -353,6 +353,7 @@ class PmsReservation(models.Model):
string="Room/s",
help="Rooms that are reserved",
compute="_compute_rooms",
+ store=True,
tracking=True,
)
credit_card_details = fields.Text(
@@ -1324,7 +1325,7 @@ class PmsReservation(models.Model):
"view_type": "form",
"view_mode": "form",
"name": "Unify the reservation",
- "res_model": "pms.reservation.wizard",
+ "res_model": "pms.reservation.split.join.swap.wizard",
"target": "new",
"type": "ir.actions.act_window",
"context": {
diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py
index 7ac6cda6e..d327e2ec1 100644
--- a/pms/models/pms_reservation_line.py
+++ b/pms/models/pms_reservation_line.py
@@ -163,9 +163,13 @@ class PmsReservationLine(models.Model):
key=lambda r: (r.reservation_id, r.date)
):
reservation = line.reservation_id
- if reservation.preferred_room_id != line.room_id or not line.room_id:
- # If reservation has a preferred_room_id We can allow
- # select room_id regardless room_type_id selected on reservation
+ if (
+ reservation.preferred_room_id
+ and reservation.preferred_room_id != line.room_id
+ ) or (
+ (reservation.preferred_room_id or reservation.room_type_id)
+ and not line.room_id
+ ):
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.availability.plan"].rooms_available(
diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv
index f6b54a3fb..67bc1107a 100644
--- a/pms/security/ir.model.access.csv
+++ b/pms/security/ir.model.access.csv
@@ -49,7 +49,8 @@ manager_access_property,manager_access_property,model_pms_property,pms.group_pms
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_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_reservation_split_join_swap_wizard,user_access_pms_reservation_split_join_swap_wizard,model_pms_reservation_split_join_swap_wizard,pms.group_pms_user,1,1,1,1
+user_access_pms_wizard_reservation_lines_split,user_access_pms_wizard_reservation_lines_split,model_pms_wizard_reservation_lines_split,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
user_access_pms_advanced_filters_wizard,user_access_pms_advanced_filters_wizard,model_pms_advanced_filters_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_folio_wizard,user_access_pms_folio_wizard,model_pms_folio_wizard,pms.group_pms_user,1,1,1,1
diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py
index 8247dc4eb..48c50bbb2 100644
--- a/pms/tests/__init__.py
+++ b/pms/tests/__init__.py
@@ -39,3 +39,4 @@ from . import test_pms_board_service_room_type
from . import test_pms_board_service_room_type_line
from . import test_pms_folio_invoice
from . import test_pms_folio_sale_line
+from . import test_pms_wizard_split_join_swap_reservation
diff --git a/pms/tests/test_pms_wizard_split_join_swap_reservation.py b/pms/tests/test_pms_wizard_split_join_swap_reservation.py
new file mode 100644
index 000000000..ec7d2e852
--- /dev/null
+++ b/pms/tests/test_pms_wizard_split_join_swap_reservation.py
@@ -0,0 +1,967 @@
+import datetime
+
+from odoo.exceptions import UserError
+from odoo.tests import common
+
+
+class TestPmsWizardMassiveChanges(common.SavepointCase):
+ def create_common_scenario(self):
+ # product.pricelist
+ self.test_pricelist = self.env["product.pricelist"].create(
+ {
+ "name": "test pricelist 1",
+ }
+ )
+ # pms.availability.plan
+ self.test_availability_plan = self.env["pms.availability.plan"].create(
+ {
+ "name": "Availability plan for TEST",
+ "pms_pricelist_ids": [(6, 0, [self.test_pricelist.id])],
+ }
+ )
+ # sequences
+ self.folio_sequence = self.env["ir.sequence"].create(
+ {
+ "name": "PMS Folio",
+ "code": "pms.folio",
+ "padding": 4,
+ "company_id": self.env.ref("base.main_company").id,
+ }
+ )
+ self.reservation_sequence = self.env["ir.sequence"].create(
+ {
+ "name": "PMS Reservation",
+ "code": "pms.reservation",
+ "padding": 4,
+ "company_id": self.env.ref("base.main_company").id,
+ }
+ )
+ self.checkin_sequence = self.env["ir.sequence"].create(
+ {
+ "name": "PMS Checkin",
+ "code": "pms.checkin.partner",
+ "padding": 4,
+ "company_id": self.env.ref("base.main_company").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,
+ "folio_sequence_id": self.folio_sequence.id,
+ "reservation_sequence_id": self.reservation_sequence.id,
+ "checkin_sequence_id": self.checkin_sequence.id,
+ }
+ )
+ # pms.room.type.class
+ self.test_room_type_class = self.env["pms.room.type.class"].create(
+ {"name": "Room", "default_code": "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",
+ "default_code": "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",
+ "default_code": "DBL_Test",
+ "class_id": self.test_room_type_class.id,
+ }
+ )
+
+ # create rooms
+ self.room1 = self.env["pms.room"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "name": "Double 101",
+ "room_type_id": self.test_room_type_double.id,
+ "capacity": 2,
+ }
+ )
+
+ self.room2 = self.env["pms.room"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "name": "Double 102",
+ "room_type_id": self.test_room_type_double.id,
+ "capacity": 2,
+ }
+ )
+
+ # self.room3 = self.env["pms.room"].create(
+ # {
+ # "pms_property_id": self.test_property.id,
+ # "name": "Double 103",
+ # "room_type_id": self.test_room_type_double.id,
+ # "capacity": 2,
+ # }
+ # )
+
+ # UNIFY TESTS # review
+ def test_unify_reservation_avail_should(self):
+ # TEST CASE
+ # Unify reservation in one room with avail for that room
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | | r1 | | | |
+ # | Double 102 | | r1 | | | | |
+ # +------------+------+------+------+----+----+----+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r1.reservation_line_ids[0].room_id = self.room2
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_join(
+ r1, self.room2
+ )
+ # ASSERT
+ self.assertEqual(
+ r1.reservation_line_ids.mapped("room_id"),
+ self.room2,
+ "The unify operation should assign the indicated room to all nights",
+ )
+
+ def test_unify_reservation_avail_not(self):
+ # TEST CASE
+ # Unify reservation in one room and
+ # there's not availability for that room
+
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | r2 | | | |
+ # | Double 102 | r0 | r0 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+ # ARRANGE
+ self.create_common_scenario()
+ self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=2),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now() + datetime.timedelta(days=2),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.test_room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2.flush()
+ # ACT & ASSERT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_join(
+ r1, self.room1
+ )
+
+ def test_unify_reservation_avail_not_room_exist(self):
+ # TEST CASE
+ # Unify reservation in one room and
+ # the room indicated doesn't exist: pms.room()
+
+ # ARRANGE
+ self.create_common_scenario()
+ self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2.flush()
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_join(
+ r2, self.env["pms.room"]
+ )
+
+ # SWAP TESTS
+ def test_swap_reservation_rooms_01(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | r1 | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | r2 | r2 | | | |
+ # | Double 102 | r1 | r1 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room1.id,
+ self.room2.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids.room_id == self.room1
+ )
+
+ def test_swap_reservation_rooms_02(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | r1 | r1 | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | r2 | r2 | | | |
+ # | Double 102 | r2 | r1 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now() + datetime.timedelta(days=1),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room1.id,
+ self.room2.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids[1:].room_id == self.room1
+ )
+
+ def test_swap_reservation_rooms_03(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | r1 | r1 | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | r2 | r2 | | | |
+ # | Double 102 | | r1 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now() + datetime.timedelta(days=1),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room2.id,
+ self.room1.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids.room_id == self.room1
+ )
+
+ def test_swap_reservation_rooms_04(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | r2 | | | | |
+ # | Double 102 | r1 | r1 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=2),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room1.id,
+ self.room2.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids[:1].room_id == self.room1
+ )
+
+ def test_swap_reservation_rooms_05(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | r2 | r2 | | | |
+ # | Double 102 | r1 | r1 | | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=2),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room2.id,
+ self.room1.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids.room_id == self.room1
+ )
+
+ def test_swap_reservation_rooms_06(self):
+ # TEST CASE
+ # Swap room1 with room2 should raise an error
+ # because room1 has no reservation between
+ # checkin & checkout provided
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | | | | | |
+ # | Double 102 | r1 | r1 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | r1 | | | |
+ # | Double 102 | | | | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room2.id,
+ self.room1.id,
+ )
+ # ASSERT
+ self.assertTrue(r1.reservation_line_ids.room_id == self.room1)
+
+ def test_swap_reservation_rooms_gap_01(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r0 | | r1 | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | | r2 | | | |
+ # | Double 102 | r0 | r2 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r0 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=1),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now() + datetime.timedelta(days=2),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room1.id,
+ self.room2.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r0.reservation_line_ids.room_id == self.room2
+ and r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids[0].room_id == self.room1
+ and r2.reservation_line_ids[2].room_id == self.room1
+ and r2.reservation_line_ids[1].room_id == self.room2
+ )
+
+ def test_swap_reservation_rooms_gap_02(self):
+ # TEST CASE
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r0 | | r1 | | | |
+ # | Double 102 | r2 | r2 | r2 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # State after swap
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r2 | r2 | r2 | | | |
+ # | Double 102 | r0 | | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r0 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=1),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now() + datetime.timedelta(days=2),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r2 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ r2.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room2.id,
+ self.room1.id,
+ )
+ # ASSERT
+ self.assertTrue(
+ r0.reservation_line_ids.room_id == self.room2
+ and r1.reservation_line_ids.room_id == self.room2
+ and r2.reservation_line_ids.room_id == self.room1
+ )
+
+ # NOT VALID TEST CASES
+ def test_swap_reservation_not_valid_01(self):
+ # TEST CASE
+ # Swap room1 with room2 should raise an error
+ # because room1 has no reservation between
+ # checkin & checkout provided
+
+ # Initial state
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | | | | | |
+ # | Double 102 | r1 | r1 | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ASSERT & ACT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservations_swap(
+ datetime.datetime.now(),
+ datetime.datetime.now() + datetime.timedelta(days=3),
+ self.room1.id,
+ self.room2.id,
+ )
+
+ # SPLIT TESTS
+ def test_split_reservation_check_room_splitted_valid_01(self):
+ # TEST CASE
+ # A reservation is created with preferred room
+ # The room for 1st night is switched to another room
+ # Expected result:
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | | r1 | r1 | | | |
+ # | Double 102 | r1 | | | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1, datetime.date.today(), self.room2
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids[0].room_id == self.room2
+ and r1.reservation_line_ids[1:].room_id == self.room1
+ )
+
+ def test_split_reservation_check_room_splitted_valid_02(self):
+ # TEST CASE
+ # A reservation is created with preferred room
+ # The room for 1st night is switched to another room
+ # Expected result:
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | r1 | | | | |
+ # | Double 102 | | | r1 | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1,
+ (
+ datetime.datetime(
+ year=datetime.date.today().year,
+ month=datetime.date.today().month,
+ day=datetime.date.today().day,
+ )
+ + datetime.timedelta(days=2)
+ ).date(),
+ self.room2,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids[2].room_id == self.room2
+ and r1.reservation_line_ids[:1].room_id == self.room1
+ )
+
+ def test_split_reservation_check_room_splitted_valid_03(self):
+ # TEST CASE
+ # A reservation is created with preferred room
+ # The room for 1st night is switched to another room
+ # Expected result:
+ # +------------+------+------+------+----+----+----+
+ # | room/date | 01 | 02 | 03 | 04 | 05 | 06 |
+ # +------------+------+------+------+----+----+----+
+ # | Double 101 | r1 | | r1 | | | |
+ # | Double 102 | | r1 | | | | |
+ # +------------+------+------+------+----+----+----+
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1,
+ (
+ datetime.datetime(
+ year=datetime.date.today().year,
+ month=datetime.date.today().month,
+ day=datetime.date.today().day,
+ )
+ + datetime.timedelta(days=1)
+ ).date(),
+ self.room2,
+ )
+ # ASSERT
+ self.assertTrue(
+ r1.reservation_line_ids[1].room_id == self.room2
+ and r1.reservation_line_ids[0].room_id == self.room1
+ and r1.reservation_line_ids[2].room_id == self.room1
+ )
+
+ def test_split_reservation_check_room_splitted_not_valid_01(self):
+ # TEST CASE
+ # Try to split the reservation for one night
+ # and set with a non valid room
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ room_not_exist = self.room3 = self.env["pms.room"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "name": "Double 103",
+ "room_type_id": self.test_room_type_double.id,
+ "capacity": 2,
+ }
+ )
+ room_not_exist.unlink()
+ # ACT & ASSERT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1, datetime.datetime.now(), room_not_exist
+ )
+
+ def test_split_reservation_check_room_splitted_not_valid_02(self):
+ # TEST CASE
+ # Try to split the reservation for one night
+ # and that night doesn't belong to reservation
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT & ASSERT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1, datetime.datetime.now() + datetime.timedelta(days=100), self.room2
+ )
+
+ def test_split_reservation_check_room_splitted_not_valid_03(self):
+ # TEST CASE
+ # Try to split the reservation for one night
+ # and the reservation not exists
+
+ # ARRANGE
+ self.create_common_scenario()
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT & ASSERT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ self.env["pms.reservation"], datetime.datetime.now(), self.room2
+ )
+
+ def test_split_reservation_check_room_splitted_not_valid_04(self):
+ # TEST CASE
+ # Try to split the reservation to one room
+ # and the room is not available
+
+ # ARRANGE
+ self.create_common_scenario()
+ self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room2.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1 = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.test_property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "preferred_room_id": self.room1.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r1.flush()
+ # ACT & ASSERT
+ with self.assertRaises(UserError):
+ self.env["pms.reservation.split.join.swap.wizard"].reservation_split(
+ r1, datetime.datetime.now(), self.room2
+ )
diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml
index 12603e3fa..c9599b969 100644
--- a/pms/views/pms_reservation_views.xml
+++ b/pms/views/pms_reservation_views.xml
@@ -77,15 +77,18 @@
style="margin-bottom:0px;"
attrs="{'invisible': [('splitted','=',False)]}"
>
- This reservation is part of splitted reservation, you can try to
- unify the reservation here
+ This reservation is part of a splitted reservation, you can try to
+ join the reservation here
+
+
@@ -949,4 +952,15 @@
sequence="5"
parent="pms.menu_reservations"
/>
+
+
+ Reservation operations
+ ir.actions.act_window
+ pms.reservation.split.join.swap.wizard
+ form
+ new
+
+ form
+
+
diff --git a/pms/wizards/__init__.py b/pms/wizards/__init__.py
index 335aeda75..5ac1f0854 100644
--- a/pms/wizards/__init__.py
+++ b/pms/wizards/__init__.py
@@ -1,4 +1,4 @@
-from . import wizard_reservation
+from . import wizard_split_join_swap_reservation
from . import wizard_massive_changes
from . import wizard_advanced_filters
from . import wizard_folio
diff --git a/pms/wizards/wizard_reservation.py b/pms/wizards/wizard_reservation.py
deleted file mode 100644
index e615fcd20..000000000
--- a/pms/wizards/wizard_reservation.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from odoo import fields, models
-
-
-class ReservationWizard(models.TransientModel):
- _name = "pms.reservation.wizard"
- allowed_rooms = fields.One2many("pms.room", compute="_compute_allowed_rooms")
- options = fields.Many2one("pms.room", string="Room")
-
- def _compute_allowed_rooms(self):
- for record in self:
- record.allowed_rooms = self._context.get("rooms_available")
-
- def unify(self):
- if self.options:
- for line in (
- self.env["pms.reservation"]
- .search([("id", "=", self._context.get("active_id"))])
- .reservation_line_ids
- ):
- line.room_id = self.options
diff --git a/pms/wizards/wizard_reservation.xml b/pms/wizards/wizard_reservation.xml
deleted file mode 100644
index 0c95bc3fb..000000000
--- a/pms/wizards/wizard_reservation.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- Reservation Wizard
- pms.reservation.wizard
-
-
-
-
-
diff --git a/pms/wizards/wizard_split_join_swap_reservation.py b/pms/wizards/wizard_split_join_swap_reservation.py
new file mode 100644
index 000000000..a2bd62642
--- /dev/null
+++ b/pms/wizards/wizard_split_join_swap_reservation.py
@@ -0,0 +1,353 @@
+import datetime
+
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+
+
+class ReservationSplitJoinSwapWizard(models.TransientModel):
+ _name = "pms.reservation.split.join.swap.wizard"
+ operation = fields.Selection(
+ [
+ ("swap", "Swap rooms"),
+ ("split", "Split reservation"),
+ ("join", "Join reservation"),
+ ],
+ string="Operation",
+ help="Operation to be applied on the reservation",
+ default=lambda self: self._context.get("default_operation")
+ if self._context.get("default_operation")
+ else "swap",
+ )
+ reservation_id = fields.Many2one(
+ string="Reservation",
+ comodel_name="pms.reservation",
+ default=lambda self: self.env["pms.reservation"]
+ .browse(self._context.get("active_id"))
+ .id
+ if self._context.get("active_id")
+ else False,
+ )
+ checkin = fields.Date(
+ string="Check In",
+ default=lambda self: self.env["pms.reservation"]
+ .browse(self._context.get("active_id"))
+ .checkin
+ if self._context.get("active_id")
+ else False,
+ )
+ checkout = fields.Date(
+ string="Check Out",
+ default=lambda self: self.env["pms.reservation"]
+ .browse(self._context.get("active_id"))
+ .checkout
+ if self._context.get("active_id")
+ else False,
+ )
+ reservations = fields.Many2many(
+ string="Reservations",
+ comodel_name="pms.reservation",
+ compute="_compute_reservations",
+ readonly=False,
+ store=True,
+ )
+ room_source = fields.Many2one(
+ string="Room Source",
+ comodel_name="pms.room",
+ domain="[('id', 'in', allowed_rooms_sources)]",
+ default=lambda self: self.env["pms.reservation"]
+ .browse(self._context.get("active_id"))
+ .preferred_room_id
+ if self._context.get("active_id")
+ and not self.env["pms.reservation"]
+ .browse(self._context.get("active_id"))
+ .splitted
+ else False,
+ )
+ room_target = fields.Many2one(
+ string="Room Target",
+ comodel_name="pms.room",
+ domain="[('id', 'in', allowed_rooms_target)]",
+ )
+ allowed_rooms_sources = fields.Many2many(
+ string="Allowed rooms source",
+ comodel_name="pms.room",
+ relation="pms_wizard_split_join_swap_reservation_rooms_source",
+ column1="wizard_id",
+ column2="room_id",
+ compute="_compute_allowed_rooms_source",
+ store=True,
+ readonly=False,
+ )
+ allowed_rooms_target = fields.Many2many(
+ string="Allowed rooms target",
+ comodel_name="pms.room",
+ relation="pms_wizard_split_join_swap_reservation_rooms_target",
+ column1="wizard_id",
+ column2="room_id",
+ compute="_compute_allowed_rooms_target",
+ store=True,
+ readonly=False,
+ )
+ reservation_lines_to_change = fields.One2many(
+ comodel_name="pms.wizard.reservation.lines.split",
+ inverse_name="reservation_wizard_id",
+ compute="_compute_reservation_lines",
+ store=True,
+ readonly=False,
+ )
+
+ @api.depends("checkin", "checkout", "room_source", "room_target")
+ def _compute_reservations(self):
+ for record in self:
+ if record.checkin and record.checkout:
+ reservation_ids = list()
+ for date_iterator in [
+ record.checkin + datetime.timedelta(days=x)
+ for x in range(0, (record.checkout - record.checkin).days)
+ ]:
+ domain_lines = []
+ if record.room_source and record.room_target:
+ domain_lines.extend(
+ [
+ "|",
+ ("room_id", "=", record.room_source.id),
+ ("room_id", "=", record.room_target.id),
+ ]
+ )
+
+ domain_lines.append(("date", "=", date_iterator))
+ lines = self.env["pms.reservation.line"].search(domain_lines)
+ reservation_ids.extend(lines.mapped("reservation_id.id"))
+ reservations = (
+ self.env["pms.reservation"]
+ .search(
+ [
+ ("id", "in", reservation_ids),
+ ("rooms", "!=", False),
+ ]
+ )
+ .sorted("rooms")
+ )
+ record.reservations = reservations
+ else:
+ record.reservations = False
+
+ @api.depends("reservation_id")
+ def _compute_reservation_lines(self):
+ for record in self:
+ if record.reservation_id:
+ cmds = [(5, 0, 0)]
+ for line in record.reservation_id.reservation_line_ids:
+ cmds.append(
+ (
+ 0,
+ 0,
+ {
+ "reservation_wizard_id": record.id,
+ "room_id": line.room_id,
+ "date": line.date,
+ },
+ )
+ )
+ record.reservation_lines_to_change = cmds
+ else:
+ record.reservation_lines_to_change = False
+
+ @api.depends("checkin", "checkout")
+ def _compute_allowed_rooms_source(self):
+ for record in self:
+ record.allowed_rooms_sources = (
+ record.reservations.reservation_line_ids.mapped("room_id")
+ )
+
+ @api.depends_context("default_operation")
+ @api.depends("checkin", "checkout", "room_source", "operation")
+ def _compute_allowed_rooms_target(self):
+ for record in self:
+ record.allowed_rooms_target = False
+ record.room_target = False
+ if record.checkin and record.checkout:
+ rooms_available = self.env["pms.availability.plan"].rooms_available(
+ checkin=record.checkin,
+ checkout=record.checkout,
+ room_type_id=False, # Allows to choose any available room
+ current_lines=record.reservation_id.reservation_line_ids.ids,
+ pricelist_id=record.reservation_id.pricelist_id.id,
+ pms_property_id=record.reservation_id.pms_property_id.id,
+ )
+ domain = [("capacity", ">=", record.reservation_id.adults)]
+ if record.room_source:
+ domain.append(("id", "!=", record.room_source.id))
+
+ if record.operation == "swap":
+ domain.append(
+ (
+ "id",
+ "in",
+ record.reservations.reservation_line_ids.mapped(
+ "room_id"
+ ).ids,
+ )
+ )
+ else:
+ domain.extend(
+ [
+ ("id", "in", rooms_available.ids),
+ ]
+ )
+ rooms = self.env["pms.room"].search(domain)
+ record.allowed_rooms_target = rooms
+
+ @api.model
+ def reservation_split(self, reservation, date, room):
+ if not reservation:
+ raise UserError(_("Invalid reservation"))
+ if not reservation or not reservation.reservation_line_ids.filtered(
+ lambda x: x.date == date
+ ):
+ raise UserError(_("Invalid date for reservation line "))
+
+ if not self.browse(room.id):
+ raise UserError(_("The room does not exist"))
+ rooms_available = self.env["pms.availability.plan"].rooms_available(
+ checkin=date,
+ checkout=(
+ datetime.datetime(year=date.year, month=date.month, day=date.day)
+ + datetime.timedelta(days=1)
+ ).date(),
+ current_lines=reservation.reservation_line_ids.ids,
+ pricelist_id=reservation.pricelist_id.id,
+ pms_property_id=reservation.pms_property_id.id,
+ )
+ if room not in rooms_available:
+ raise UserError(_("The room is not available"))
+
+ reservation.reservation_line_ids.filtered(
+ lambda x: x.date == date
+ ).room_id = room.id
+
+ @api.model
+ def reservation_join(self, reservation, room):
+ rooms_available = self.env["pms.availability.plan"].rooms_available(
+ checkin=reservation.checkin,
+ checkout=reservation.checkout,
+ current_lines=reservation.reservation_line_ids.ids,
+ pricelist_id=reservation.pricelist_id.id,
+ pms_property_id=reservation.pms_property_id.id,
+ )
+ if room in rooms_available:
+ for line in (
+ self.env["pms.reservation"]
+ .search([("id", "=", reservation.id)])
+ .reservation_line_ids
+ ):
+ line.room_id = room.id
+ else:
+ raise UserError(_("Room {} not available.".format(room.name)))
+
+ @api.model
+ def reservations_swap(self, checkin, checkout, source, target):
+ reservations = self.env["pms.reservation"].search(
+ [("checkin", ">=", checkin), ("checkout", "<=", checkout)]
+ )
+ lines = self.env["pms.reservation.line"].search_count(
+ [("room_id", "=", source), ("reservation_id", "in", reservations.ids)]
+ )
+ if not lines:
+ raise UserError(_("There's no reservations lines with provided room"))
+
+ for date_iterator in [
+ checkin + datetime.timedelta(days=x)
+ for x in range(0, (checkout - checkin).days)
+ ]:
+ line_room_source = self.env["pms.reservation.line"].search(
+ [("date", "=", date_iterator), ("room_id", "=", source)]
+ )
+ line_room_target = self.env["pms.reservation.line"].search(
+ [("date", "=", date_iterator), ("room_id", "=", target)]
+ )
+ if line_room_source and line_room_target:
+
+ # this causes an unique error constraint
+ line_room_target.occupies_availability = False
+ line_room_source.occupies_availability = False
+
+ line_room_target.room_id = source
+ line_room_source.room_id = target
+
+ self.flush()
+
+ line_room_target._compute_occupies_availability()
+ line_room_source._compute_occupies_availability()
+
+ else:
+ line_room_source.room_id = target
+
+ def action_split(self):
+ for record in self:
+ for line in record.reservation_lines_to_change:
+ self.reservation_split(
+ record.reservation_id,
+ line.date,
+ line.room_id,
+ )
+
+ def action_join(self):
+ for record in self:
+ self.reservation_join(record.reservation_id, record.room_target)
+
+ def action_swap(self):
+ self.reservations_swap(
+ self.checkin, self.checkout, self.room_source.id, self.room_target.id
+ )
+
+
+class ReservationLinesToSplit(models.TransientModel):
+ _name = "pms.wizard.reservation.lines.split"
+
+ reservation_wizard_id = fields.Many2one(
+ comodel_name="pms.reservation.split.join.swap.wizard",
+ )
+ date = fields.Date(
+ string="Date",
+ )
+ room_id = fields.Many2one(
+ string="Room",
+ comodel_name="pms.room",
+ domain="[('id', 'in', allowed_room_ids)]",
+ )
+ allowed_room_ids = fields.Many2many(
+ string="Allowed Rooms",
+ help="It contains all available rooms for this line",
+ comodel_name="pms.room",
+ compute="_compute_allowed_room_ids",
+ store=True,
+ # readonly=False
+ )
+
+ @api.depends(
+ "date",
+ "room_id",
+ "reservation_wizard_id.reservation_id.pricelist_id",
+ )
+ def _compute_allowed_room_ids(self):
+ for line in self:
+ reservation = line.reservation_wizard_id.reservation_id
+ rooms_available = False
+ if line.date and reservation:
+ if reservation.overbooking or reservation.state in ("cancelled"):
+ line.allowed_room_ids = self.env["pms.room"].search(
+ [("active", "=", True)]
+ )
+ return
+ rooms_available = self.env["pms.availability.plan"].rooms_available(
+ checkin=line.date,
+ checkout=line.date + datetime.timedelta(days=1),
+ room_type_id=False, # Allows to choose any available room
+ pricelist_id=reservation.pricelist_id.id,
+ pms_property_id=reservation.pms_property_id.id,
+ )
+ rooms_available += line.room_id
+ line.allowed_room_ids = rooms_available
+ else:
+ line.allowed_room_ids = False
diff --git a/pms/wizards/wizard_split_join_swap_reservation.xml b/pms/wizards/wizard_split_join_swap_reservation.xml
new file mode 100644
index 000000000..3ad6051d7
--- /dev/null
+++ b/pms/wizards/wizard_split_join_swap_reservation.xml
@@ -0,0 +1,127 @@
+
+
+
+ Split, join or swap reservations
+ pms.reservation.split.join.swap.wizard
+
+
+
+
+