Repository navigation
[ADD] connector_pms: charge a fee per child - #171
Draft
jesusVMayor wants to merge 1 commit into
Draft
jesusVMayor wants to merge 1 commit into
jesusVMayor wants to merge 1 commit into
Conversation
Room types are priced by the adults staying in them, as that is what the occupancy of a room type counts, so children came along for free however many they were. Channel managers charge them as a fee on top of that price, and the PMS had no way to express it. A pricelist now carries the fee for each child and night of a room type, the property carries the age up to which a guest is charged as a child, and reservations keep the age of each child as the channel declares them. Those ages change nothing by themselves. The price is derived from the declared number of guests and never from the birthdate of the guests checking in, so filling the check-in data in does not reprice a reservation: nobody is going to retarify at the desk because a child had a birthday. The ages the law goes by are another matter, live in pms_l10n_es and are read from the real birthdate, untouched by this. Assisted-by: Claude Opus 5
jesusVMayor
marked this pull request as ready for review
October 9, 2026 10:15
jesusVMayor
marked this pull request as draft
October 9, 2026 10:15
Diff CoverageDiff: origin/16.0...HEAD, staged and unstaged changes
Summary
connector_pms/models/pms_pricelist_occupancy.pyLines 1-13 1 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
! 2 from odoo import _, api, fields, models
! 3 from odoo.exceptions import ValidationError
4
5
! 6 class PmsPricelistOccupancy(models.Model):
! 7 _inherit = "pms.pricelist.occupancy"
8
! 9 children_fee = fields.Float(
10 help="Charged for each child and night, on top of the price for the "
11 "adults staying in the room",
12 digits=("Product Price"),
13 )Lines 12-34 12 digits=("Product Price"),
13 )
14
15 # pylint: disable=W8110
! 16 @api.depends("children_fee")
! 17 def _compute_summary(self):
! 18 return super()._compute_summary()
19
! 20 def _summary_parts(self):
21 """Children show up in the summary as what each of them adds."""
! 22 parts = super()._summary_parts()
! 23 if self.children_fee:
! 24 parts.append(
25 _("child +%s") % self._format_modifier("amount", self.children_fee)
26 )
! 27 return parts
28
! 29 @api.constrains("children_fee")
! 30 def _check_children_fee_not_negative(self):
! 31 for record in self:
! 32 if record.children_fee < 0:
! 33 raise ValidationError(_("The children fee can't be negative."))connector_pms/models/pms_property.pyLines 1-12 1 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
! 2 from odoo import fields, models
3
4
! 5 class PmsProperty(models.Model):
! 6 _inherit = "pms.property"
7
! 8 children_max_age = fields.Integer(
9 string="Children Max. Age",
10 help="Age up to which a guest is charged as a child. This is a "
11 "commercial decision of the property and has nothing to do with the "
12 "ages the law goes by, which are read from the birthdate of the guest",connector_pms/models/pms_reservation.pyLines 14-22 14 ota_reservation_code = fields.Char(
15 string="OTA Reservation Code",
16 readonly=True,
17 )
! 18 children_ages = fields.Json(
19 help="Age of each child of the reservation, as the channel declares "
20 "them, such as [5, 13]. The price is derived from the declared number "
21 "of guests, never from this, so filling it in changes nothing by "
22 "itself",Lines 21-39 21 "of guests, never from this, so filling it in changes nothing by "
22 "itself",
23 )
24
! 25 def _occupancy_price_fields(self):
26 """Children are charged too, so changing how many there are reprices."""
! 27 return super()._occupancy_price_fields() + ["children"]
28
29 # pylint: disable=W8110
30 @api.depends("ota_reservation_code")
31 def _compute_external_reference(self):
! 32 super()._compute_external_reference()
33
34 def _get_reservation_external_reference(self):
! 35 reference = super()._get_reservation_external_reference()
36 if self.ota_reservation_code:
37 reference = self.ota_reservation_code
38 return referenceconnector_pms/models/pms_reservation_line.pyLines 1-13 1 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
! 2 from odoo import models
3
4
! 5 class PmsReservationLine(models.Model):
! 6 _inherit = "pms.reservation.line"
7
! 8 def _get_price_kwargs(self):
9 """Children are charged on top of the price for the adults."""
! 10 res = super()._get_price_kwargs()
! 11 res["children"] = self.reservation_id.children
! 12 return resconnector_pms/models/product_pricelist_item.pyLines 1-12 1 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
! 2 from odoo import models
3
4
! 5 class ProductPricelistItem(models.Model):
! 6 _inherit = "product.pricelist.item"
7
! 8 def _apply_occupancy_price(self, product, price, **kwargs):
9 """Add the fee of the children staying to the price of the room.
10
11 The price derived from the occupancy is the one for the adults, as the
12 occupancy of a room type counts them alone. Children are charged on topLines 11-26 11 The price derived from the occupancy is the one for the adults, as the
12 occupancy of a room type counts them alone. Children are charged on top
13 of it, the same way channel managers do.
14 """
! 15 price = super()._apply_occupancy_price(product, price, **kwargs)
! 16 children = kwargs.get("children")
! 17 room_type = product.room_type_id if children else False
! 18 if not room_type:
! 19 return price
! 20 occupancy_rule = room_type._get_occupancy_rule(
21 kwargs.get("pricelist") or self.pricelist_id
22 )
! 23 if not occupancy_rule.children_fee:
! 24 return price
! 25 return price + occupancy_rule.children_fee * children |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on OCA/pms#464, which adds the
pms.pricelist.occupancymodel thisextends.
connector_pmswill not install until that one is merged.Room types are priced by the adults staying in them, as that is what the
occupancy of a room type counts, so children came along for free however many
they were. Channel managers charge them as a fee on top of that price, and the
PMS had no way to express it.
What this adds
children_feeon the occupancy configuration of a pricelist, charged foreach child and night on top of the price derived for the adults.
children_max_ageon the property: the age up to which a guest is charged asa child. A commercial decision, unrelated to the ages the law goes by.
children_ageson the reservation, afields.Jsonholding the age of eachchild as the channel declares them (
[5, 13]).The ages change nothing by themselves
The price is derived from the declared number of guests and never from the
birthdate of the guests checking in, so filling the check-in data in does not
reprice a reservation: nobody is going to retarify at the desk because a child
had a birthday.
Three age thresholds now coexist and must stay apart: the legal one in
pms_l10n_es(AGE_OF_MAJORITY, and 14 for the document, read from the realbirthdate), the fiscal one of the tourist tax, and this commercial one. Only
the first two look at birthdates.
children_agesis stored for the connectors to fill in when importing, asChannex sends
rooms[].occupancy.ages. Nothing reads it yet.