# Copyright 2021 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import datetime from odoo import _, api, fields, models from odoo.exceptions import ValidationError class PmsAvailability(models.Model): _name = "pms.availability" _description = "Room type availability per day" _check_pms_properties_auto = True room_type_id = fields.Many2one( string="Room Type", help="Room type for which availability is indicated", readonly=True, required=True, comodel_name="pms.room.type", ondelete="cascade", check_pms_properties=True, ) date = fields.Date( string="Date", help="Date for which availability applies", readonly=True, required=True, ) pms_property_id = fields.Many2one( string="Property", help="Property to which the availability is directed", readonly=True, required=True, comodel_name="pms.property", ondelete="restrict", check_pms_properties=True, ) reservation_line_ids = fields.One2many( string="Reservation Lines", help="They are the lines of the reservation into a reservation," "they corresponds to the nights", readonly=True, comodel_name="pms.reservation.line", inverse_name="avail_id", check_pms_properties=True, ) avail_rule_ids = fields.One2many( string="Avail record rules", comodel_name="pms.availability.plan.rule", inverse_name="avail_id", check_pms_properties=True, ) real_avail = fields.Integer( string="Real Avail", help="", store=True, readonly=True, compute="_compute_real_avail", ) parent_avail_id = fields.Many2one( string="Parent Avail", help="Parent availability for this availability", comodel_name="pms.availability", ondelete="restrict", compute="_compute_parent_avail_id", store=True, check_pms_properties=True, ) child_avail_ids = fields.One2many( string="Child Avails", help="Child availabilities for this availability", comodel_name="pms.availability", inverse_name="parent_avail_id", compute="_compute_child_avail_ids", store=True, check_pms_properties=True, ) _sql_constraints = [ ( "room_type_registry_unique", "unique(room_type_id, date, pms_property_id)", "Only can exists one availability in the same \ day for the same room type!", ) ] @api.depends( "reservation_line_ids", "reservation_line_ids.occupies_availability", "room_type_id.total_rooms_count", "parent_avail_id", "parent_avail_id.reservation_line_ids", "parent_avail_id.reservation_line_ids.occupies_availability", "child_avail_ids", "child_avail_ids.reservation_line_ids", "child_avail_ids.reservation_line_ids.occupies_availability", ) def _compute_real_avail(self): for record in self: Rooms = self.env["pms.room"] total_rooms = Rooms.search_count( [ ("room_type_id", "=", record.room_type_id.id), ("pms_property_id", "=", record.pms_property_id.id), ] ) room_ids = record.room_type_id.mapped("room_ids.id") count_rooms_not_avail = len( record.get_rooms_not_avail( checkin=record.date, checkout=record.date + datetime.timedelta(1), room_ids=room_ids, pms_property_id=record.pms_property_id.id, ) ) record.real_avail = total_rooms - count_rooms_not_avail @api.depends("reservation_line_ids", "reservation_line_ids.room_id") def _compute_parent_avail_id(self): for record in self: parent_rooms = record.room_type_id.mapped("room_ids.parent_id.id") if parent_rooms: for room_id in parent_rooms: room = self.env["pms.room"].browse(room_id) parent_avail = self.env["pms.availability"].search( [ ("date", "=", record.date), ("room_type_id", "=", room.room_type_id.id), ("pms_property_id", "=", record.pms_property_id.id), ] ) if parent_avail: record.parent_avail_id = parent_avail else: record.parent_avail_id = self.env["pms.availability"].create( { "date": record.date, "room_type_id": room.room_type_id.id, "pms_property_id": record.pms_property_id.id, } ) else: record.parent_avail_id = False @api.depends("reservation_line_ids", "reservation_line_ids.room_id") def _compute_child_avail_ids(self): for record in self: child_rooms = record.room_type_id.mapped("room_ids.child_ids.id") if child_rooms: for room_id in child_rooms: room = self.env["pms.room"].browse(room_id) child_avail = self.env["pms.availability"].search( [ ("date", "=", record.date), ("room_type_id", "=", room.room_type_id.id), ("pms_property_id", "=", record.pms_property_id.id), ] ) if child_avail: record.child_avail_ids = [(4, child_avail.id)] else: record.child_avail_ids = [ ( 0, 0, { "date": record.date, "room_type_id": room.room_type_id.id, "pms_property_id": record.pms_property_id.id, }, ) ] else: record.child_avail_ids = False @api.model def get_rooms_not_avail( self, checkin, checkout, room_ids, pms_property_id, current_lines=False ): RoomLines = self.env["pms.reservation.line"] rooms = self.env["pms.room"].browse(room_ids) occupied_room_ids = [] for room in rooms.filtered("parent_id"): if self.get_occupied_parent_rooms( room=room.parent_id, checkin=checkin, checkout=checkout, ): occupied_room_ids.append(room.id) for room in rooms.filtered("child_ids"): if self.get_occupied_child_rooms( rooms=room.child_ids, checkin=checkin, checkout=checkout, ): occupied_room_ids.append(room.id) occupied_room_ids.extend( RoomLines.search( [ ("date", ">=", checkin), ("date", "<=", checkout - datetime.timedelta(1)), ("room_id", "in", room_ids), ("pms_property_id", "=", pms_property_id), ("occupies_availability", "=", True), ("id", "not in", current_lines if current_lines else []), ] ).mapped("room_id.id") ) return occupied_room_ids @api.model def get_occupied_parent_rooms(self, room, checkin, checkout): RoomLines = self.env["pms.reservation.line"] if ( RoomLines.search_count( [ ("date", ">=", checkin), ("date", "<=", checkout - datetime.timedelta(1)), ("room_id", "=", room.id), ("occupies_availability", "=", True), ] ) > 0 ): return True if room.parent_id: return self.get_occupied_parent_rooms( room=room.parent_id, checkin=checkin, checkout=checkout ) return False @api.model def get_occupied_child_rooms(self, rooms, checkin, checkout): RoomLines = self.env["pms.reservation.line"] mapped_properties = list(set(rooms.mapped("pms_property_id.id"))) if len(mapped_properties) > 1: raise ValidationError(_("Rooms shared between different properties")) if ( RoomLines.search_count( [ ("date", ">=", checkin), ("date", "<=", checkout - datetime.timedelta(1)), ("room_id", "in", rooms.ids), ("occupies_availability", "=", True), ] ) > 0 ): return True for room in rooms.filtered("child_ids"): if self.get_occupied_child_rooms( rooms=room.child_ids, checkin=checkin, checkout=checkout, ): return True return False @api.constrains( "room_type_id", "pms_property_id", ) def _check_property_integrity(self): for rec in self: if rec.pms_property_id and rec.room_type_id: if ( rec.room_type_id.pms_property_ids.ids and rec.pms_property_id.id not in rec.room_type_id.pms_property_ids.ids ): raise ValidationError( _("Property not allowed on availability day compute") )