Merge pull request #19 from commitsun/14.0-availability-plans

14.0 availability plans
This commit is contained in:
Darío Lodeiros
2020-12-15 10:48:32 +01:00
committed by GitHub
25 changed files with 1039 additions and 283 deletions

View File

@@ -29,7 +29,7 @@ This module is an all-in-one property management system (PMS) focused on medium-
for managing every aspect of your property's daily operations.
You can manage hotel properties with multi-hotel and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and restriction plans among other hotel functionalities.
reservations, check-in, daily reports, board services, rate and availability plans among other hotel functionalities.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.

View File

@@ -52,8 +52,8 @@
"views/account_move_views.xml",
"views/res_users_views.xml",
"views/pms_room_type_class_views.xml",
"views/pms_room_type_restriction_views.xml",
"views/pms_room_type_restriction_item_views.xml",
"views/pms_room_type_availability_views.xml",
"views/pms_room_type_availability_rule_views.xml",
"views/pms_service_views.xml",
"views/pms_service_line_views.xml",
"views/pms_shared_room_views.xml",

View File

@@ -2,14 +2,17 @@
<odoo>
<data noupdate="1">
<!-- Basic pms -->
<record id="main_pms_room_type_restriction" model="pms.room.type.restriction">
<field name="name">Restriction Plan</field>
<record id="main_pms_room_type_availability" model="pms.room.type.availability">
<field name="name">Availability Plan</field>
</record>
<record id="main_pms_property" model="pms.property">
<field name="name">My Property</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="default_restriction_id" ref="main_pms_room_type_restriction" />
<field
name="default_availability_id"
ref="main_pms_room_type_availability"
/>
<field name="street">Rua Street Demo, s/n</field>
<field name="city">Commitsun city</field>
<field name="country_id" ref="base.es" />

View File

@@ -298,14 +298,17 @@
</field>
</record>
<!-- Multi pms Demo -->
<record id="demo_pms_room_type_restriction" model="pms.room.type.restriction">
<field name="name">Restriction Plan Demo</field>
<record id="demo_pms_room_type_availability" model="pms.room.type.availability">
<field name="name">Availability Plan Demo</field>
</record>
<record id="demo_pms_property" model="pms.property">
<field name="name">My pms Demo</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="default_restriction_id" ref="demo_pms_room_type_restriction" />
<field
name="default_availability_id"
ref="demo_pms_room_type_availability"
/>
</record>
<!-- pms.room.type -->
<record id="demo_pms_room_type_0" model="pms.room.type">

View File

@@ -24,8 +24,7 @@ from . import product_template
from . import res_company
from . import account_payment
from . import pms_room_type_availability
from . import pms_room_type_restriction
from . import pms_room_type_restriction_item
from . import pms_room_type_availability_rule
from . import pms_reservation_line
from . import pms_checkin_partner
from . import product_pricelist

View File

@@ -41,11 +41,11 @@ class PmsProperty(models.Model):
required=True,
help="The default pricelist used in this property.",
)
default_restriction_id = fields.Many2one(
"pms.room.type.restriction",
"Restriction Plan",
default_availability_id = fields.Many2one(
"pms.room.type.availability",
"Availability Plan",
required=True,
help="The default restriction plan used in this property.",
help="The default availability plan used in this property.",
)
default_arrival_hour = fields.Char(
"Arrival Hour (GMT)", help="HH:mm Format", default="14:00"

View File

@@ -507,7 +507,11 @@ class PmsReservation(models.Model):
)
@api.depends(
"reservation_line_ids.date", "overbooking", "state", "preferred_room_id"
"reservation_line_ids.date",
"overbooking",
"state",
"preferred_room_id",
"pricelist_id",
)
def _compute_allowed_room_ids(self):
for reservation in self:
@@ -524,6 +528,7 @@ class PmsReservation(models.Model):
checkout=reservation.checkout,
room_type_id=False, # Allow chosen any available room
current_lines=reservation.reservation_line_ids.ids,
pricelist=reservation.pricelist_id.id,
)
reservation.allowed_room_ids = rooms_available
@@ -1102,6 +1107,7 @@ class PmsReservation(models.Model):
checkin=self.checkin,
checkout=self.checkout,
current_lines=self.reservation_line_ids.ids,
pricelist=self.pricelist_id.id,
)
# REVIEW: check capacity room
return {
@@ -1184,6 +1190,7 @@ class PmsReservation(models.Model):
checkin=self.checkin,
checkout=self.checkout,
room_type_id=self.room_type_id.id or False,
pricelist=self.pricelist_id.id,
)
if rooms_available:
room_chosen = rooms_available[0]

View File

