Module: StayGo / Hotels (hotels / hotels)
Status: π‘ Partially implemented (5,218 hotels imported, 2026-09-06 live count; booking side unexercised β 0 bookings) Β· Last validated: 2026-09-06 Β· Priority: P0
Hotel/stay discovery and booking, incl. external-source import, meal plans, add-ons, bulk/group bookings, and per-property invoicing/tax/refund settings. Validated against classs/HotelModule.php (8,987 lines by wc -l, 2026-09-06 β corrected from an earlier "~3,000 lines" estimate, which undercounted the file by roughly two-thirds) and the live schema.
A. Purpose
Customers search stays and book rooms; hotel owners manage properties, room types/units, rates, availability, refund policy, invoices. Commission 5% (wired). Revenue potential high; booking throughput currently ~0.
B. Actors
Customer/guest; hotel owner + hotel_staff_assignments; admin. External sources (import) via upsertExternalHotel/RoomType/Package.
C. Entry points
Consumer: /staygo_intro.php, /hotelslist.php, hotel detail, booking. Merchant: hotel dashboard, room types, rates, bookings-finance. Trip Planner integration via tripPlannerMatch (:2712).
D. Onboarding
createHotel(ownerId, data) (:1071) or bulk importHotelsFromXls (:168) / upsertExternalHotel (:808). Room types via saveRoomType (:1576). Module hotels on; owner enrolled; refund policy + invoice/tax settings configured before selling.
E. Feature registry (prefix STAY-)
| ID | Feature | Actor | Method | Tables | Status |
|---|---|---|---|---|---|
| STAY-001 | Import hotels (XLS/external) | owner/admin | importHotelsFromXls :168, upsertExternalHotel :808 | hotels_master | π’ |
| STAY-002 | Create/update hotel | owner | createHotel :1071, ownerUpdateHotel :1383 | hotels_master | π’ |
| STAY-003 | Room types & units | owner | saveRoomType :1576, listRoomTypes :1695 | hotel_room_types, hotel_room_units | π’ |
| STAY-004 | Rates / availability | owner | rate overrides / inventory | hotel_room_rate_overrides, hotel_inventory_calendar | π‘ |
| STAY-005 | Meal plans & add-ons | owner | (meal/addon config) | hotel_meal_plans (1,796), hotel_addons (1,347), hotel_meals_master (1,353) | π’ |
| STAY-006 | Refund policy | owner | ownerSaveRefundPolicy :1930 | hotel_refund_policies (62, 2026-09-06 β was 57) | π’ |
| STAY-007 | Invoice / tax settings | owner | ownerSaveInvoiceSettings :2211 | hotel_invoice_settings (9), hotel_tax_settings (543, 2026-09-06 β was 525) | π’ |
| STAY-008 | Quote a room | customer | quoteRoomType :2935 | β | π’ |
| STAY-009 | Book a stay | customer | (booking write path) | hotel_bookings, hotel_booking_unit_assignments, hotel_guest_folio, hotel_payments | π‘ (0 bookings) |
| STAY-010 | Booking finance / folio | owner | ownerListBookingsFinance :2418, ownerGetBookingFinance :2502 | hotel_payments, hotel_invoices, hotel_guest_folio | π‘ |
| STAY-011 | Bulk / group bookings | customer/owner | bulk methods | hotel_bulk_requests (2), hotel_bulk_orders | π‘ |
| STAY-012 | Trip Planner match | customer | tripPlannerMatch :2712 | β | π’ |
F. Booking flow (STAY-009)
hotelslist.php β hotel detail β quoteRoomType(payload) :2935 (price + availability)
β book β INSERT hotel_bookings (header) + hotel_booking_unit_assignments (which unit)
β hotel_guest_folio (charges) β hotel_payments (wallet-first)
β central finance ledger (module_key='hotels', order_table='hotel_bookings')
β commission β notifications
G. Data model (2026-09-06, re-verified β counts moved intraday, see anomaly below) β the real room/booking tables
| Table | Rows | Role |
|---|---|---|
hotels_master | 5,218 | properties (mostly imported) |
hotel_room_types | 2,070 | room categories (the real "rooms", not hotel_rooms) |
hotel_room_units | 193 | physical units within a type |
hotel_inventory_calendar | 7 | date availability |
hotel_room_rate_overrides | 0 | date/rate overrides |
hotel_meal_plans / hotel_meals_master | 1,796 / 1,353 | meal plans |
hotel_addons | 1,347 | add-ons |
hotel_refund_policies | 62 | cancellation rules |
hotel_tax_settings / hotel_invoice_settings | 543 / 9 | GST + invoice config |
hotel_bookings | 0 | booking header |
hotel_booking_unit_assignments | 38 | unit assigned per booking |
hotel_guest_folio | 80 | folio charges |
hotel_payments | 0 | payments |
hotel_invoices | 0 | invoices |
hotel_bulk_requests | 2 | bulk/group booking requests |
Dormant/empty (don't use):hotel_rooms(0 β legacy; real rooms arehotel_room_types+hotel_room_units),hotel_master(0 β legacy singular; usehotels_master),hotel_packages(0 β usehotel_meal_plans). Data-state anomaly (requires verification β numbers moved during this same audit pass): an earlier reading today hadhotel_bookings= 0 whilehotel_payments= 362,hotel_guest_folio= 92,hotel_booking_unit_assignments= 39,hotel_invoices= 9 β read as "bookings occurred but the header table got purged while child/payment rows were retained." A re-run of the same exact COUNT(*) queries later the same day (2026-09-06) now showshotel_payments= 0 andhotel_invoices= 0 as well (both fully emptied), whilehotel_guest_foliodropped only to 80 (β12) andhotel_booking_unit_assignmentsonly to 38 (β1). That is a live, in-progress data change, not a stale doc β this environment is under active testing tonight and something is deleting booking/payment/invoice rows table-by-table rather than a one-shot purge. Do not treat any of these counts as stable; re-run the_tools/probes before quoting a number. Same general pattern (header/derived-table mismatch) as FoodVersefood_orders, but here the drift was observed happening in real time.
H. Financial impact
Wallet-first debit (HotelModule.php:4459) β escrow credit to CLASSIFIED_ESCROW_BUCKET_USER_ID (:4465, "Hotel booking escrow") β hotel_payments + central ledger (module_key='hotels') β 5% commission (wired, CommissionEngineModule::computeAndRecord('hotel_booking', β¦) at :4424; zero commission_transactions rows exist for hotel bookings today β consistent with 0 live bookings) β per-property invoice (hotel_invoices, GST via hotel_tax_settings) β refund per hotel_refund_policies. See ../../40-financial/central-finance-ledger.md. Note: the guest debit (:4459) and escrow credit (:4465) share one $meta array that carries no acct_purpose, so both fall through to WalletModule::journalAccountsFor()'s historical default (classs/WalletModule.php:952-983): the guest's debit books as USER_WALLET_LIABILITY β REVENUE_GENERAL (recognised revenue) rather than an escrow-liability movement, and the escrow credit books as if it were an external top-up rather than a liability reclassification. Same gap noted on the Food and Freelancer pages β real today, but currently unexercised in practice since hotel_bookings = 0.
K. State machine (hotel_bookings)
quoted β confirmed β checked_in β checked_out β completed, with cancelled (refund per policy) / no_show / modified (hotel_booking_modifications).
L. Errors (prefix STAY-E)
STAY-E001no availability βquoteRoomType/inventory calendar returns none.STAY-E002booking header missing for a payment β see data-state anomaly.
M / N. Dependencies & change-impact
Wallet, finance ledger, commission, invoicing, Trip Planner, notifications. Changing the booking path affects availability, folio, payments and invoicing β re-test quoteβbookβfolioβpaymentβinvoice.