Salta ai contenuti

Booking policies and no-shows

Questi contenuti non sono ancora disponibili nella tua lingua.

GuestCraft implements local commercial conditions, cancellation fees, room no-show, guarantee attestations and prepayment tracking, on top of the folio workflow. ADR 0032 defines the commercial arithmetic.

A member with policies.configure (built-in: administrators) opens Settings → Booking policies, creates a policy from Flexible, Moderate or Non-refundable, edits its tiers/guarantee/prepayment and selects it on a rate plan. Each policy has a pol_ identifier and version. Active plan references prevent archival (policy.in_use, 409). Policy edits and archive/restore use strong If-Match: a stale version is policy.version_mismatch (412, with current_version and the ETag), and a policy that is not in the property is policy.not_found (404). Members with policies.read can inspect them; with the built-in roles that is every role but housekeeping. All writes are tenant scoped and include audit/events in their transaction. Reasons stay on tenant records, not audit/event payloads.

A quote shows the policy’s current terms. New reservation submits the quoted nightly prices and quoted_policy_id/quoted_policy_version; prices or conditions changing before submission give 412, preserving the draft. The server freezes terms, policy version, property time zone, acceptance instant and prepayment deadline/amount per room. A room whose sale changes (type, dates, plan or nightly amounts) freezes the current conditions, and the caller must then send quoted_policy_id and quoted_policy_version. An edit that leaves the sale unchanged retains the sold snapshot, even when it re-sends rate_plan_id or quotes the policy version already sold. An edit to the policy never changes earlier bookings.

Arrival time is explicit on the policy. A day deadline subtracts local calendar days; an hour deadline subtracts elapsed hours from arrival. A deadline is inclusive of the earlier tier: cancelling exactly 7 days before arrival is still under the earlier (cheaper) tier, and the next tier applies from the following nanosecond. Missing local times move forward and repeated times use their earlier occurrence. Tiers are entered from farthest to nearest deadline; when DST changes their actual order, the latest effective instant wins. Percentage fees use integer basis points, exact arithmetic and half-up rounding in minor units. Night fees sum the first N frozen nights; fixed amounts use the property’s currency exponent and apply per room. Every fee is capped at the room’s frozen total. No legal tax or statutory refund rule is hardcoded into this engine.

Existing rate-plan presets migrate to policy references. Legacy custom text is preserved on the plan, with no inferred cancellation penalty; a member with policies.configure must review it. Backfilled snapshots preserve today’s conditions of the plan but not what was sold: they are marked provenance: "backfill", carry accepted_at: null and, for custom-text plans, needs_review: true with a zero no-show fee. Only a plan that was explicitly non-refundable keeps a full no-show fee. Migration adds no historical charges. Existing manual bookings keep flexible cancellation and no inferred no-show fee. The old cancellation/cancellation_text fields remain compatibility input; policy_id takes precedence. Without a reference, legacy preset input reuses only matching preset terms; an editable policy name cannot change the preset’s meaning. New manual bookings without a plan receive flexible conditions.

POST /properties/{property_id}/reservations/{id}/cancellation-simulation returns CancellationSimulation: reservation version, per-room policy name/fee, exact total and the flags tentative (an unconfirmed option: nothing is charged) and partial (other rooms keep their own outcome; only the listed rooms are cancelled). The UI presents it before cancellation and, with policies.override_cancellation, supports an explicit final amount (zero waives) with a required reason.

POST .../cancel sends If-Match, reason, simulated_amount, and optionally override_amount/override_reason. Confirmation recomputes the fee. A deadline crossing that changes the amount returns quote.changed (412), requiring another simulation. The same transaction releases inventory, reconciles/reverses accommodation charges, posts the penalty and records the decision/audit/event. Fees are source: "policy" folio charges. Exact retries with an old version cannot post another fee. Legacy API clients can omit simulation only when the computed fee is zero; the UI always simulates. Cancellation acts on every room still expected. When all rooms are expected the reservation is cancelled; when others are no-show or departed, only the expected rooms become cancelled (audited as reservation.rooms_cancelled), the reservation stays confirmed and the other rooms keep their decisions and fees. A room in the house blocks cancellation until check-out. A tentative option is cancelled without a fee, never carries a prepayment and is not listed in deposits due. Reinstating a cancelled room remains planned.

Simulating needs policies.simulate_cancellation, cancelling reservations.cancel and marking a no-show policies.no_show; changing a cancellation penalty needs policies.override_cancellation and waiving a no-show fee policies.waive_no_show. With the built-in roles, administrators and front desk cancel and mark no-shows, and only the administrator changes penalties or waives fees. An override total is allocated to rooms in persisted order, capped at each room total; the reason and calculated/final amounts remain visible on the frozen decision.

POST .../rooms/{room_id}/no_show requires If-Match and reason. Only an expected, uncancelled room can become no_show, from its arrival date in the property’s current zone (from 00:00, before the policy’s arrival time). Confirmation remains unchanged. The frozen no_show_release is from_today or from_arrival; either way nights before the property’s today are never changed, so on the arrival day or later they release from today. All assignments become inactive. The frozen no-show fee is posted atomically; no accommodation charge is invented for an unserved no-show, and a tentative option posts no fee.