@@ -80,6 +80,12 @@ class PmsReservationLine(models.Model):
store=True,
help="This record is taken into account to calculate availability",
)
impacts_quota = fields.Integer(
string="Impacts quota",
compute="_compute_impact_quota",
store=True,
readonly=False,
)
_sql_constraints = [
(
@@ -105,14 +111,14 @@ class PmsReservationLine(models.Model):
rooms_available = self.env[
"pms.room.type.availability"
].rooms_available(
checkin=reservation.checkin,
checkout=reservation.checkout,
checkin=line.reservation_id.checkin,
checkout=line.reservation_id.checkout,
room_type_id=reservation.room_type_id.id
if not free_room_select
else False,
current_lines=line._origin.reservation_id.reservation_line_ids.ids,
pricelist=line.reservation_id.pricelist_id.id,
)
# if there is availability for the entire stay
if rooms_available:
@@ -134,9 +140,20 @@ class PmsReservationLine(models.Model):
# available for the entire stay
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(
checkin=line.reservation_id.checkin,
checkout=line.reservation_id.checkout,
room_type_id=line.reservation_id.room_type_id.id,
current_lines=line._origin.reservation_id.reservation_line_ids.ids,
pricelist=line.reservation_id.pricelist_id,
):
raise ValidationError(
_("%s: No room type available")
% (line.reservation_id.room_type_id.name)
)
# if there is no availability for the entire stay without
# changing rooms (we assume a split reservation)
# the reservation can be allocated into several rooms
else:
rooms_ranking = dict()
@@ -173,12 +190,8 @@ class PmsReservationLine(models.Model):
if room.id not in rooms_ranking
else rooms_ranking[room.id] + 1
)
if len(rooms_ranking) == 0:
raise ValidationError(
_("%s: No room type available")
% (reservation.room_type_id.name)
)
else:
if len(rooms_ranking) > 0:
# we get the best score in the ranking
best = max(rooms_ranking.values())
@@ -218,6 +231,17 @@ class PmsReservationLine(models.Model):
# no matter what it is
line.room_id = list(bests.keys())[0]
@api.depends("reservation_id.room_type_id", "reservation_id.pricelist_id")
def _compute_impact_quota(self):
for line in self:
reservation = line.reservation_id
line.impacts_quota = self.env["pms.room.type.availability"].update_quota(
pricelist_id=reservation.pricelist_id,
room_type_id=reservation.room_type_id,
date=line.date,
line=line,
)
@api.depends(
"reservation_id",
"reservation_id.pricelist_id",

View File

@@ -1,92 +1,193 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# Copyright 2018 Pablo Quesada
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
import datetime
from odoo import api, fields, models
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"
_description = "Availability"
_inherit = "mail.thread"
_description = "Reservation availability plan"
# Default methods
@api.model
def _default_max_avail(self):
return self.room_type_id.default_max_avail
@api.model
def _default_quota(self):
return self.room_type_id.default_quota
def _get_default_pms_property(self):
return self.env.user.pms_property_id or None
# Fields declaration
room_type_id = fields.Many2one(
"pms.room.type", "Room Type", required=True, ondelete="cascade"
)
date = fields.Date(
"Date",
required=True,
tracking=True,
)
quota = fields.Integer(
"Quota",
default=_default_quota,
tracking=True,
help="Generic Quota assigned.",
)
max_avail = fields.Integer(
"Max. Availability",
default=-1,
readonly=True,
tracking=True,
help="Maximum simultaneous availability on own Booking Engine.",
)
no_web = fields.Boolean(
"No Web",
default=False,
tracking=True,
help="Set zero availability to the own Booking Engine "
"even when the availability is positive,",
name = fields.Char("Availability Plan Name", required=True)
pms_property_id = fields.Many2one(
comodel_name="pms.property",
string="Property",
ondelete="restrict",
)
_sql_constraints = [
(
"unique_availability_room_type_rule_date",
"unique(room_type_id, date)",
"The availability rule for this date in this room type already exists, "
"modify it instead of trying to create a new one",
),
]
pms_pricelist_ids = fields.One2many(
comodel_name="product.pricelist",
inverse_name="availability_id",
string="Pricelists",
required=False,
ondelete="restrict",
)
item_ids = fields.One2many(
comodel_name="pms.room.type.availability.rule",
inverse_name="availability_id",
string="Rule Items",
copy=True,
)
active = fields.Boolean(
string="Active",
default=True,
help="If unchecked, it will allow you to hide the "
"Availability plan without removing it.",
)
# Business Methods
@classmethod
def any_rule_applies(cls, checkin, checkout, item):
reservation_len = (checkout - checkin).days
return any(
[
(0 < item.max_stay < reservation_len),
(0 < item.min_stay > reservation_len),
(0 < item.max_stay_arrival < reservation_len and checkin == item.date),
(0 < item.min_stay_arrival > reservation_len and checkin == item.date),
item.closed,
(item.closed_arrival and checkin == item.date),
(item.closed_departure and checkout == item.date),
(item.quota == 0 or item.max_avail == 0),
]
)
@api.model
def rooms_available(
self, checkin, checkout, room_type_id=False, current_lines=False
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pricelist=False,
):
domain = self._get_domain_reservations_occupation(
dfrom=checkin,
dto=checkout - timedelta(1),
current_lines=current_lines,
)
reservation_lines = self.env["pms.reservation.line"].search(domain)
reservations_rooms = reservation_lines.mapped("room_id.id")
free_rooms = self.env["pms.room"].search([("id", "not in", reservations_rooms)])
if room_type_id:
rooms_linked = (
self.env["pms.room.type"].search([("id", "=", room_type_id)]).room_ids
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
rooms_not_avail = (
self.env["pms.reservation.line"]
.search(
[
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
("occupies_availability", "=", True),
("id", "not in", current_lines if current_lines else []),
]
)
free_rooms = free_rooms & rooms_linked
.mapped("room_id.id")
)
domain_rooms = [
("id", "not in", rooms_not_avail if len(rooms_not_avail) > 0 else [])
]
domain_rules = [
("date", ">=", checkin),
("date", "<=", checkout),
]
if room_type_id:
domain_rooms.append(("room_type_id", "=", room_type_id))
domain_rules.append(("room_type_id", "=", room_type_id))
free_rooms = self.env["pms.room"].search(domain_rooms)
if pricelist:
domain_rules.append(("availability_id.pms_pricelist_ids", "=", pricelist))
rule_items = self.env["pms.room.type.availability.rule"].search(
domain_rules
)
if len(rule_items) > 0:
room_types_to_remove = []
for item in rule_items:
if self.any_rule_applies(checkin, checkout, item):
room_types_to_remove.append(item.room_type_id.id)
free_rooms = free_rooms.filtered(
lambda x: x.room_type_id.id not in room_types_to_remove
)
return free_rooms.sorted(key=lambda r: r.sequence)
@api.model
def _get_domain_reservations_occupation(self, dfrom, dto, current_lines=False):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
domain = [
("date", ">=", dfrom),
("date", "<=", dto),
("occupies_availability", "=", True),
("id", "not in", current_lines),
]
return domain
def splitted_availability(
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pricelist=False,
):
for date_iterator in [
checkin + datetime.timedelta(days=x)
for x in range(0, (checkout - checkin).days)
]:
rooms_avail = self.rooms_available(
checkin=date_iterator,
checkout=date_iterator + datetime.timedelta(1),
room_type_id=room_type_id,
current_lines=current_lines,
pricelist=pricelist.id,
)
if len(rooms_avail) < 1:
return False
return True
@api.model
def update_quota(self, pricelist_id, room_type_id, date, line):
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),
("room_type_id", "=", room_type_id.id),
("date", "=", date),
]
)
# applies a rule
if rule:
rule.ensure_one()
if rule and rule.quota != -1 and rule.quota > 0:
# the line has no rule item applied before
if not line.impacts_quota:
rule.quota -= 1
return rule.id
# the line has a rule item applied before
elif line.impacts_quota != rule.id:
# decrement quota on current rule item
rule.quota -= 1
# check old rule item
old_rule = self.env["pms.room.type.availability.rule"].search(
[("id", "=", line.impacts_quota)]
)
# restore quota in old rule item
if old_rule:
old_rule.quota += 1
return rule.id
# in any case, check old rule item
if line.impacts_quota:
old_rule = self.env["pms.room.type.availability.rule"].search(
[("id", "=", line.impacts_quota)]
)
# and restore quota in old rule item
if old_rule:
old_rule.quota += 1
return False

View File

@@ -0,0 +1,116 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsRoomTypeAvailabilityRule(models.Model):
_name = "pms.room.type.availability.rule"
_description = "Reservation rule by day"
# Field Declarations
availability_id = fields.Many2one(
comodel_name="pms.room.type.availability",
string="Availability Plan",
ondelete="cascade",
index=True,
)
room_type_id = fields.Many2one(
comodel_name="pms.room.type",
string="Room Type",
required=True,
ondelete="cascade",
)
date = fields.Date(string="Date")
min_stay = fields.Integer(
string="Min. Stay",
default=0,
)
min_stay_arrival = fields.Integer(
string="Min. Stay Arrival",
default=0,
)
max_stay = fields.Integer(
string="Max. Stay",
default=0,
)
max_stay_arrival = fields.Integer(
string="Max. Stay Arrival",
default=0,
)
closed = fields.Boolean(
string="Closed",
default=False,
)
closed_departure = fields.Boolean(
string="Closed Departure",
default=False,
)
closed_arrival = fields.Boolean(
string="Closed Arrival",
default=False,
)
quota = fields.Integer(
string="Quota",
store=True,
readonly=False,
compute="_compute_quota",
help="Generic Quota assigned.",
)
max_avail = fields.Integer(
string="Max. Availability",
store=True,
readonly=False,
compute="_compute_max_avail",
help="Maximum simultaneous availability on own Booking Engine.",
)
_sql_constraints = [
(
"room_type_registry_unique",
"unique(availability_id, room_type_id, date)",
"Only can exists one availability rule in the same \
day for the same room type!",
)
]
@api.depends("room_type_id")
def _compute_quota(self):
for record in self:
if not record.quota:
record.quota = record.room_type_id.default_quota
@api.depends("room_type_id")
def _compute_max_avail(self):
for record in self:
if not record.max_avail:
record.max_avail = record.room_type_id.default_max_avail
@api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival")
def _check_min_max_stay(self):
for record in self:
if record.min_stay < 0:
raise ValidationError(_("Min. Stay can't be less than zero"))
elif record.min_stay_arrival < 0:
raise ValidationError(_("Min. Stay Arrival can't be less than zero"))
elif record.max_stay < 0:
raise ValidationError(_("Max. Stay can't be less than zero"))
elif record.max_stay_arrival < 0:
raise ValidationError(_("Max. Stay Arrival can't be less than zero"))
elif (
record.min_stay != 0
and record.max_stay != 0
and record.min_stay > record.max_stay
):
raise ValidationError(_("Max. Stay can't be less than Min. Stay"))
elif (
record.min_stay_arrival != 0
and record.max_stay_arrival != 0
and record.min_stay_arrival > record.max_stay_arrival
):
raise ValidationError(
_("Max. Stay Arrival can't be less than Min. Stay Arrival")
)

View File

@@ -1,37 +0,0 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class PmsRoomTypeRestriction(models.Model):
"""The room type restriction is used as a daily restriction plan for room types
and therefore is related only with one property."""
_name = "pms.room.type.restriction"
_description = "Reservation restriction plan"
# Default methods
@api.model
def _get_default_pms_property(self):
return self.env.user.pms_property_id or None
# Fields declaration
name = fields.Char("Restriction Plan Name", required=True)
pms_property_id = fields.Many2one(
"pms.property",
"Property",
ondelete="restrict",
default=_get_default_pms_property,
)
item_ids = fields.One2many(
"pms.room.type.restriction.item",
"restriction_id",
string="Restriction Items",
copy=True,
)
active = fields.Boolean(
"Active",
default=True,
help="If unchecked, it will allow you to hide the "
"restriction plan without removing it.",
)

View File

@@ -1,49 +0,0 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsRoomTypeRestrictionItem(models.Model):
_name = "pms.room.type.restriction.item"
_description = "Reservation restriction by day"
# Field Declarations
restriction_id = fields.Many2one(
"pms.room.type.restriction", "Restriction Plan", ondelete="cascade", index=True
)
room_type_id = fields.Many2one(
"pms.room.type", "Room Type", required=True, ondelete="cascade"
)
date = fields.Date("Date")
min_stay = fields.Integer("Min. Stay")
min_stay_arrival = fields.Integer("Min. Stay Arrival")
max_stay = fields.Integer("Max. Stay")
max_stay_arrival = fields.Integer("Max. Stay Arrival")
closed = fields.Boolean("Closed")
closed_departure = fields.Boolean("Closed Departure")
closed_arrival = fields.Boolean("Closed Arrival")
_sql_constraints = [
(
"room_type_registry_unique",
"unique(restriction_id, room_type_id, date)",
"Only can exists one restriction in the same \
day for the same room type!",
)
]
# Constraints and onchanges
@api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival")
def _check_min_stay(self):
for record in self:
if record.min_stay < 0:
raise ValidationError(_("Min. Stay can't be less than zero"))
elif record.min_stay_arrival < 0:
raise ValidationError(_("Min. Stay Arrival can't be less than zero"))
elif record.max_stay < 0:
raise ValidationError(_("Max. Stay can't be less than zero"))
elif record.max_stay_arrival < 0:
raise ValidationError(_("Max. Stay Arrival can't be less than zero"))

View File

@@ -22,6 +22,12 @@ class ProductPricelist(models.Model):
[("daily", "Daily Plan")], string="Pricelist Type", default="daily"
)
availability_id = fields.Many2one(
comodel_name="pms.room.type.availability",
string="Availability Plan",
ondelete="restrict",
)
# Constraints and onchanges
# @api.constrains("pricelist_type", "pms_property_ids")
# def _check_pricelist_type_property_ids(self):

