Skip to content

[FIX] pms_api_rest, connector_pms_wubook: bill board service via pricelist, not raw amount#79

Open
DarioLodeiros wants to merge 1 commit into
16.0from
16.0-fix-board-service-package-price-mismatch
Open

[FIX] pms_api_rest, connector_pms_wubook: bill board service via pricelist, not raw amount#79
DarioLodeiros wants to merge 1 commit into
16.0from
16.0-fix-board-service-package-price-mismatch

Conversation

@DarioLodeiros
Copy link
Copy Markdown
Member

Summary

When an external channel (OTA via /folios POST, Wubook connector) sends a reservation with a package price (room + board) plus a boardServiceId, the room price is computed by subtracting the per-adult/children board service amount from the package. The previous code subtracted pms.board.service.room.type.line.amount × (adults|children) directly, but the auto-computed pms.service.line bills the board service through the pricelist + fiscal-position tax path. When these two computations disagree (different pricelist override, intracommunity fiscal position mapping a price-include tax to a non-include one, etc), the reservation line ends up under-priced by the gap and the folio total falls below what the channel actually sent.

The fix routes the API-side computation through the same pricing path the service line will use, so the split is always consistent.

Changes

  • New helper pms.board.service.room.type._get_billed_day_price(...) in pms_api_rest/models/pms_board_service_room_type.py. Mirrors pms.service.line._get_price_unit_line: pricelist _get_product_price with board_service_line_id + consumption_date context, then _fix_tax_included_price_company against the fiscal-position-mapped taxes.
  • pms_api_rest/services/pms_folio_service.py: replaces the three buggy callsites that subtracted the raw amount (POST /folios, the update-reservations flow, and wrapper_reservation_lines). The per-day amount is now computed via the helper and threaded as a dict by date.
  • connector_pms_wubook/models/pms_reservation_line/mapper_import.py: same swap for the Wubook import mapper.
  • Unit tests for the helper: zero composition, adults-only path, and the children-line skip.

Test plan

  • docker-compose run --rm odoo odoo --test-tags /pms_api_rest -d devel --no-http -u pms_api_rest — 13 tests, 0 failures
  • Manually reproduced the original bug payload against staging (demo-alda): folio total post-fix matches the package price exactly.
  • Smoke-tested against production (transaction rollback): folio amount_total = 59.9 € for the original failing payload, matching what the OTA sent.

…elist, not raw amount

When an external channel posts a reservation with a package price
(room + board) plus a boardServiceId, the room price was computed by
subtracting `pms.board.service.room.type.line.amount * (adults|children)`
from the package. But the auto-computed `pms.service.line` actually
bills the board service through the pricelist + fiscal-position tax
path, so the two diverge whenever the line amount does not match the
pricelist-derived price (different pricelist override, intracommunity
fiscal position mapping a price-include tax to a non-include one, etc).
Result: the reservation line ends up under-priced by the gap, leaving
the folio total below what the channel sent.

Centralize the per-day billed price computation in a new helper
`pms.board.service.room.type._get_billed_day_price`, mirroring
`pms.service.line._get_price_unit_line`, and call it from the three
buggy callsites (POST /folios, the update flow, and the wubook
connector mapper).
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Diff Coverage

Diff: origin/16.0...HEAD, staged and unstaged changes

  • connector_pms_wubook/models/pms_reservation_line/mapper_import.py (0.0%): Missing lines 58,70
  • pms_api_rest/models/pms_board_service_room_type.py (95.0%): Missing lines 38
  • pms_api_rest/services/pms_folio_service.py (0.0%): Missing lines 986,994-995,2571,2577,2588-2589,2604,2611-2612,2656,2659

Summary

  • Total: 34 lines
  • Missing: 15 lines
  • Coverage: 55%

connector_pms_wubook/models/pms_reservation_line/mapper_import.py

Lines 54-62

  54         if record["board"] and record["board_included"]:
  55             board_service_room = get_board_service_room_type(
  56                 self, room_type, record["board"], pricelist_id
  57             )
! 58             board_day_price = board_service_room._get_billed_day_price(
  59                 pricelist=self.env["product.pricelist"].browse(pricelist_id),
  60                 consumption_date=record["day"],
  61                 pms_property_id=self.backend_record.pms_property_id.id,
  62                 adults=record["occupancy"],

Lines 66-72

  66                 if agency
  67                 else False,
  68                 company=self.backend_record.pms_property_id.company_id,
  69             )
