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.
Sale and deadlines
Section titled “Sale and deadlines”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.
Cancellation
Section titled “Cancellation”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.
No-show and recovery
Section titled “No-show and recovery”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.
Prepayments and guarantees
Section titled “Prepayments and guarantees”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 ownership and clients
Section titled “OTA ownership and clients”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.
Verification
Section titled “Verification”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.