View File

@@ -2,4 +2,4 @@ This module is an all-in-one property management system (PMS) focused on medium-
for managing every aspect of your property's daily operations.
You can manage hotel properties with multi-hotel and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and restriction plans among other hotel functionalities.
reservations, check-in, daily reports, board services, rate and availability plans among other hotel functionalities.

View File

@@ -3,7 +3,6 @@ user_access_pms_floor,user_access_pms_floor,model_pms_floor,pms.group_pms_user,1
user_access_pms_amenity,user_access_pms_amenity,model_pms_amenity,pms.group_pms_user,1,0,0,0
user_access_pms_amenity_type,user_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_user,1,0,0,0
user_access_pms_service,user_access_pms_service,model_pms_service,pms.group_pms_user,1,1,1,1
user_access_pms_room_type_restriction,user_access_pms_room_type_restriction,model_pms_room_type_restriction,pms.group_pms_user,1,0,0,0
user_access_pms_reservation_line,user_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_user,1,1,1,1
user_access_room_closure_reason,user_access_room_closure_reason,model_room_closure_reason,pms.group_pms_user,1,0,0,0
user_access_pms_service_line,user_access_pms_service_line,model_pms_service_line,pms.group_pms_user,1,1,1,1
@@ -12,7 +11,7 @@ user_access_pms_checkin_partner,user_access_pms_checkin_partner,model_pms_checki
user_access_pms_room_type_class,user_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_user,1,0,0,0
user_access_pms_room,user_access_pms_room,model_pms_room,pms.group_pms_user,1,0,0,0
user_access_shared_pms_room,user_access_pms_shared_room,model_pms_shared_room,pms.group_pms_user,1,0,0,0
user_access_pms_room_type_restriction_item,user_access_pms_room_type_restriction_item,model_pms_room_type_restriction_item,pms.group_pms_user,1,0,0,0
user_access_pms_room_type_availability_rule,user_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_user,1,0,0,0
user_access_pms_reservation,user_access_pms_reservation,model_pms_reservation,pms.group_pms_user,1,1,1,1
user_access_pms_folio,user_access_pms_folio,model_pms_folio,pms.group_pms_user,1,1,1,1
user_access_pms_room_type,user_access_pms_room_type,model_pms_room_type,pms.group_pms_user,1,0,0,0
@@ -29,7 +28,6 @@ manager_access_pms_floor,manager_access_pms_floor,model_pms_floor,pms.group_pms_
manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1
manager_access_pms_amenity_type,manager_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_manager,1,1,1,1
manager_access_pms_service,manager_access_pms_service,model_pms_service,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_restriction,manager_access_pms_room_type_restriction,model_pms_room_type_restriction,pms.group_pms_manager,1,1,1,1
manager_access_pms_reservation_line,manager_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_manager,1,1,1,1
manager_access_room_closure_reason,manager_access_room_closure_reason,model_room_closure_reason,pms.group_pms_manager,1,1,1,1
manager_access_pms_service_line,manager_access_pms_service_line,model_pms_service_line,pms.group_pms_manager,1,1,1,1
@@ -38,7 +36,7 @@ manager_access_pms_checkin_partner,manager_access_pms_checkin_partner,model_pms_
manager_access_pms_room_type_class,manager_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_manager,1,1,1,1
manager_access_pms_room,manager_access_pms_room,model_pms_room,pms.group_pms_manager,1,1,1,1
manager_access_pms_shared_room,manager_access_pms_shared_room,model_pms_shared_room,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_restriction_item,manager_access_pms_room_type_restriction_item,model_pms_room_type_restriction_item,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_availability_rule,manager_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_manager,1,1,1,1
manager_access_pms_reservation,manager_access_pms_reservation,model_pms_reservation,pms.group_pms_manager,1,1,1,1
manager_access_pms_folio,manager_access_pms_folio,model_pms_folio,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type,manager_access_pms_room_type,model_pms_room_type,pms.group_pms_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 user_access_pms_amenity user_access_pms_amenity model_pms_amenity pms.group_pms_user 1 0 0 0
4 user_access_pms_amenity_type user_access_pms_amenity_type model_pms_amenity_type pms.group_pms_user 1 0 0 0
5 user_access_pms_service user_access_pms_service model_pms_service pms.group_pms_user 1 1 1 1
user_access_pms_room_type_restriction user_access_pms_room_type_restriction model_pms_room_type_restriction pms.group_pms_user 1 0 0 0
6 user_access_pms_reservation_line user_access_pms_reservation_line model_pms_reservation_line pms.group_pms_user 1 1 1 1
7 user_access_room_closure_reason user_access_room_closure_reason model_room_closure_reason pms.group_pms_user 1 0 0 0
8 user_access_pms_service_line user_access_pms_service_line model_pms_service_line pms.group_pms_user 1 1 1 1
11 user_access_pms_room_type_class user_access_pms_room_type_class model_pms_room_type_class pms.group_pms_user 1 0 0 0
12 user_access_pms_room user_access_pms_room model_pms_room pms.group_pms_user 1 0 0 0
13 user_access_shared_pms_room user_access_pms_shared_room model_pms_shared_room pms.group_pms_user 1 0 0 0
14 user_access_pms_room_type_restriction_item user_access_pms_room_type_availability_rule user_access_pms_room_type_restriction_item user_access_pms_room_type_availability_rule model_pms_room_type_restriction_item model_pms_room_type_availability_rule pms.group_pms_user 1 0 0 0
15 user_access_pms_reservation user_access_pms_reservation model_pms_reservation pms.group_pms_user 1 1 1 1
16 user_access_pms_folio user_access_pms_folio model_pms_folio pms.group_pms_user 1 1 1 1
17 user_access_pms_room_type user_access_pms_room_type model_pms_room_type pms.group_pms_user 1 0 0 0
28 manager_access_pms_amenity manager_access_pms_amenity model_pms_amenity pms.group_pms_manager 1 1 1 1
29 manager_access_pms_amenity_type manager_access_pms_amenity_type model_pms_amenity_type pms.group_pms_manager 1 1 1 1
30 manager_access_pms_service manager_access_pms_service model_pms_service pms.group_pms_manager 1 1 1 1
manager_access_pms_room_type_restriction manager_access_pms_room_type_restriction model_pms_room_type_restriction pms.group_pms_manager 1 1 1 1
31 manager_access_pms_reservation_line manager_access_pms_reservation_line model_pms_reservation_line pms.group_pms_manager 1 1 1 1
32 manager_access_room_closure_reason manager_access_room_closure_reason model_room_closure_reason pms.group_pms_manager 1 1 1 1
33 manager_access_pms_service_line manager_access_pms_service_line model_pms_service_line pms.group_pms_manager 1 1 1 1
36 manager_access_pms_room_type_class manager_access_pms_room_type_class model_pms_room_type_class pms.group_pms_manager 1 1 1 1
37 manager_access_pms_room manager_access_pms_room model_pms_room pms.group_pms_manager 1 1 1 1
38 manager_access_pms_shared_room manager_access_pms_shared_room model_pms_shared_room pms.group_pms_manager 1 1 1 1
39 manager_access_pms_room_type_restriction_item manager_access_pms_room_type_availability_rule manager_access_pms_room_type_restriction_item manager_access_pms_room_type_availability_rule model_pms_room_type_restriction_item model_pms_room_type_availability_rule pms.group_pms_manager 1 1 1 1
40 manager_access_pms_reservation manager_access_pms_reservation model_pms_reservation pms.group_pms_manager 1 1 1 1
41 manager_access_pms_folio manager_access_pms_folio model_pms_folio pms.group_pms_manager 1 1 1 1
42 manager_access_pms_room_type manager_access_pms_room_type model_pms_room_type pms.group_pms_manager 1 1 1 1

View File

@@ -371,7 +371,7 @@ ul.auto-toc {
<p>This module is an all-in-one property management system (PMS) focused on medium-sized hotels
for managing every aspect of your propertys daily operations.</p>
<p>You can manage hotel properties with multi-hotel and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and restriction plans among other hotel functionalities.</p>
reservations, check-in, daily reports, board services, rate and availability plans among other hotel functionalities.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.

View File

@@ -25,4 +25,5 @@ from . import test_pms_pricelist_priority
from . import test_pms_checkin_partner
from . import test_pms_sale_channel
from . import test_pms_folio
from . import test_pms_room_type
from . import test_pms_room_type_availability_rules
from . import test_pms_room_type

View File

@@ -15,19 +15,19 @@ class TestPmsPricelistRules(common.TransactionCase):
{"name": "Category1"}
)
self.restriction = self.env["pms.room.type.restriction"].create(
{"name": "Restriction1"}
self.availability = self.env["pms.room.type.availability"].create(
{"name": "Availability 1"}
)
self.restriction2 = self.env["pms.room.type.restriction"].create(
{"name": "Restriction2"}
self.availability2 = self.env["pms.room.type.availability"].create(
{"name": "Availability"}
)
self.property1 = self.env["pms.property"].create(
{
"name": "Property_1",
"company_id": self.env.ref("base.main_company").id,
"default_pricelist_id": self.env.ref("product.list0").id,
"default_restriction_id": self.restriction.id,
"default_availability_id": self.availability.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_restriction_id": self.restriction2.id,
"default_availability_id": self.availability2.id,
}
)

View File

@@ -11,9 +11,9 @@ from .common import TestHotel
@freeze_time("2012-01-14")
class TestPmsReservations(TestHotel):
def create_common_scenario(self):
# create a room type restriction
self.room_type_restriction = self.env["pms.room.type.restriction"].create(
{"name": "Restriction plan for TEST"}
# create a room type availability
self.room_type_availability = self.env["pms.room.type.availability"].create(
{"name": "Availability plan for TEST"}
)
# create a property
@@ -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_restriction_id": self.room_type_restriction.id,
"default_availability_id": self.room_type_availability.id,
}
)

View File

@@ -0,0 +1,568 @@
import datetime
from freezegun import freeze_time
from odoo import fields
from odoo.exceptions import ValidationError
from .common import TestHotel
@freeze_time("1980-01-01")
class TestPmsRoomTypeAvailabilityRules(TestHotel):
def create_common_scenario(self):
# product.pricelist
self.test_pricelist1 = self.env["product.pricelist"].create(
{
"name": "test pricelist 1",
}
)
# pms.room.type.availability
self.test_room_type_availability1 = self.env[
"pms.room.type.availability"
].create(
{
"name": "Availability plan for TEST",
"pms_pricelist_ids": [(6, 0, [self.test_pricelist1.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_pricelist1.id,
"default_availability_id": self.test_room_type_availability1.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,
}
)
# pms.room
self.test_room1_double = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Double 201 test",
"room_type_id": self.test_room_type_double.id,
"capacity": 2,
}
)
# pms.room
self.test_room2_double = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Double 202 test",
"room_type_id": self.test_room_type_double.id,
"capacity": 2,
}
)
# pms.room
# self.test_room3_double = self.env["pms.room"].create(
# {
# "pms_property_id": self.test_property.id,
# "name": "Double 203 test",
# "room_type_id": self.test_room_type_double.id,
# "capacity": 2,
# }
# )
# # pms.room
# self.test_room4_double = self.env["pms.room"].create(
# {
# "pms_property_id": self.test_property.id,
# "name": "Double 204 test",
# "room_type_id": self.test_room_type_double.id,
# "capacity": 2,
# }
# )
# pms.room
self.test_room1_single = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Single 101 test",
"room_type_id": self.test_room_type_single.id,
"capacity": 1,
}
)
# pms.room
self.test_room2_single = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Single 102 test",
"room_type_id": self.test_room_type_single.id,
"capacity": 1,
}
)
def test_availability_rooms_all(self):
# TEST CASE
# get availability withouth rules
# ARRANGE
self.create_common_scenario()
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_rooms_double_rooms = self.env["pms.room"].search(
[("pms_property_id", "=", self.test_property.id)]
)
# ACT
result = self.env["pms.room.type.availability"].rooms_available(
checkin=checkin,
checkout=checkout,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_rooms_all_lines(self):
# TEST CASE
# get availability withouth rules
# given reservation lines to not consider
# ARRANGE
self.create_common_scenario()
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_rooms_double_rooms = self.env["pms.room"].search(
[("pms_property_id", "=", self.test_property.id)]
)
test_reservation = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin,
"checkout": checkout,
}
)
# ACT
result = self.env["pms.room.type.availability"].rooms_available(
checkin=checkin,
checkout=checkout,
current_lines=test_reservation.reservation_line_ids.ids,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_rooms_room_type(self):
# TEST CASE
# get availability withouth rules
# given a room type
# ARRANGE
self.create_common_scenario()
test_rooms_double_rooms = self.env["pms.room"].search(
[
("pms_property_id", "=", self.test_property.id),
("room_type_id", "=", self.test_room_type_double.id),
]
)
# ACT
result = self.env["pms.room.type.availability"].rooms_available(
checkin=fields.date.today(),
checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(),
room_type_id=self.test_room_type_double.id,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_closed_no_room_type(self):
# TEST CASE:
# coverage for 2 points:
# 1. without room type, availability rules associated
# with the pricelist are applied
# 2. availability rule "closed" is taken into account
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability1_item1 = self.env[
"pms.room.type.availability.rule"
].create(
{
"availability_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(
checkin=fields.date.today(),
checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(),
# room_type_id=False, # <- (2/2)
pricelist=self.test_pricelist1.id,
)
# ASSERT
self.assertNotIn(
self.test_room_type_double,
result.mapped("room_type_id"),
"Availability should not contain rooms of a type "
"which its availability rules applies",
)
def test_availability_rules(self):
# TEST CASE
# the availability should take into acount availability rules:
# closed_arrival, closed_departure, min_stay, max_stay,
# min_stay_arrival, max_stay_arrival
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability1_item1 = self.env[
"pms.room.type.availability.rule"
].create(
{
"availability_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(),
}
)
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_cases = [
{
"closed": False,
"closed_arrival": True,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": True,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkout,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 5,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 2,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 5,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 3,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": 0,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": 0,
"date": checkin,
},
]
for test_case in test_cases:
with self.subTest(k=test_case):
# ACT
self.test_room_type_availability1_item1.write(test_case)
result = self.env["pms.room.type.availability"].rooms_available(
checkin=checkin,
checkout=checkout,
room_type_id=self.test_room_type_double.id,
pricelist=self.test_pricelist1.id,
)
# ASSERT
self.assertNotIn(
self.test_room_type_double,
result.mapped("room_type_id"),
"Availability should not contain rooms of a type "
"which its availability rules applies",
)
@freeze_time("1980-11-01")
def test_rule_on_create_reservation(self):
# TEST CASE
# an availability rule should be applied that would prevent the
# creation of reservations
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability1_item1 = self.env[
"pms.room.type.availability.rule"
].create(
{
"availability_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,
}
)
checkin = datetime.datetime.now()
checkout = datetime.datetime.now() + datetime.timedelta(days=4)
# ACT & ASSERT
with self.assertRaises(
ValidationError,
msg="Availability rules should be applied that would"
" prevent the creation of the reservation.",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin,
"checkout": checkout,
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
}
)
@freeze_time("1980-11-01")
def test_rules_on_create_splitted_reservation(self):
# TEST CASE
# an availability rule should be applied that would prevent the
# creation of reservations including splitted reservations.
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability1_item1 = self.env[
"pms.room.type.availability.rule"
].create(
{
"availability_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,
}
)
checkin_test = datetime.datetime.now()
checkout_test = datetime.datetime.now() + datetime.timedelta(days=4)
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,
"room_type_id": self.test_room_type_double.id,
"preferred_room_id": self.test_room1_double.id,
}
)
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=4),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"preferred_room_id": self.test_room2_double.id,
}
)
# ACT & ASSERT
with self.assertRaises(
ValidationError,
msg="Availability rule should be applied that would"
" prevent the creation of splitted reservation.",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin_test,
"checkout": checkout_test,
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
}
)
@freeze_time("1980-11-01")
def test_rule_update_quota_on_create_reservation(self):
# TEST CASE
# quota rule is changed after creating a reservation
# with pricelist linked to a availability plan that applies
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability1_item1 = self.env[
"pms.room.type.availability.rule"
].create(
{
"availability_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": 1,
}
)
r1 = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
}
)
r1.flush()
with self.assertRaises(
ValidationError,
msg="The quota shouldnt be enough to create a new reservation",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
}
)
@freeze_time("1980-11-01")
def test_rule_update_quota_on_update_reservation(self):
# TEST CASE
# quota rule is restored after creating a reservation
# with pricelist linked to a availability rule that applies
# and then modify the pricelist of the reservation and
# no rules applies
# ARRANGE
self.create_common_scenario()
test_quota = 2
test_pricelist2 = self.env["product.pricelist"].create(
{
"name": "test pricelist 2",
}
)
rule = self.env["pms.room.type.availability.rule"].create(
{
"availability_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": test_quota,
}
)
reservation = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
}
)
# ACT
reservation.pricelist_id = test_pricelist2.id
reservation.flush()
self.assertEqual(
test_quota,
rule.quota,
"The quota should be restored after changing the reservation's pricelist",
)

