diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index b42d974d0..1a0cdc2c7 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -318,6 +318,10 @@ class PmsFolio(models.Model): reservations_pending_count = fields.Integer( compute="_compute_reservations_pending_arrival" ) + max_reservation_prior = fields.Integer( + string="Max reservation priority on the entire folio", + compute="_compute_max_reservation_prior", + ) # Invoice Fields----------------------------------------------------- invoice_status = fields.Selection( [ @@ -719,6 +723,11 @@ class PmsFolio(models.Model): } record.update(vals) + def _compute_max_reservation_prior(self): + for record in self: + reservation_priors = record.reservation_ids.mapped("priority") + record.max_reservation_prior = max(reservation_priors) + # Action methods def action_pay(self): diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 8c3021c1b..ab5bc9d92 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -16,7 +16,9 @@ class PmsReservation(models.Model): _name = "pms.reservation" _description = "Reservation" _inherit = ["mail.thread", "mail.activity.mixin", "portal.mixin"] - _order = "priority desc, name" + _order = "priority desc, create_date desc, write_date desc" + # TODO: + # consider near_to_checkin & pending_notifications to order _check_company_auto = True # Default Methods ang Gets @@ -224,6 +226,15 @@ class PmsReservation(models.Model): left_for_checkin = fields.Boolean( compute="_compute_left_for_checkin", search="_search_left_for_checkin" ) + + left_for_checkout = fields.Boolean( + compute="_compute_left_for_checkout", search="_search_left_for_checkout" + ) + + left_for_cancel = fields.Boolean( + compute="_compute_left_for_cancel", search="_search_left_for_cancel" + ) + checkin_today = fields.Boolean( compute="_compute_checkin_today", search="_search_checkin_today" ) @@ -281,7 +292,11 @@ class PmsReservation(models.Model): tracking=True, help="Number of children there in guest list.", ) - to_assign = fields.Boolean("To Assign", tracking=True) + to_assign = fields.Boolean( + string="To Assign", + tracking=True, + default=True, + ) state = fields.Selection( [ ("draft", "Pre-reservation"), @@ -382,6 +397,9 @@ class PmsReservation(models.Model): partner_internal_comment = fields.Text( string="Internal Partner Notes", related="partner_id.comment" ) + + requests = fields.Text(string="Partner Requests") + folio_internal_comment = fields.Text( string="Internal Folio Notes", related="folio_id.internal_comment", @@ -505,8 +523,17 @@ class PmsReservation(models.Model): @api.depends("checkin") def _compute_priority(self): - # TODO: Logic priority (100 by example) - self.priority = 100 + for record in self: + record.priority = 0 + if any( + [ + not record.to_assign, + not record.left_for_checkin, + record.left_for_checkout, + record.state == "onboard" and record.folio_pending_amount > 0, + ] + ): + record.priority += 1 @api.depends("pricelist_id", "room_type_id") def _compute_board_service_room_id(self): @@ -772,6 +799,27 @@ class PmsReservation(models.Model): else False ) + def _compute_left_for_checkout(self): + # Reservations still pending checkout today + for record in self: + record.left_for_checkout = ( + True + if ( + record.state in ["onboard", "no_checkout"] + and record.checkout >= fields.Date.today() + ) + else False + ) + + def _compute_left_for_cancel(self): + # Reservations can be cancelled + for record in self: + record.left_for_cancel = ( + True + if (record.state not in ["cancelled", "done", "no_checkout"]) + else False + ) + def _search_left_for_checkin(self, operator, value): if operator not in ("=",): raise UserError( @@ -789,6 +837,37 @@ class PmsReservation(models.Model): ("checkin", "<=", today), ] + def _search_left_for_checkout(self, operator, value): + if operator not in ("=",): + raise UserError( + _("Invalid domain operator %s for left of checkout", operator) + ) + + if value not in (True,): + raise UserError( + _("Invalid domain right operand %s for left of checkout", value) + ) + + today = fields.Date.context_today(self) + return [ + ("state", "in", ("onboard", "no_checkout")), + ("checkout", ">=", today), + ] + + def _search_left_for_cancel(self, operator, value): + if operator not in ("=",): + raise UserError( + _("Invalid domain operator %s for left of cancel", operator) + ) + + if value not in (True,): + raise UserError( + _("Invalid domain right operand %s for left of cancel", value) + ) + return [ + ("state", "not in", ("cancelled", "done", "no_checkout")), + ] + def _compute_ready_for_checkin(self): # Reservations with hosts data enought to checkin for record in self: diff --git a/pms/tests/test_pms_folio.py b/pms/tests/test_pms_folio.py index 6e5138727..079fe2ebc 100644 --- a/pms/tests/test_pms_folio.py +++ b/pms/tests/test_pms_folio.py @@ -2,12 +2,61 @@ import datetime from freezegun import freeze_time +from odoo import fields + from .common import TestHotel freeze_time("2000-02-02") class TestPmsFolio(TestHotel): + def create_common_scenario(self): + # create a room type availability + self.room_type_availability = self.env[ + "pms.room.type.availability.plan" + ].create({"name": "Availability plan for TEST"}) + + # create a property + self.property = self.env["pms.property"].create( + { + "name": "MY PMS TEST", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + + # create room type class + self.room_type_class = self.env["pms.room.type.class"].create({"name": "Room"}) + + # create room type + self.room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.property.id], + "name": "Double Test", + "code_type": "DBL_Test", + "class_id": self.room_type_class.id, + } + ) + # create room + self.room1 = self.env["pms.room"].create( + { + "pms_property_id": self.property.id, + "name": "Double 101", + "room_type_id": self.room_type_double.id, + "capacity": 2, + } + ) + + # create room + self.room2 = self.env["pms.room"].create( + { + "pms_property_id": self.property.id, + "name": "Double 102", + "room_type_id": self.room_type_double.id, + "capacity": 2, + } + ) + def test_commission_and_partner_correct(self): # ARRANGE PmsFolio = self.env["pms.folio"] @@ -56,3 +105,33 @@ class TestPmsFolio(TestHotel): self.assertEqual( folio.agency_id, folio.partner_id, "Agency has to be the partner" ) + + def test_compute_folio_priority(self): + self.create_common_scenario() + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + r1.left_for_checkin = False + + self.env["pms.reservation"].create( + { + "folio_id": r1.folio_id.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + + self.assertEqual( + r1.priority, + r1.folio_id.max_reservation_prior, + "The max. reservation priority on the whole folio is incorrect", + ) diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py index 63ea621f5..c2cceeffe 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -548,3 +548,136 @@ class TestPmsReservations(TestHotel): "room_type_id": self.browse_ref("pms.pms_room_type_0").id, } ) + + @freeze_time("1981-11-01") + def test_order_priority_to_assign(self): + # ARRANGE + self.create_common_scenario() + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + r1.to_assign = False + # ACT + reservations = self.env["pms.reservation"].search( + [("pms_property_id", "=", self.property.id)] + ) + # ASSERT + self.assertEqual(r1, reservations[0]) + + @freeze_time("1981-11-01") + def test_order_priority_left_for_checkin(self): + # ARRANGE + self.create_common_scenario() + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + r1.left_for_checkin = False + # ACT + reservations = self.env["pms.reservation"].search( + [("pms_property_id", "=", self.property.id)] + ) + # ASSERT + self.assertEqual(r1, reservations[0]) + + @freeze_time("1981-11-01") + def test_order_priority_left_for_checkout(self): + # ARRANGE + self.create_common_scenario() + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + r1.left_for_checkout = True + # ACT + reservations = self.env["pms.reservation"].search( + [("pms_property_id", "=", self.property.id)] + ) + # ASSERT + self.assertEqual(r1, reservations[0]) + + @freeze_time("1981-11-01") + def test_order_priority_state_onboard_and_pending_amount(self): + # ARRANGE + self.create_common_scenario() + host = self.env["res.partner"].create( + { + "name": "Miguel", + "phone": "654667733", + "email": "miguel@example.com", + } + ) + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": host.id, + "pms_property_id": self.property.id, + } + ) + checkin = self.env["pms.checkin.partner"].create( + { + "partner_id": host.id, + "reservation_id": r1.id, + } + ) + checkin.action_on_board() + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "pms_property_id": self.property.id, + } + ) + # ACT + reservations = self.env["pms.reservation"].search( + [("pms_property_id", "=", self.property.id)] + ) + # ASSERT + self.assertEqual(r1, reservations[0]) diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index f37a2b8c5..606c795ab 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -531,18 +531,27 @@ - - - - - - - - - +
+
+ + + + + + + + + +
+
+ + + +
+