Oracle Opera / OHIP Folio Ingest — Validation Results
The two billing edge cases that break automated corporate invoicing in a multi-brand, multi-currency estate: split-folio routing and cross-border corrections. Both are covered by the mapper and asserted on every build.
MEASURED against synthetic payloads in our own test harness — 17/17 mapper assertions passing, plus 11/11 smoke-test checks in dry-run. These are not yet results from a live Opera instance; the same suite runs unchanged against a customer OHIP sandbox once read-only credentials are issued, which is the first deliverable of a 60-day shadow pilot.
A personal bar tab must never land on the corporate invoice
- Only the company window is consolidated onto the corporate invoice
- Personal incidentals on windows 2+ never reach the corporate invoice
- An explicit routing instruction overrides the window number in both directions
- Reversed postings are dropped so credits are never invoiced
- Single-window folios (no window attribute) behave unchanged — no regression
- Corporate windows are configurable per property (Opera window config varies)
- Every retained line keeps a stable OPERA_-prefixed charge id — no cross-source collisions
A THB posting on a EUR contract converts once, at Opera's rate — or not at all
- Property-currency lines convert to the contracted settlement currency using Opera's own exchange block
- Converted amounts match rate x gross to 2dp with no rounding drift
- Lines already in the contracted currency pass through untouched
- A line with no usable rate keeps its native currency — no invented rate, reconciliation flags it
- Every posting carries an explicit currency code
What runs on day one of a shadow pilot
- OAuth2 token exchange against the OHIP host
- Single read-only folio GET — no POST, PATCH or DELETE anywhere in the script
- Folio windows flattened with window number, routing instruction and reversal flags preserved
- The same Scenario A and B assertions re-run against whatever the live sandbox returned
- Timestamped JSON report written as the pilot connection-test artefact
- Non-zero exit on any failed check
Required from your side: an OHIP sandbox host, read-only client credentials, an application key, and a test reservation — ideally one with a genuine split folio and a cross-currency posting. Nothing is written back to Opera at any stage.
Transaction-code ranges and folio window conventions are property-configurable in Opera. Our defaults are confirmed against your actual OHIP configuration during pilot setup — a configuration exercise, not a code change.
Stateless processing — guest data never reaches storage in readable form
Ingest and aggregation run in Cloudflare Workers / Deno isolates with no local disk, no session state and no cross-request caches. Each folio is processed inside a single request lifecycle; the isolate holds nothing once the response is returned.
Guest name, room number and folio narrative exist only as local variables during aggregation. They are never written to a table, a log line or an object store. References are released before the handler returns so the runtime GC reclaims them immediately.
What is persisted is the corporate-billing skeleton: a hashed reservation key, PMS charge id, amounts, currency, tax and item type. Guest identity is replaced by a salted SHA-256 digest, so line items reconcile across runs without any table holding a readable guest name.
OHIP client id, secret, integration user and application key are read from environment secrets inside the server handler only. They are never present in a client bundle, never returned to the browser, and bearer tokens are cached per-isolate in memory with a 60-second pre-expiry refresh.
The OHIP client issues GET requests only. There is no POST, PATCH or DELETE path to Opera anywhere in the ingest codebase — write-back is absent from the implementation, not merely disabled by configuration.
The validation layer is an observer. A fault, timeout or deploy inside it cannot alter a folio, block a checkout or affect the PMS ledger, which remains the sole source of truth for the pilot.
Every tenant-scoped table enforces PostgreSQL Row Level Security keyed on organisation membership, with least-privilege grants issued per table. An automated validation report blocks release on any table lacking isolation controls.
Every ingest carries a trace id linking request, mapping decision and resulting line items, retained for the full validation window and exportable for your security review.
Every OHIP field we request — and nothing else
| OHIP field | Why it is required | Persisted |
|---|---|---|
| reservationId | Correlate folio lines to one stay | Hashed (salted SHA-256) |
| hotelId | Property attribution and tax jurisdiction | Yes — plain code |
| folioNumber | Groups postings under one folio | Hashed (salted SHA-256) |
| businessDate | Invoice period allocation | Yes — date only |
| transactionLines[].id | Idempotency key; prevents double-billing | Yes — OPERA_-prefixed |
| transactionLines[].transactionCode | Classifies room / incidental / tax | Yes |
| transactionLines[].description | Invoice line narrative | Yes — charge text only |
| amount.grossAmount | Invoice line gross | Yes |
| amount.netAmount | Net for tax reconciliation | Yes |
| amount.taxAmount | VAT / regional tax compliance | Yes |
| amount.currencyCode | Native posting currency | Yes |
| amount.exchange.* | Single conversion to contracted currency | Yes — rate + target code |
| folioWindow | Excludes personal windows from the corporate invoice | Yes — integer |
| routingInstruction | Explicit company/guest override of window | Yes — enum |
| isReversed | Drops reversed postings so credits are never invoiced | Not stored — filter only |
15 fields, all within the folio transaction block. Read scope only.
Explicitly not requested, not received, not stored
- ✕Guest name, email, phone or address
- ✕Loyalty number or profile id
- ✕Card tokens, PAN fragments or payment instruments
- ✕Passport, ID document or nationality fields
- ✕Rate codes, negotiated rates or commercial terms beyond the posted amount
- ✕Any reservation field outside the folio transaction block