0032. Frozen commercial policies and explicit no-shows
Questi contenuti non sono ancora disponibili nella tua lingua.
- Status: Accepted for implementation
- Date: 2026-10-27
- Depends on: ADRs 0024, 0027, 0028 and 0029; PR #31
Observable acceptance criteria
Section titled “Observable acceptance criteria”An administrator manages named property policies and selects one on a rate plan. Policies support cancellation tiers, percentage/night/fixed penalties, a no-show fee, inventory release, guarantee and a dated prepayment obligation. Flexible, moderate and non-refundable presets can be copied and edited. Invalid input and stale versions preserve stored data. A receptionist sees conditions on a quote and a new booking, then reloads the same frozen conditions even after the policy is edited. Repricing explicitly freezes current terms; assignment, occupancy-only edits and other lifecycle actions retain sold terms, including an edit that re-sends the unchanged plan, dates, type and amounts.
Before cancelling, the receptionist simulates the exact fee and confirms with the same reservation version and simulated amount. Deadline crossings require another simulation. Confirmation atomically releases inventory, reverses posted accommodation and posts the fee. An administrator can change or waive it with a reason stored on the tenant record; audit records only identifiers and safe override flags. Other roles cannot override. A tentative option is not a sale: cancelling it, or recording its no-show, posts no fee, it has no prepayment obligation and is never listed in prepayments due. When other rooms of the reservation already have their own outcome (no-show or departed), cancelling acts on the rooms still expected only and the others keep their decision and fee.
A receptionist marks an expected room no-show from its arrival day in the property zone. No-show is a room stay state, separate from cancellation. The frozen release rule and fee are applied together with inventory, folio, audit and event. Same-property-day undo reverses the fee and rechecks inventory and assignments; failed recovery writes nothing. An administrator can waive a posted no-show fee with a reason, mirroring a cancellation exception. Today counts no-shows separately from arrivals that happened, lists the overdue expected arrivals of the last seven days (with the count of older ones and a “view all”) and due prepayments. Detail shows obligations, net payments covering them, and a manually confirmed/waived guarantee, without card data. Read-only roles see permitted data and cannot mutate; another workspace cannot access it. Browser acceptance covers non-refundable cancellation, no-show, reload and released capacity.
Decisions
Section titled “Decisions”Policies belong to the existing reservations context. Migration 0010 adds property policies with forced RLS and composite references, plan references and room snapshots. Monetary amounts on public contracts are decimal strings; percentage values use integer basis points. Each policy specifies a local arrival time. Calendar-day deadlines subtract local days; hour deadlines subtract elapsed hours from that zoned arrival. Temporal’s compatible DST disambiguation moves a missing local time forward and chooses the earlier repeated time. A deadline is inclusive of the earlier tier: “free until 7 days before arrival” includes the deadline instant itself, and the next tier starts just after it (a product decision of 2026-10-27). A fixed amount applies per room, so a three-room booking with a fixed fee pays it three times; the UI and the documents say so. Penalties are bounded by the frozen room total; nights use the first N frozen nightly prices. Percentage rounding is half-up in minor units. These are commercial rules, independent of national taxes and fiscal documents.
The existing named rate cancellation fields remain deprecated compatibility input. A
policy reference is authoritative. Existing plans are migrated to equivalent presets;
custom text is retained with zero inferred penalty and requires operator review. Historical
manual bookings retain flexible conditions. Migration must not invent folio debt, and it does
not invent a no-show exposure either: only a plan that was explicitly non-refundable keeps a
full no-show fee; flexible, custom-text and plan-less bookings get none.
Backfilled snapshots are marked provenance: "backfill", have acceptedAt: null (the
original acceptance is unknown) and, for custom-text plans, needsReview: true; the UI labels
them “conditions at migration, not confirmed as sold”.
Deprecated preset input reuses an existing policy only when all its terms match the
preset. A policy’s editable name never identifies its commercial meaning.
Prepayment is an obligation, not a charge or a card authorization. Recorded net payments (payments minus unreversed refunds) cover room obligations in stable due-time/room-ID order; coverage is never counted twice. Guarantee confirmation is an operator attestation, not proof of a provider authorization. No raw card, security code or provider secret is stored.
OTA commercial state remains owned by the OTA. Local no-show records operations and releases inventory but posts no automatic penalty. OTA cancellation continues through booking import. Recording a local no-show sends nothing to the OTA by itself: reporting an OTA booking is the separate, explicit workflow of “Reporting to the OTA” below. Automated collection, damage deposits, provider card authorization and statutory tax treatment remain planned. The assigned brief substitutes for a separate design issue. No new dependency is required.
Migration backfills have system audit/events, without inventing charges or acceptance
provenance (the review of PR #39 found an earlier draft that stamped the booking creation
instant as acceptance; the instant is now null). Policy fee postings use the distinct folio source policy. Reservation
lifecycle and folio policy events use past-tense action names.
Decisions after the independent review
Section titled “Decisions after the independent review”- Per-room outcomes. A room has the stay state
cancelledwhen it was cancelled while other rooms of its reservation had another outcome; the reservation stays confirmed. When every room is cancelled together the reservation is cancelled as before. A reservation whose rooms are all no-show is closed in practice: no prepayment, deposit or overdue entry, no cancel action. A guest in the house blocks the cancellation of the rest until they leave. - Policy fees are changed only by their own flows. The generic folio reversal refuses
source = policymovements (folio.not_reversible): cancellation override, no-show waiver and same-day undo keep the reservation’s decision and the account in agreement. - No-show release. The options are
from_todayandfrom_arrival. Nights before the property’s today are never changed, so the two coincide whenever the no-show is recorded on the arrival day or later (the only days it is allowed);from_arrivalrecords the intent (the whole stay is released) and is kept as a distinct, explicit policy value. - No-show timing. Recording is allowed from 00:00 of the arrival day in the property’s zone, before the policy arrival time (a product decision).
Refreshed folio dependency
Section titled “Refreshed folio dependency”The reviewed PR #31 is integrated with its asynchronous tax port and last-room settlement
rules. API revision 1.9.0 combines those contracts with policies. A no-show is a finished
room for last-room settlement and the refunds-due list; undo makes it expected again.
Policy penalties remain frozen commercial amounts, explicitly not tax-computed; country
calculation and re-rating of these fees remain planned. Room/extra tax adapters and their
additional lines retain their existing atomic behavior. System posting keys reserve the
policy: namespace, and no-show undo reverses its frozen movement without recalculation.
Legacy missing deposit deadlines
Section titled “Legacy missing deposit deadlines”A snapshot with a prepayment but no stored due instant is read using its frozen arrival time, time zone and deadline rule. Only a due-at-booking obligation uses acceptance (or reservation creation when acceptance is unknown). The SQL candidate query includes missing due dates conservatively; domain allocation and local-day filtering determine what Today actually shows. Frozen amounts stay authoritative.
Reporting to the OTA
Section titled “Reporting to the OTA”Date: 2026-11-01. Workflow contract: booking reports.
- Scope. A member with the new property-scoped grant
reservations.report_to_ota(built-in administrator and front desk, the roles that record no-shows) sends three reports through Channex’s Booking Reporting API, as the public OpenAPI describes them (snapshot captured 2026-11-01):- a no-show (
BookingOtaReports_noShow, Booking.com and Expedia); - an invalid card (
BookingOtaReports_invalidCard, Booking.com); - the cancellation over an invalid card (
BookingOtaReports_cancelDueInvalidCard, Booking.com). Any other channel is refused with a stableota_report.channel_unsupported.
- a no-show (
- Channel. It is read from the booking’s
ota_nameby a small normaliser that ignores case, spaces and punctuation:BookingCom/Booking.com, andExpedia/A-Expedia. - Windows. They are computed in the property’s time zone with Temporal and an injected clock,
half-open:
- the no-show from 00:00 on the arrival date for 48 elapsed hours;
- the invalid card from the booking’s creation until 00:00 on the arrival date;
- the cancellation from the end of the guest’s update window after GuestCraft’s own successful invalid-card report until 00:00 on the departure date. Channex documents no end, so the departure date is a prudent choice.
- Booking creation. It is the first stored
newrevision’sinserted_at; failing that, the reservation’screatedAt, which is later than the truth and treated as inexact. - Update window (inference to confirm at certification). It is 24 hours. It is
max(2 h, min(12 h, time until the next 15:00 property-local at or after the report))only for an exactly dated booking made less than 48 hours before 00:00 of the arrival date in the property’s time zone, the start of check-in that the no-show and invalid-card windows use too. A booking whose creation time is inexact keeps the 24 hours. - Extranet reports. A report made on the extranet is invisible to GuestCraft.
- Refusals. Each reason has a stable problem code; the first that applies wins:
ota_report.not_ota_booking,channel_unsupported,property_not_linked,reservation_cancelled,overbooked(no-show, whatever the rooms’ status),stay_status_not_allowed, theninvalid_card_not_reported(cancellation) andwindow_closed(withopens_at/closes_at). - Local no-show first. The no-show report requires every room the booking still holds to be a
local no-show. Its
waived_feesand, for Expedia,penalty_amountdefault to that outcome: waived only when the posted no-show fees were waived, and the penalty equal to the fees posted and not waived. On an OTA booking this rule posts no fee, so the default is “not waived, no penalty”. The member may change both before sending.- The web app’s Mark no-show and report records the local no-show with the existing use case first and then sends the report as a separate idempotent step; a failed report leaves the local no-show in place.
- Card reports and the cancellation change nothing locally. Local cancellation follows the OTA’s next booking revision, and penalties stay in this module and the folio.
- Idempotency. It follows the claimed-state pattern of
ADR 0044.
- Storage. Migration 0072 adds one row per reservation and kind in
booking_ota_reports, with forced RLS, property isolation,workspace_activeand cascading composite references. One booking maps to one reservation per workspace. - Claim and call. A request claims the row as
sendingin a transaction, with an audit entry and event. A repeated or concurrent request answers the stored report. The Channex call runs outside any transaction in one never-retried attempt. - Outcome. It is committed with an audit entry and event:
sent, which publishesreservation.ota_reported@1;failed, with the safe reason, for a definite refusal (409ota_report.refused_by_channel) or nothing applied (503); a later request may send it again;unknown, for a lost answer, an unreadable success, a 408 or a 5xx (ota_report.outcome_unknown).
- Unknown reports. Channex offers no read-back, so an unknown report, or a claim left
sendingfor more than two minutes, is never sent again blindly. A member records what the channel’s extranet shows (reconcile, audited), which makes the reportsentorfailed. - Stored data. No upstream body, card or guest data is stored, logged or audited.
- Storage. Migration 0072 adds one row per reservation and kind in
- Composition. The port takes the connection’s
ChannexKey, and the adapterbookingReportRemote(fetch?)builds its client throughchannexClient, so a report reaches only the installation that verified the key (ADR 0050). Anenvironment_mismatchconnection fails closed asota_report.property_not_linked. Tests use the simulator only, which serves the three operations with their documented answers and records every call.