diff --git a/pms/README.rst b/pms/README.rst
index e1d5d560ac..fa40b9e9b8 100644
--- a/pms/README.rst
+++ b/pms/README.rst
@@ -56,6 +56,22 @@ You will find the hotel settings in PMS Management > Configuration > Properties
This module required additional configuration for company, accounting, invoicing and user privileges.
+Children and room capacity
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default children take no place in the room, so only the adults are checked
+against its capacity. Properties that want to count them can set the system
+parameter ``pms.children_occupy_capacity`` to ``True``, in Settings > Technical
+> System Parameters.
+
+Once it is on, children take the regular places of the room first and only
+overflow into its children places, the ones where only a child can sleep, such
+as child bunk beds. Adults never occupy those, so a room with free children
+places can still be full.
+
+Mind that turning it on refuses reservations that the property may have been
+taking until then.
+
Usage
=====
diff --git a/pms/__manifest__.py b/pms/__manifest__.py
index 8f3e7ce0ed..65587c7ab4 100644
--- a/pms/__manifest__.py
+++ b/pms/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "PMS (Property Management System)",
"summary": "A property management system",
- "version": "16.0.4.24.0",
+ "version": "16.0.4.26.0",
"development_status": "Beta",
"category": "Generic Modules/Property Management System",
"website": "https://github.com/OCA/pms",
diff --git a/pms/demo/pms_master_data_no_update.xml b/pms/demo/pms_master_data_no_update.xml
index 8fe01cd73e..134cfd2b16 100644
--- a/pms/demo/pms_master_data_no_update.xml
+++ b/pms/demo/pms_master_data_no_update.xml
@@ -326,6 +326,7 @@
1
+ 1
@@ -333,6 +334,7 @@
1
+ 1
@@ -340,6 +342,7 @@
1
+ 1
@@ -347,6 +350,7 @@
1
+ 1
@@ -354,6 +358,7 @@
1
+ 1
diff --git a/pms/migrations/16.0.4.25.0/pre-migration.py b/pms/migrations/16.0.4.25.0/pre-migration.py
new file mode 100644
index 0000000000..f90dc667b0
--- /dev/null
+++ b/pms/migrations/16.0.4.25.0/pre-migration.py
@@ -0,0 +1,21 @@
+# Copyright 2026 Commit [Sun]
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from openupgradelib import openupgrade
+
+# children_occupying held the children taking a place in the room, apart from
+# children, which held them all. Nothing kept both in sync, so a reservation
+# could declare an occupying child without counting it in children. Those
+# children are folded into children before the column goes away, or they would
+# be lost.
+_FOLD_CHILDREN_OCCUPYING = """
+ UPDATE pms_reservation
+ SET children = children_occupying
+ WHERE children_occupying > COALESCE(children, 0)
+"""
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ if openupgrade.column_exists(env.cr, "pms_reservation", "children_occupying"):
+ openupgrade.logged_query(env.cr, _FOLD_CHILDREN_OCCUPYING)
+ openupgrade.drop_columns(env.cr, [("pms_reservation", "children_occupying")])
diff --git a/pms/models/__init__.py b/pms/models/__init__.py
index adf6e66a5b..aa0a90b3c8 100644
--- a/pms/models/__init__.py
+++ b/pms/models/__init__.py
@@ -27,6 +27,7 @@
from . import pms_checkin_partner
from . import product_pricelist
from . import product_pricelist_item
+from . import pms_pricelist_occupancy
from . import res_partner
from . import pms_sale_channel
from . import mail_compose_message
diff --git a/pms/models/pms_pricelist_occupancy.py b/pms/models/pms_pricelist_occupancy.py
new file mode 100644
index 0000000000..4a0e43dfc3
--- /dev/null
+++ b/pms/models/pms_pricelist_occupancy.py
@@ -0,0 +1,136 @@
+# Copyright 2026 Commit [Sun]
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class PmsPricelistOccupancy(models.Model):
+ _name = "pms.pricelist.occupancy"
+ _description = "Occupancy price modifiers of a pricelist for a room type"
+ _check_pms_properties_auto = True
+
+ pricelist_id = fields.Many2one(
+ string="Pricelist",
+ help="Pricelist these modifiers belong to",
+ comodel_name="product.pricelist",
+ required=True,
+ index=True,
+ ondelete="cascade",
+ check_pms_properties=True,
+ )
+ room_type_id = fields.Many2one(
+ string="Room Type",
+ help="Room type these modifiers apply to",
+ comodel_name="pms.room.type",
+ required=True,
+ index=True,
+ ondelete="cascade",
+ check_pms_properties=True,
+ )
+ pms_property_ids = fields.Many2many(
+ string="Properties",
+ help="Properties of the room type these modifiers apply to",
+ comodel_name="pms.property",
+ related="room_type_id.pms_property_ids",
+ )
+ increase_mode = fields.Selection(
+ help="How to charge each guest above the default occupancy",
+ selection=[("percent", "Percentage"), ("amount", "Fixed amount")],
+ default="percent",
+ required=True,
+ )
+ increase_value = fields.Float(
+ string="Increase",
+ help="Charged for each guest above the default occupancy of the room "
+ "type. Zero to charge the same price no matter how many they are",
+ digits=("Product Price"),
+ )
+ decrease_mode = fields.Selection(
+ help="How to discount each guest below the default occupancy",
+ selection=[("percent", "Percentage"), ("amount", "Fixed amount")],
+ default="percent",
+ required=True,
+ )
+ decrease_value = fields.Float(
+ string="Decrease",
+ help="Discounted for each guest below the default occupancy of the "
+ "room type. Zero to charge the same price no matter how many they are",
+ digits=("Product Price"),
+ )
+ summary = fields.Char(
+ string="Per Guest",
+ help="What each guest of difference changes in the price",
+ compute="_compute_summary",
+ )
+
+ _sql_constraints = [
+ (
+ "pricelist_room_type_uniq",
+ "unique(pricelist_id, room_type_id)",
+ "A room type can only have one occupancy configuration per " "pricelist.",
+ )
+ ]
+
+ @api.depends("increase_mode", "increase_value", "decrease_mode", "decrease_value")
+ def _compute_summary(self):
+ """A one-liner for the list, so the form holds the detail."""
+ for record in self:
+ parts = record._summary_parts()
+ record.summary = " / ".join(parts) if parts else _("Same price")
+
+ def _summary_parts(self):
+ """The pieces of the summary, one per modifier that is set."""
+ self.ensure_one()
+ parts = []
+ if self.increase_value:
+ modifier = self._format_modifier(self.increase_mode, self.increase_value)
+ parts.append(f"+{modifier}")
+ if self.decrease_value:
+ modifier = self._format_modifier(self.decrease_mode, self.decrease_value)
+ parts.append(f"-{modifier}")
+ return parts
+
+ def _format_modifier(self, mode, value):
+ self.ensure_one()
+ if mode == "percent":
+ return f"{value:g}%"
+ return f"{value:g} {self.pricelist_id.currency_id.symbol or ''}"
+
+ @api.constrains("increase_value", "decrease_value")
+ def _check_values_not_negative(self):
+ """The direction is given by the field, so the value is an amount.
+
+ A negative increase would silently discount, and the other way round.
+ """
+ for record in self:
+ if record.increase_value < 0 or record.decrease_value < 0:
+ raise ValidationError(
+ _("Occupancy increase and decrease can't be negative.")
+ )
+
+ def _apply(self, price, difference):
+ """Price for an occupancy differing by `difference` guests.
+
+ The modifier is linear: it applies once per guest of difference, which
+ is what channel managers derive from a default occupancy as well.
+ """
+ self.ensure_one()
+ if difference > 0:
+ mode, value, guests, sign = (
+ self.increase_mode,
+ self.increase_value,
+ difference,
+ 1,
+ )
+ else:
+ mode, value, guests, sign = (
+ self.decrease_mode,
+ self.decrease_value,
+ -difference,
+ -1,
+ )
+ if not value:
+ return price
+ if mode == "percent":
+ return price * (1 + sign * value * guests / 100)
+ return price + sign * value * guests
diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py
index 8a93d2df36..bda6cca9c7 100644
--- a/pms/models/pms_reservation.py
+++ b/pms/models/pms_reservation.py
@@ -356,12 +356,9 @@ class PmsReservation(models.Model):
compute="_compute_adults",
tracking=True,
)
- children_occupying = fields.Integer(
- help="Number of children there in guest list whose presence counts",
- )
children = fields.Integer(
- help="Number total of children there in guest list,"
- "whose presence counts or not",
+ help="Number of children there in guest list. They take the regular "
+ "places of the room first, and only overflow into its children places",
readonly=False,
tracking=True,
)
@@ -1031,7 +1028,9 @@ def _compute_user_id(self):
else:
res.user_id = active_user_id or res.pms_property_id.user_id
- @api.depends("pricelist_id", "room_type_id")
+ @api.depends(
+ lambda self: ["pricelist_id", "room_type_id", *self._occupancy_price_fields()]
+ )
def _compute_show_update_pricelist(self):
for reservation in self:
if (
@@ -1044,6 +1043,10 @@ def _compute_show_update_pricelist(self):
reservation.room_type_id
and reservation._origin.room_type_id != reservation.room_type_id
)
+ or any(
+ reservation._origin[field] != reservation[field]
+ for field in reservation._occupancy_price_fields()
+ )
):
reservation.show_update_pricelist = True
else:
@@ -2215,9 +2218,10 @@ def write(self, vals):
for record in self:
record._check_services(vals)
- # Only check if adult to avoid to check capacity in intermediate states
- # (p.e. flush) that not take access to possible extra beds service in vals
- if "adults" in vals:
+ # Only check on occupancy changes to avoid checking capacity in
+ # intermediate states (p.e. flush) that not take access to possible
+ # extra beds service in vals
+ if "adults" in vals or "children" in vals:
record._check_capacity()
# The room of the lines is recomputed from the preferred room, so
# that write does not go through pms.reservation.line.write and the
@@ -2311,6 +2315,17 @@ def get_services_to_update_channel(self, vals):
services_to_update_channel += service
return services_to_update_channel
+ def _occupancy_price_fields(self):
+ """Fields the price of a night is derived from, besides the pricelist
+ and the room type.
+
+ Changing them leaves the prices of the nights outdated, the same as
+ changing the pricelist or the room type does: the nights keep the price
+ they have and the reservation flags it, so that they are updated on
+ purpose. Modules pricing on more than the adults staying add their own.
+ """
+ return ["adults"]
+
def update_prices(self):
self.ensure_one()
for line in self.reservation_line_ids:
diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py
index faa7b44a02..1c3b15493a 100644
--- a/pms/models/pms_reservation_line.py
+++ b/pms/models/pms_reservation_line.py
@@ -231,7 +231,6 @@ def _get_pricelist_price(self):
qty = 1.0
uom = product.uom_id
currency = self.currency_id or self.order_id.company_id.currency_id
- consumption_date = self.date
price = pricelist_rule._compute_consumption_price(
product,
@@ -239,12 +238,25 @@ def _get_pricelist_price(self):
uom,
order_date,
currency=currency,
- consumption_date=consumption_date,
- pms_property_id=self.pms_property_id.id,
+ **self._get_price_kwargs(),
)
return price
+ def _get_price_kwargs(self):
+ """What the pricelist resolves the price of this night with.
+
+ Kept apart so that modules pricing on more than the adults staying can
+ add to it without repeating how a night is priced.
+ """
+ self.ensure_one()
+ return {
+ "consumption_date": self.date,
+ "pms_property_id": self.pms_property_id.id,
+ "occupancy": self.reservation_id.adults,
+ "pricelist": self.reservation_id.pricelist_id,
+ }
+
def _get_product_price_context(self):
"""Gives the context for product price computation.
@@ -685,23 +697,31 @@ def _check_room_capacity(self):
of the room (capacity + allowed extra beds) instead of the extra beds
actually sold, to avoid false positives in intermediate states where
the extra bed service is not created yet.
+
+ Children count too: they take regular places first and only overflow
+ into the children places of the room.
"""
if self.env.context.get("avoid_capacity_check"):
return
for record in self:
reservation = record.reservation_id
- if not record.room_id or reservation.reservation_type == "out":
+ room = record.room_id
+ if not room or reservation.reservation_type == "out":
continue
- occupancy = reservation.adults + reservation.children_occupying
- max_capacity = record.room_id.capacity + record.room_id.extra_beds_allowed
- if occupancy > max_capacity:
+ if not room.fits_occupancy(
+ reservation.adults, reservation.children, room.extra_beds_allowed
+ ):
raise ValidationError(
_(
- "The room %(room)s can't hold %(occupancy)s guests "
- "(maximum capacity: %(capacity)s) (%(reservation)s)",
- room=record.room_id.display_name,
- occupancy=occupancy,
- capacity=max_capacity,
+ "The room %(room)s can't hold %(adults)s adults and "
+ "%(children)s children (capacity: %(capacity)s, "
+ "children places: %(children_capacity)s) "
+ "(%(reservation)s)",
+ room=room.display_name,
+ adults=reservation.adults,
+ children=reservation.children,
+ capacity=room.capacity + room.extra_beds_allowed,
+ children_capacity=room.children_capacity,
reservation=reservation.name,
)
)
diff --git a/pms/models/pms_room.py b/pms/models/pms_room.py
index d4502935a2..cb7ec0ed1e 100644
--- a/pms/models/pms_room.py
+++ b/pms/models/pms_room.py
@@ -4,6 +4,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
+from odoo.tools import str2bool
class PmsRoom(models.Model):
@@ -77,7 +78,14 @@ class PmsRoom(models.Model):
check_pms_properties=True,
)
capacity = fields.Integer(
- help="The maximum number of people that can occupy a room"
+ help="Number of places where anyone can sleep, adults or children"
+ )
+ children_capacity = fields.Integer(
+ help="Number of places where only children can sleep, such as child "
+ "bunk beds. Adults never occupy them, children occupy them only once "
+ "the regular capacity is full",
+ required=True,
+ default=0,
)
extra_beds_allowed = fields.Integer(
help="Number of extra beds allowed in room",
@@ -314,16 +322,15 @@ def _check_adults(self, reservation, service_line_ids=False):
and x.product_id.is_extra_bed is True
)
num_extra_beds = sum(extra_beds.mapped("day_qty")) if extra_beds else 0
- if line.room_id:
- if (
- reservation.adults + reservation.children_occupying
- ) > line.room_id.get_capacity(num_extra_beds):
- raise ValidationError(
- _(
- "Persons can't be higher than room capacity (%s)",
- reservation.name,
- )
+ if line.room_id and not line.room_id.fits_occupancy(
+ reservation.adults, reservation.children, num_extra_beds
+ ):
+ raise ValidationError(
+ _(
+ "Persons can't be higher than room capacity (%s)",
+ reservation.name,
)
+ )
@api.constrains("short_name")
def _check_short_name(self):
@@ -430,3 +437,36 @@ def get_capacity(self, extra_bed=0):
_("Extra beds can't be greater than allowed beds for this room")
)
return record.capacity + extra_bed
+
+ @api.model
+ def _children_occupy_capacity(self):
+ """Whether children take a place in the room.
+
+ Off unless the property turns it on, which is how it worked until
+ now: counting them refuses reservations that properties have been
+ taking, so it is theirs to decide and not something an upgrade does.
+ """
+ return str2bool(
+ self.env["ir.config_parameter"]
+ .sudo()
+ .get_param("pms.children_occupy_capacity"),
+ default=False,
+ )
+
+ def fits_occupancy(self, adults, children=0, extra_bed=0):
+ """Whether the room can hold the given guests.
+
+ Children sleep in the regular capacity as well, and only overflow
+ into 'children_capacity' once it is full. Adults never occupy those
+ places, so a room with free children places can still be full.
+
+ Whether children take a place at all is up to the property, see
+ _children_occupy_capacity.
+ """
+ self.ensure_one()
+ capacity = self.get_capacity(extra_bed)
+ if adults > capacity:
+ return False
+ if not self._children_occupy_capacity():
+ return True
+ return adults + children <= capacity + self.children_capacity
diff --git a/pms/models/pms_room_type.py b/pms/models/pms_room_type.py
index fba64f3cb1..bf5a7c225d 100644
--- a/pms/models/pms_room_type.py
+++ b/pms/models/pms_room_type.py
@@ -73,6 +73,22 @@ class PmsRoomType(models.Model):
compute="_compute_total_rooms_count",
store=True,
)
+ max_occupancy = fields.Integer(
+ string="Max. Occupancy",
+ help="Occupancy the room type guarantees: the smallest capacity among "
+ "its rooms, as any of them can be the one assigned. Zero when the "
+ "room type has no rooms yet",
+ compute="_compute_max_occupancy",
+ store=True,
+ )
+ default_occupancy = fields.Integer(
+ help="Occupancy the price of this room type is given for. Prices for "
+ "a different number of adults are derived from it. Zero to price the "
+ "room type regardless of its occupancy",
+ compute="_compute_default_occupancy",
+ store=True,
+ readonly=False,
+ )
default_max_avail = fields.Integer(
string="Default Max. Availability",
help="Maximum simultaneous availability on own Booking Engine "
@@ -119,6 +135,52 @@ def _compute_total_rooms_count(self):
for record in self:
record.total_rooms_count = len(record.room_ids)
+ @api.depends("room_ids", "room_ids.active", "room_ids.capacity")
+ def _compute_max_occupancy(self):
+ """The smallest capacity among the rooms of the type.
+
+ The smallest and not the largest one: any room of the type can be the
+ one assigned, so a larger capacity is not guaranteed and selling it
+ would end up in an overbooking.
+ """
+ for record in self:
+ capacities = record.room_ids.mapped("capacity")
+ record.max_occupancy = min(capacities) if capacities else 0
+
+ @api.depends("max_occupancy")
+ def _compute_default_occupancy(self):
+ """Keep the occupancy the price is given for within reach of the rooms.
+
+ It defaults to the maximum occupancy and follows it down when a smaller
+ room joins the type, as otherwise adding that room would be refused. A
+ room type priced for fewer guests than it holds keeps its own value.
+ """
+ for record in self:
+ default_occupancy = record.default_occupancy or record.max_occupancy
+ if record.max_occupancy:
+ default_occupancy = min(default_occupancy, record.max_occupancy)
+ record.default_occupancy = default_occupancy
+
+ @api.constrains("default_occupancy")
+ def _check_default_occupancy(self):
+ """A room type cannot be priced for more guests than it guarantees.
+
+ Only on the value itself, never on max_occupancy: a smaller room
+ joining the type brings the default down (see
+ _compute_default_occupancy) and adding that room must not be refused.
+ Room types with no rooms are left alone, as they come before them.
+ """
+ for record in self:
+ if record.max_occupancy and record.default_occupancy > record.max_occupancy:
+ raise ValidationError(
+ _(
+ "The default occupancy of %(room_type)s can't be "
+ "higher than the occupancy it guarantees (%(max)s)",
+ room_type=record.display_name,
+ max=record.max_occupancy,
+ )
+ )
+
@api.model
def get_room_types_by_property(self, pms_property_id, default_code=None):
"""
@@ -216,6 +278,38 @@ def get_room_type_capacity(self, pms_property_id):
).mapped("capacity")
return min(capacities) if any(capacities) else 0
+ def _get_occupancy_rule(self, pricelist):
+ """The occupancy modifiers this pricelist holds for the room type."""
+ self.ensure_one()
+ if not pricelist:
+ return self.env["pms.pricelist.occupancy"]
+ return self.env["pms.pricelist.occupancy"].search(
+ [
+ ("pricelist_id", "=", pricelist.id),
+ ("room_type_id", "=", self.id),
+ ],
+ limit=1,
+ )
+
+ def _get_occupancy_price(self, price, occupancy, pricelist):
+ """Derive the price of the room type for a given number of adults.
+
+ The price of a pricelist is the one for the default occupancy of the
+ room type, and the configured modifiers derive the rest from it. Room
+ types with no default occupancy, or pricelists with no modifiers for
+ them, are priced the same no matter how many guests stay.
+ """
+ self.ensure_one()
+ if not occupancy or not self.default_occupancy:
+ return price
+ difference = occupancy - self.default_occupancy
+ if not difference:
+ return price
+ occupancy_rule = self._get_occupancy_rule(pricelist)
+ if not occupancy_rule:
+ return price
+ return max(occupancy_rule._apply(price, difference), 0)
+
@api.model
def get_default_board_service(self, pms_property_id, room_type_id, pricelist_id):
board_services_candidates = self.env["pms.board.service.room.type"].search(
diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py
index b78c346163..764c24326f 100644
--- a/pms/models/product_pricelist.py
+++ b/pms/models/product_pricelist.py
@@ -71,6 +71,14 @@ class ProductPricelist(models.Model):
help="If the pricelist is available in the PMS",
default=False,
)
+ occupancy_ids = fields.One2many(
+ string="Occupancy Prices",
+ help="How the price of each room type changes with the number of "
+ "adults staying in it",
+ comodel_name="pms.pricelist.occupancy",
+ inverse_name="pricelist_id",
+ check_pms_properties=True,
+ )
def _get_applicable_rules_domain(self, products, date, **kwargs):
domain = super()._get_applicable_rules_domain(products, date, **kwargs)
diff --git a/pms/models/product_pricelist_item.py b/pms/models/product_pricelist_item.py
index d3b02c2266..d303419234 100644
--- a/pms/models/product_pricelist_item.py
+++ b/pms/models/product_pricelist_item.py
@@ -218,7 +218,21 @@ def convert(p):
product, quantity, uom, date, currency, **kwargs
)
- return price
+ return self._apply_occupancy_price(product, price, **kwargs)
+
+ def _apply_occupancy_price(self, product, price, **kwargs):
+ """Derive the price for the occupancy asked for, if any.
+
+ The price of the rule is the one for the default occupancy of the room
+ type. Callers that do not ask for an occupancy get that price, so this
+ is transparent to anything pricing a room type without guests in hand.
+ """
+ occupancy = kwargs.get("occupancy")
+ room_type = product.room_type_id if occupancy else False
+ if not room_type:
+ return price
+ pricelist = kwargs.get("pricelist") or self.pricelist_id
+ return room_type._get_occupancy_price(price, occupancy, pricelist)
def _compute_base_consumption_price(
self, product, quantity, uom, date, target_currency, **kwargs
diff --git a/pms/readme/CONFIGURE.rst b/pms/readme/CONFIGURE.rst
index 940574e837..52c20657da 100644
--- a/pms/readme/CONFIGURE.rst
+++ b/pms/readme/CONFIGURE.rst
@@ -1,3 +1,19 @@
You will find the hotel settings in PMS Management > Configuration > Properties > Your Property.
This module required additional configuration for company, accounting, invoicing and user privileges.
+
+Children and room capacity
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default children take no place in the room, so only the adults are checked
+against its capacity. Properties that want to count them can set the system
+parameter ``pms.children_occupy_capacity`` to ``True``, in Settings > Technical
+> System Parameters.
+
+Once it is on, children take the regular places of the room first and only
+overflow into its children places, the ones where only a child can sleep, such
+as child bunk beds. Adults never occupy those, so a room with free children
+places can still be full.
+
+Mind that turning it on refuses reservations that the property may have been
+taking until then.
diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv
index 3f9e79aa4a..a8be9bc455 100644
--- a/pms/security/ir.model.access.csv
+++ b/pms/security/ir.model.access.csv
@@ -49,6 +49,7 @@ manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pm
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
manager_access_pms_team_member,manager_access_pms_team_member,model_pms_team_member,pms.group_pms_manager,1,1,1,1
+manager_access_pms_pricelist_occupancy,manager_access_pms_pricelist_occupancy,model_pms_pricelist_occupancy,pms.group_pms_manager,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
@@ -68,3 +69,4 @@ user_access_pms_precheckin_portal,user_access_pms_precheckin_portal,model_pms_ch
user_access_pms_booking_duplicate,user_access_pms_booking_duplicate,model_pms_booking_duplicate,pms.group_pms_user,1,1,1,1
user_access_pms_reservation_duplicate,user_access_pms_reservation_duplicate,model_pms_reservation_duplicate,pms.group_pms_user,1,1,1,1
user_access_ir_pms_property,user_access_ir_pms_property,model_ir_pms_property,pms.group_pms_user,1,1,1,1
+user_access_pms_pricelist_occupancy,user_access_pms_pricelist_occupancy,model_pms_pricelist_occupancy,pms.group_pms_user,1,0,0,0
diff --git a/pms/static/description/index.html b/pms/static/description/index.html
index 584e80f0d6..daa00b91fd 100644
--- a/pms/static/description/index.html
+++ b/pms/static/description/index.html
@@ -383,13 +383,16 @@
By default children take no place in the room, so only the adults are checked
+against its capacity. Properties that want to count them can set the system
+parameter pms.children_occupy_capacity to True, in Settings > Technical
+> System Parameters.
+
Once it is on, children take the regular places of the room first and only
+overflow into its children places, the ones where only a child can sleep, such
+as child bunk beds. Adults never occupy those, so a room with free children
+places can still be full.
+
Mind that turning it on refuses reservations that the property may have been
+taking until then.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -417,15 +433,15 @@