View File

@@ -21,10 +21,10 @@
<group
colspan="4"
col="4"
string="Price and Restriction Plans"
string="Price and Availability Plans"
>
<field name="default_pricelist_id" required="True" />
<field name="default_restriction_id" required="True" />
<field name="default_availability_id" required="True" />
</group>
<group string="Timezone">
<field name="tz" widget="timezone_mismatch" />

View File

@@ -1,21 +1,26 @@
<?xml version="1.0" ?>
<odoo>
<record id="room_type_restriction_item_view_form" model="ir.ui.view">
<field name="name">pms.room.type.restriction.item.form</field>
<field name="model">pms.room.type.restriction.item</field>
<record id="room_type_availability_rule_view_form" model="ir.ui.view">
<field name="name">pms.room.type.availability.rule.form</field>
<field name="model">pms.room.type.availability.rule</field>
<field name="arch" type="xml">
<form string="Restrictions">
<form string="Availability Plans">
<group>
<field name="room_type_id" required="True" />
</group>
<group>
<field name="date" />
</group>
<group>
<group>
<field name="min_stay" />
<field name="min_stay_arrival" />
</group>
<group>
<field name="max_stay" />
<field name="max_stay_arrival" />
</group>
<group>
<field name="quota" />
<field name="max_avail" />
</group>
<group>
<field name="closed" />
@@ -26,11 +31,11 @@
</form>
</field>
</record>
<record id="room_type_restriction_item_view_tree" model="ir.ui.view">
<field name="name">pms.room.type.restriction.item.tree</field>
<field name="model">pms.room.type.restriction.item</field>
<record id="room_type_availability_rule_view_tree" model="ir.ui.view">
<field name="name">pms.room.type.availability.rule.tree</field>
<field name="model">pms.room.type.availability.rule</field>
<field name="arch" type="xml">
<tree string="Restrictions">
<tree string="Availability rules">
<field name="room_type_id" />
<field name="date" />
<field name="min_stay" />

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" ?>
<odoo>
<record id="room_type_availability_view_form" model="ir.ui.view">
<field name="name">pms.room.type.availability.form</field>
<field name="model">pms.room.type.availability</field>
<field name="arch" type="xml">
<form string="Rules">
<sheet>
<div class="oe_button_box" name="button_box">
<button
name="toggle_active"
type="object"
class="oe_stat_button"
icon="fa-archive"
>
<field
name="active"
widget="boolean_button"
options='{"terminology": "archive"}'
/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Name" />
<h1>
<field name="name" />
</h1>
</div>
<group>
<field
name="pms_property_id"
options="{'no_create': True,'no_open': True}"
/>
<field
name="pms_pricelist_ids"
widget="many2many_tags"
options="{'no_create': True,'no_open': True}"
/>
</group>
<separator string="Availability Rules" />
<field name="item_ids" nolabel="1">
<tree string="Availability Rules">
<field name="room_type_id" />
<field name="date" />
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="room_type_availability_view_tree" model="ir.ui.view">
<field name="name">pms.room.type.availability.tree</field>
<field name="model">pms.room.type.availability</field>
<field name="arch" type="xml">
<tree string="Availability Plans">
<field name="name" />
<field name="pms_property_id" />
<field
name="pms_pricelist_ids"
widget="many2many_tags"
options="{'no_create': True,'no_open': True}"
/>
<field name="active" />
</tree>
</field>
</record>
<!-- Action of reservation availability plan-->
<record model="ir.actions.act_window" id="room_type_availability_action">
<field name="name">Reservation Availability Plans</field>
<field name="res_model">pms.room.type.availability</field>
<field name="view_mode">tree,form</field>
</record>
<!-- MENUS -->
<menuitem
name="Availability Plans"
id="reservation_availability_rules_menu"
action="room_type_availability_action"
sequence="22"
parent="pms.configuration_others"
/>
</odoo>

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" ?>
<odoo>
<record id="room_type_restriction_view_form" model="ir.ui.view">
<field name="name">pms.room.type.restriction.form</field>
<field name="model">pms.room.type.restriction</field>
<field name="arch" type="xml">
<form string="Restrictions">
<sheet>
<div class="oe_button_box" name="button_box">
<button
name="toggle_active"
type="object"
class="oe_stat_button"
icon="fa-archive"
>
<field
name="active"
widget="boolean_button"
options='{"terminology": "archive"}'
/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Name" />
<h1>
<field name="name" />
</h1>
</div>
<div>
<field
name="pms_property_id"
options="{'no_create': True,'no_open': True}"
/>
<separator string="Restriction Items" />
<field name="item_ids" nolabel="1">
<tree string="Restriction Items">
<field name="room_type_id" />
<field name="date" />
<field name="min_stay" />
<field name="closed" />
</tree>
</field>
</div>
</sheet>
</form>
</field>
</record>
<record id="room_type_restriction_view_tree" model="ir.ui.view">
<field name="name">pms.room.type.restriction.tree</field>
<field name="model">pms.room.type.restriction</field>
<field name="arch" type="xml">
<tree string="Restrictions">
<field name="name" />
<field name="active" />
</tree>
</field>
</record>
<!-- Action of reservation restriction -->
<record model="ir.actions.act_window" id="room_type_restriction_action">
<field name="name">Reservation restrictions</field>
<field name="res_model">pms.room.type.restriction</field>
<field name="view_mode">tree,form</field>
</record>
<!-- MENUS -->
<menuitem
name="Restrictions"
id="reservation_restriction_menu"
action="room_type_restriction_action"
sequence="22"
parent="pms.configuration_others"
/>
</odoo>

View File

@@ -12,6 +12,8 @@
/>
<field name="pricelist_type" />
<field name="cancelation_rule_id" />
<field name="availability_id" />
</xpath>
<xpath
expr="//field[@name='item_ids']/tree/field[@name='base']"