0043. Property business dates and manual night audit
Questi contenuti non sono ancora disponibili nella tua lingua.
- Status: proposed; owner review required
- Date: 2026-10-29
Observable acceptance criteria
Section titled “Observable acceptance criteria”An administrator or front desk operator opens Night audit for a property and sees its persisted business date, unresolved arrivals/departures, accounts with nonzero balances and pending OTA quarantines. Arrivals and departures block completion; the operator follows the reservation link to check in/out, postpone or explicitly mark a no-show under its frozen policy. Other warnings require explicit acknowledgement. Refresh rechecks live data. Creating a run twice for the same date returns the same durable run. Completing it reconciles room charges and advances exactly one civil day. Reload retains the new business date and an immutable result with the actor, dates and warning counts. A retry after a crash or lost response cannot duplicate charges or advance twice. Two simultaneous requests cannot close the same day twice. Viewers and other properties/workspaces are denied. Invalid dates, stale runs, missing acknowledgement and premature closure explain recovery without writing partial financial state.
Decisions and scope
Section titled “Decisions and scope”The business date is initialized from each property’s local date at migration/creation. Today, stay eligibility and same-day corrections, no-show decisions, and financial posting bounds use it. Audit completion is allowed only once the local wall-clock day is later than the closing date; catching up runs one day at a time. Calendar arithmetic uses Temporal PlainDate, including DST and year boundaries. The property’s zone remains explicit.
When the business date lags, every property screen displays the desk and local dates,
with an audit link for authorized operators. A normal departure (after the business date
but on or before the local date) refuses check-out and its balance preview with
night_audit.required; catch up the audit first. Shortening an in-house stay cannot remove
already stayed nights. Undo check-in after local midnight returns
reservation.invalid_state: that correction window is closed and running the audit cannot reopen it.
For an early departure later than the local date, inventory and room charges release from
the later of the business and local dates, preserving every stayed night. The balance preview
uses that same release date. The check-out date stays the business date for same-day undo.
Physical room blocks and their form defaults use the local wall date.
Execution is manual. No automatic no-show policy is enabled: unresolved arrivals block closure, and the existing explicit no-show action applies frozen penalties and releases inventory. Automatic scheduling and automatic no-show configuration are deferred; this avoids silently applying a new commercial policy to existing bookings. The screen lists these arrivals and links to their existing resolution workflow.
ADR 0029 posts all frozen nights at check-in. This change preserves that financial contract: audit reconciles existing room obligations idempotently, preserving human waivers, rather than charging an already posted night again. It does not switch existing accounts to nightly accrual. The result reports additional ledger movements. ADR 0031 is booked revenue over explicit stay-date ranges, including future dates, and deliberately restates current capacity; it does not need a frozen report snapshot. The audit freezes its operational checklist and summary; performance reports retain their documented definitions. Closed-period corrections remain append-only folio reversals, not a prohibition on correcting reservations.
Wall-clock instants still govern authentication, scheduler leases/retries, OTA/ARI horizons, commercial cancellation/prepayment deadlines and audit timestamps. Lagging the desk’s date must not postpone security, external publication or contractual instant deadlines.
A durable pending run commits before completion. Completion obtains the same property row
lock as lifecycle writes, rechecks the checklist, plans every financial change before any
write, and commits postings, result, audit/event and date advancement in one transaction.
A crash rolls that transaction back; resuming the pending run repeats the same reconciliation.
A completed run is immutable. A persisted version changes from 1 (pending) to 2 (completed).
The public action requires the current completion version tag: missing or malformed If-Match returns
400 request.invalid; a stale version returns 412 and the current ETag. A lost-response
caller reads the run before retrying.
Opening an existing run returns 200; opening a new run returns 201. Run identity is property + date;
there is no separately editable date or bypass action. Balance warnings include in-house
stays and stays released/checked out on the audited day whose arrival is not in the future.
Future deposits and unrelated older departed accounts do not reappear each night. The summary
stores identifiers, warning keys and financial facts; guest, room and operator names are
resolved live for authorized readers, rather than copied into the immutable summary.
Room labels name an arrival’s first room and a departure’s last room (ADR 0042 splits stays across rooms).
Migration 0021_night_audit.sql is a contract promotion: drain old writers before
applying it. The backfill starts at the local date; resolve any historical expected
arrivals and overdue departures listed by the first precheck before attempting closure
(Today only shows a seven-day overdue window).
Wave 1: automatic audit and cash shifts (2026-10-31)
Section titled “Wave 1: automatic audit and cash shifts (2026-10-31)”This section supersedes the manual-only scope above. Observable acceptance: an administrator configures a property’s audit in Night audit, including local time and explicit automatic no-shows; the scheduler opens/resumes the same durable day as a manual operator. Expired tentative bookings release inventory without fees and appear in the result. An operator opens the Cash drawer with a float, records ordinary cash payments/refunds from a folio, counts the drawer and closes it. Expected cash, declared cash, difference, actors and linked movements survive reload, and stale counts cannot overwrite new cash.
The module owns versioned night_audit_settings and cash_shift_settings sub-resources,
separate tables, property-scoped permissions and screens. Existing properties default to
manual audit, 03:00 local time, no automatic no-shows, and optional cash shifts. The settings
write requires its own If-Match. Execution health changes do not invalidate configuration
versions. No housekeeping work runs inside the audit; OPS owns that lifecycle.
Scheduling uses the existing worker and authenticated cron drain, with identifier-only jobs. The due instant uses a noon pivot: 00:00–11:59 runs on the morning after the closing date; 12:00–23:59 runs on that closing date in the evening. The screen shows the computed next run instant in the property time zone. Compatible Temporal DST disambiguation moves a missing minute forward by the gap and chooses the earlier repeated minute. Run identity remains property + business date, so a repeated hour cannot double-close it. Each invocation closes at most seven days, oldest first; subsequent ticks continue. Existing property locks serialize manual and automatic runs. Blocked attempts and safe failure codes persist in health and the lag banner links recovery. Enabling automation authorizes acknowledgement of nonblocking balance and OTA warnings; those warnings remain in the immutable result. Unresolved departures always block closure. No-show automation applies the same frozen policy engine as the explicit action, including fee-free tentative and OTA treatment and existing inventory/waiver rules.
A tentative option has a nullable local option_expires_on date. Omitted create input means
no expiry; omitted edit preserves it; null clears it; confirming clears it. Only tentative
direct bookings accept a new date. The audit closes that expiry business date, including when configured for an evening
close; expiry equal to the closing date is eligible. Release is fee-free. Option
and no-show actions each commit their reservation, inventory/ARI, folio, audit/event and
pending-run result identifiers together. A later blocker leaves visible resolved work;
retry continues without duplicating it. Day closure itself remains one atomic transaction.
Cash uses one drawer per property, enforced by a database partial unique index, rather than
one drawer per operator or named till. Currency is the property’s currency. Float, declared
count and threshold are nonnegative exact minor-unit money; expected and difference may be
negative. Difference is declared minus expected. An absolute difference strictly above the
current property threshold requires a reason; that threshold is frozen on the closed report.
Cash postings advance the shift version as well as expected cash, preventing stale closure.
The folio seam is only a nullable server-assigned cash_shift_id on new cash rows; clients
cannot select it. Refunds and cash reversals affect the currently open drawer, even when
the original payment belonged to an older closed shift. Closed reports remain immutable.
Idempotent payment retries return their original attribution and do not affect a new drawer.
Writers lacking cash read permission receive only an acknowledgement.
Migration 0033 is a contract promotion: drain old writers before applying the nullable cash attribution and worker grants. No historical cash is invented or assigned retroactively. Ordinary requests probe for due audits after the response, at most once per warm instance per minute, and wake the existing bounded pump only when work is due. Audit-only drains do not wait for idle work. The daily 03:00 UTC Vercel cron remains a safety net; without traffic, execution waits for that trigger. No paid hosting plan is required. Docker dispatches every minute. Discovery and the domain both enforce a fifteen-minute retry back-off. Health updates are data-only; the audit trail records the first failure and recovery rather than every attempt. Migration 0033 is not applied to any deployment yet and is amended in this PR. After its contract promotion an older runtime refuses startup. Recovery is roll-forward only; do not use Vercel Instant Rollback to an older runtime. Financial foreign keys retain NO ACTION, matching existing ledger tables. Only night-audit settings cascade. Allotments, fiscal closure, automatic cash counting and online payment providers remain planned or outside this track. A cash count remains an explicit operator declaration.
No-show result responses may include live no_show_details (room ID, parent reservation ID,
code, guest name and room label). These labels are hydrated in bulk within the authorized
property/workspace scope; only room IDs are persisted. The UI links the parent reservation,
with plain room-ID fallback for unavailable historical labels.
Module-only custom roles do not require the general properties.read grant. The read-only
GET /properties/{property_id}/night_audit_context exposes only time_zone, under audit
run/configure permission. GET /properties/{property_id}/cash_shift_context exposes only
currency and time zone under any cash grant. These scoped metadata resources let the web
format the module correctly without reading unrelated property settings. They return no
configuration or financial facts and do not change settings versions.
Precheck descriptions identify arrival-day expired options as resolvable during completion, without weakening the persisted arrival/departure blockers. Manual completion works with automatic no-shows disabled. Drawer inputs use ISO 4217 minor units, independently of CLDR display rounding (for example HUF has two decimals and IQD has three).
Review corrections (PR 284, round 2)
Section titled “Review corrections (PR 284, round 2)”Automatic no-show decisions carry an automatic marker. An authorized operator may undo one during the next business date when its decision date is the previous business day. Inventory availability is rechecked before restoring it; a sale of the released room still refuses undo. Manual no-show recovery retains its existing same-business-date window.
Automatic acknowledgement remains nonblocking. The health card shows the last automatic run’s warning, OTA quarantine and balance counts with a run link. The business-date banner area retains that notice, even after catch-up and reload, until an authorized operator opens that completed automatic run. Review state is a monotonic property date, separate from immutable run facts and settings versions; older concurrent reviews cannot hide newer warnings.
Settings and pending-run ETags are optimistic-write version guards, not representation validators. Health, next-run instants, live names and pending reservation result identifiers may change without changing them. There is no If-None-Match support. Cash history follows ADR 0016 (opaque cursor, default 20, data/next_cursor/has_more). Actor and reservation names are hydrated live within scope. Only folio post assigns a drawer; all movement builders reset inherited attribution to null. Worker cash-write grants are absent. The shared predicate of background writes, formerly ownsWebhookDelivery, was renamed ownsBackgroundWrites when INT integrated (N7). FOL must construct its transfer legs with null cash attribution and wrap its append path in the existing cash admission/recording seam.
Unreviewed warning lookup excludes later clean runs, so they cannot hide an earlier notice. Review is recorded only after the selected run result renders, independently of checklist fetches.
Financial foreign keys from movements to shifts and from shifts/cash settings to properties use NO ACTION, matching existing ledger tables. Only night-audit settings cascade. No runtime operation deletes a property or a cash shift.
Round 3 operator recovery and scheduling
Section titled “Round 3 operator recovery and scheduling”An automatic no-show exposes additive live policy_decision.undo_available on authorized
reservation reads and writes. The server computes it using the same domain predicate as undo,
under the existing property read snapshot or mutation lock. It is never stored in policy JSON.
The reservation screen offers undo during that window; capacity checks and fee reversals still
run atomically when submitted.
Automation continues reservation batches within the current job’s cancellation budget without
starting failure back-off for night_audit.in_progress. Manual completion says “Complete the
audit again to continue.” Disabled automation has no next run. An enabled next-run instant is
the earliest next attempt, including catch-up and the fifteen-minute failure back-off.
Warning review requires a browser member session with audit rights. Opening a rendered run
posts review only when that date has unreviewed warnings, then invalidates only warning queries.
Manual runs return night_audit.review_unavailable with an explanation that they need no review.
Clean runs have no warning notice. Warning counts use ICU plurals.
The settings help states both consequences of the noon pivot: with automatic no-shows on, a 12:00–17:00 run marks that day’s still-expected arrivals as no-shows; after an evening close, a late walk-in cannot be booked on the closed date.