! 70             price -= board_day_price
  71         return {"price": price}

pms_api_rest/models/pms_board_service_room_type.py

Lines 34-42

  34         ):
  35             if bsl.adults and adults:
  36                 qty = adults
  37             elif bsl.children and children:
! 38                 qty = children
  39             else:
  40                 continue
  41             raw_price = pricelist._get_product_price(
  42                 product=bsl.product_id.with_context(

pms_api_rest/services/pms_folio_service.py

Lines 982-990

  982                     # package (room + board). Compute the board portion
  983                     # per date with the same pricing logic the auto-
  984                     # computed pms.service.line will use, so we can
  985                     # subtract it cleanly and leave the room price.
! 986                     board_day_prices = {}
  987                     if external_app and vals.get("board_service_room_id"):
  988                         board = (
  989                             self.env["pms.board.service.room.type"]
  990                             .sudo()

Lines 990-999

  990                             .sudo()
  991                             .browse(vals["board_service_room_id"])
  992                         )
  993                         pms_api_check_access(user=self.env.user, records=board)
! 994                         for rline in reservation.reservationLines:
! 995                             board_day_prices[rline.date] = board._get_billed_day_price(
  996                                 pricelist=folio.pricelist_id,
  997                                 consumption_date=rline.date,
  998                                 pms_property_id=folio.pms_property_id.id,
  999                                 adults=reservation.adults,

Lines 2567-2575

  2567                 new_res or proposed_reservation.state in ["draft", "confirm"]
  2568             ):
  2569                 # The service price is included in day price
  2570                 # when it is a board service (external api)
! 2571                 board_day_prices = {}
  2572                 if external_app:
  2573                     # if proposed reservation and not board_service_room_id in vals,
  2574                     # get board_day_price from the already-billed board services
  2575                     # of the proposed reservation (same value for every day).

Lines 2573-2581

  2573                     # if proposed reservation and not board_service_room_id in vals,
  2574                     # get board_day_price from the already-billed board services
  2575                     # of the proposed reservation (same value for every day).
  2576                     if proposed_reservation and not vals.get("board_service_room_id"):
! 2577                         avg = sum(
  2578                             proposed_reservation.service_ids.filtered(
  2579                                 lambda s: s.is_board_service
  2580                             ).mapped("price_total")
  2581                         ) / (

Lines 2584-2593

  2584                                 - proposed_reservation.checkin
  2585                             ).days
  2586                             or 1
  2587                         )
! 2588                         for rline in info_reservation.reservationLines:
! 2589                             board_day_prices[rline.date] = avg
  2590                     else:
  2591                         board = (
  2592                             self.env["pms.board.service.room.type"]
  2593                             .sudo()

Lines 2600-2608

  2600                                 )
  2601                             )
  2602                         )
  2603                         pms_api_check_access(user=self.env.user, records=board)
! 2604                         pricelist = (
  2605                             self.env["product.pricelist"]
  2606                             .sudo()
  2607                             .browse(info_reservation.pricelistId)
  2608                             if info_reservation.pricelistId

Lines 2607-2616

  2607                             .browse(info_reservation.pricelistId)
  2608                             if info_reservation.pricelistId
  2609                             else folio.pricelist_id
  2610                         )
! 2611                         for rline in info_reservation.reservationLines:
! 2612                             board_day_prices[rline.date] = board._get_billed_day_price(
  2613                                 pricelist=pricelist,
  2614                                 consumption_date=rline.date,
  2615                                 pms_property_id=folio.pms_property_id.id,
  2616                                 adults=info_reservation.adults,

Lines 2652-2663

  2652         board_day_prices=None,
  2653         proposed_reservation=False,
  2654         commission_percent_to_deduct=0,
  2655     ):
! 2656         board_day_prices = board_day_prices or {}
  2657         cmds = []
  2658         for line in reservation.reservationLines:
! 2659             board_day_price = board_day_prices.get(line.date, 0)
  2660             if proposed_reservation:
  2661                 # Not is necesay check new dates, becouse a if the dates change,
  2662                 # the reservation is new
  2663                 proposed_line = proposed_reservation.reservation_line_ids.filtered(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant