Hamsun Housekeeping
The housekeeping module manages cleaning and deep-cleaning tasks — generated automatically from booking events and the daily schedule, or manually requested by guest or reception. The cleaning team owns this queue end-to-end. Reception only sees a peek (in the command center) so they know what's pending and overdue.
Items requests (towels, linen, amenities, laundry pickup) are not handled here. Those continue to flow through the existing reception request portal.
What's in the housekeeping module
Hamsun has a recurring confusion between "housekeeping items" (towels, linen) and "housekeeping cleaning" (room cleaning by the cleaning team). This module handles only the latter.
Housekeeping module · this spec
In scope- Daily cleaning
- Deep cleaning
- Bathroom-only cleaning
- Turn-down service
- Post-checkout cleaning
- Linen change as part of cleaning visit
Reception requests · existing portal
Out of scope- Extra towels (separately)
- Toiletries refill
- Slippers, robe, kettle, hairdryer
- Amenities top-up
- Laundry pickup
- Iron / ironing board
Task sources
Cleaning tasks enter the queue in 4 distinct ways. Two are automatic (system-generated), two are user-driven.
On checkout
bookings.checkin_status changes to CHECKED_OUT
post_checkout cleaning task for that room
Daily auto-cleaning
09:00 PKT ← decide time
auto_daily_cleaning = true AND checkin_status = CHECKED_IN
daily_clean task per qualifying room
Guest portal
source = 'guest_portal'
Reception / PMS
source = 'staff_manual'
cleaning.create — reception + housekeeping team
Auto-daily cleaning · the booking-level toggle
A booking-level boolean drives whether the daily cron auto-creates a cleaning task each morning. Three entry points let the toggle be flipped — the system stays in sync regardless of how the guest opted in.
The single source of truth
Three ways to flip it
Guest portal
/portal/preferences page · saves immediately, WhatsApp confirmWhatsApp reply
Reception / PMS
Daily cron reads it once each morning.
Skip rules
Even when auto_daily_cleaning = true, the daily cron still skips a booking when any of these apply:
- Arrival day — guest is checking in, room was just cleaned post-checkout of the previous guest
- Departure day — guest is leaving today, the post-checkout source will handle it
- DND today — booking has
cleaning_dnd_today = true(guest told reception "no service today") - Already cleaned today — there's already an active or done
daily_cleantask for this room today - Date in the skip list — booking has
cleaning_skip_datesarray containing today's date (planned skips, e.g. business meeting day)
Task lifecycle · 5 states
Each cleaning task moves through these states. The cleaning team primarily uses the queue; reception sees the live count via the command center peek card.
States
Actions
| Button | From | To | Flags |
|---|---|---|---|
| → Assign | scheduled | → assigned | requires assignee |
| ✓ Started | assigned | → in_progress | — |
| ✓ Done | in_progress | → done | notify guest |
| ⊘ Skip today | any active | → skipped | requires reason |
| ✕ Cancel | any active | → cancelled | destructive · note |
Subtypes
Sample timeline · 5-night stay
Guest in Room G06, books Mon–Sat (5 nights). Below shows what tasks the system auto-generates each day under different settings.
A · Booking has auto_daily_cleaning = true
B · Same booking, but guest sets DND on Wed and skips Thu in advance
C · Booking has auto-daily OFF, but guest manually requests deep clean on Wed
Data model changes
Two parts: additions to the bookings table for the per-booking preference, and a new cleaning request_type that reuses the existing portal.requests infrastructure.
1. Booking preference columns
2. Use existing portal.requests with new cleaning type
No new tables — cleaning tasks are portal.requests rows with type_slug = 'cleaning'. This reuses workflow engine, status history, audit log, and the dynamic action bar.
3. Optional log table for daily-cleaning bookkeeping
WhatsApp opt-in flow
At check-in (or on first WhatsApp interaction), the system sends a one-tap opt-in for daily cleaning. Guest's reply parses to bookings.auto_daily_cleaning.
Sample conversation
Would you like daily cleaning around 10am every day during your stay? You can change this anytime.
Reply STOP to turn off daily cleaning entirely.
Reply parsing rules
| Guest message | Effect | Bot reply |
|---|---|---|
DAILY, "yes daily", "yes please" |
Set auto_daily_cleaning = true |
"Got it — daily cleaning starting tomorrow." |
STOP, "no daily", "cancel daily" |
Set auto_daily_cleaning = false |
"Daily cleaning paused. Reply DAILY anytime to restart." |
SKIP, "skip today", "no clean today" |
Set cleaning_dnd_today = true |
"Skipping today's cleaning. Tomorrow as usual." |
CLEAN NOW, "send cleaning" |
Create immediate cleaning task | "Cleaning crew on the way — within 30 min." |
CHANGE TIME 14:00 |
Set cleaning_pref_time = '14:00' |
"Cleaning shifted to 2pm starting tomorrow." |
Guest portal toggle
A "Cleaning preferences" section in the guest's stay portal — same toggle, surfaced visibly so guests don't need WhatsApp.
Cron & triggers
Three pieces of automation glue the module together.
Postgres trigger · post-checkout cleaning
runs on updateUPDATE OF checkin_status ON public.bookings when new value is CHECKED_OUT and old value wasn't, insert a portal.requests row with type_slug = 'cleaning', subtype = 'post_checkout', source = 'checkout_trigger', scheduled_for = now().
Daily cron · auto-daily-cleaning
09:00 PKT dailydaily-cleaning-cron. Loops every booking where auto_daily_cleaning = true AND checkin_status = CHECKED_IN. Applies skip rules. For each qualifying booking, inserts a cleaning request with subtype = 'daily_clean', source = 'cleaning_cron'. Logs every booking checked into housekeeping.daily_cron_log with the outcome.
WhatsApp inbound · reply parser
on incoming messageDAILY, STOP, SKIP, CLEAN NOW, CHANGE TIME hh:mm. Updates bookings.auto_daily_cleaning / cleaning_dnd_today / cleaning_pref_time via the booking inferred from sender's WhatsApp number. Sends confirmation reply.
Daily DND reset
23:59 PKT dailyUPDATE bookings SET cleaning_dnd_today = false WHERE cleaning_dnd_today = true. Runs at end-of-day so the flag doesn't accidentally carry into the next day.
Open decisions · 8 questions for you
Before I write the schema migration and edge functions, please confirm these:
cleaning or housekeeping?