POST .../rooms/{room_id}/waive_no_show (policies.waive_no_show) requires If-Match and a reason. It reverses the exact posted fee, sets the decision’s amount to zero with overridden: true and waived_at, and keeps the calculated amount as simulated_amount. Generic folio reversal refuses fees posted by a policy (folio.not_reversible), so the reservation and the account cannot disagree.

POST .../rooms/{room_id}/undo_no_show requires a reason and the current version. It is available on the business date of marking; automatic decisions from the previous business date may also be undone during the next business date. It rechecks capacity and unit overlap before restoring inventory/assignments, and reverses the policy fee. The room retains the original decision, undo instant and operator reason; zero-fee corrections retain their reason too. Another sale can prevent undo; the refusal leaves the no-show and ledger intact. No-show rooms are finished for last-room settlement and refunds due; undo makes them expected again and removes an otherwise unfinished booking from refunds due. Policy fees remain marked not tax-computed and are outside room/extra re-rating; country fee tax calculation remains planned. Today has the last 7 days of overdue expected arrivals, no-show counts and prepayments due; detail and reservation filters show no-show separately from cancellation. Planning does not imply active occupancy for released nights. Administrators can enable automatic no-show marking through the property’s night-audit settings. It is off by default and applies the same frozen policies and inventory release rules during manual or scheduled audits.

GET .../prepayments returns required, covered and outstanding currency amounts plus frozen due instants. Net unreversed folio payments minus refunds cover obligations once, ordered by due instant then room ID. They do not create accommodation revenue. Today shows up to 100 outstanding expected-room obligations; the limit notice appears only when more than 100 are actually due. Both reads need policies.prepayments for the property, and both answer the amounts with it, with or without folios.read. Today shows the prepayments tab only to a member with that grant. The candidate scan continues past future legacy deadlines whose stored due date is null. Tentative options are excluded. Payments covering a booking remove its fully covered obligations from this candidate list. Per-payment allocation remains planned. The Payments due list and damage-deposit holds belong to online payments.

PUT .../rooms/{room_id}/guarantee sends If-Match and { status, reason }, where status is pending, confirmed or waived. Attesting needs policies.manage_guarantee (built-in: admin and front_desk) and waiving policies.waive_guarantee (built-in: admin). This is an operator record, not a card authorization or a payment. A card the guest saves through a payment link confirms the card guarantees still pending, recorded with source: provider (guarantee_source on prepayment statuses); attested and waived guarantees keep their provenance. Card numbers and security codes are never stored.

OTA cancellation state comes from booking import. Cancellation simulation identifies OTA ownership; local cancellation is refused. Local OTA no-show updates operational state and inventory but posts no automatic fee. Later revisions preserve local no-show information; conflicting commercial changes require recovery. Reporting the no-show, an invalid card or a cancellation over an invalid card to the OTA is a separate step after the local decision, in booking reports; automatic OTA fees remain planned.

The SDK exposes policies.{list,create,update}, reservations.{simulateCancellation, prepayments} and reservationRooms.{markNoShow,undoNoShow,waiveNoShow,guarantee}. frontDesk.get lists overdue arrivals of the last 7 days with older_overdue_arrivals for the rest; all_overdue=true lists every one, and counts.no_show_arrivals is separate from completed_arrivals. Existing cancel accepts the simulation/override fields. Generated React query/mutation factories expose the same operations through each caller’s scoped transport. No write is automatically retried. No country-specific module or fiscal document is added; online payments are in their own guide.

Run pnpm quality, the generated-contract checks, REUSE, production build and Docker build. pnpm --filter @guestcraft/web test:e2e:policies uses the ordinary disposable production-browser harness; set GUESTCRAFT_E2E_ADMIN_URL, a free assigned GUESTCRAFT_E2E_BASE_URL, and optionally GUESTCRAFT_E2E_SCREENSHOTS. It covers creating terms, sale, policy edits retaining old terms, fee simulation/cancellation, reload, no-show, folio and inventory, with axe and desktop/mobile screenshots. PostgreSQL tests also inject posting failures and competing sales during undo; RLS tests cover both roles.

The frozen prepaymentAmount is the sold deposit obligation, including after room-night edits. Legacy snapshots without it use the stored penalty and nights. A null due date is recovered from the sold snapshot’s deadline rule, room arrival, arrival time and time zone. Only due: "booking" uses policy acceptance (or reservation creation when acceptance is absent). SQL includes missing due dates as candidates; the domain computes the true deadline and filters Today.

Automatic audit no-show decisions are marked as automatic. During the next business date, an operator may undo an automatic decision dated the previous business day. Manual decisions retain the same-business-date recovery window. Both paths recheck availability before restoring inventory; a conflicting sale refuses undo without changing the fee or no-show.