room-planner/backend/src/db.js
jtricerolph 1e658b6a48 feat: add room-planner app — 3-day HK room view with NewBook integration
NewBook-connected daily housekeeping planner. Replaces the hotelhubmodule-housekeeping-dailylist
WordPress plugin. LXC 120 · 10.10.10.120:3080 · slug: room-planner.

- 3-day booking window (yesterday/today/tomorrow) fetched live from NewBook
- Task completion ticks back to NewBook; room status patches NewBook directly
- 23px border sliver CSS system for adjacent-day booking status
- 3-state filter cycling (off→inclusive→exclusive) for categories and flow types
- Stat filters for outstanding tasks and clean/dirty status
- Rolling 48h activity log with checkout/checkin/status/tasks events
- newbook_pings event bus for future NewBook poller integration
- Room modal with permission-gated guest/rate/notes, task checkboxes, status buttons
- Placeholder sections for future linen-count and routine-tasks modules
- Settings page: task type colours, twin/extra-bed detection, category exclusions
- Mobile-first layout (sidebar desktop, compact top bar mobile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 13:07:00 +00:00

70 lines
2.2 KiB
JavaScript

import pg from 'pg'
const { Pool } = pg
export const pool = new Pool({ connectionString: process.env.DATABASE_URL })
export async function initDb() {
await pool.query(`
-- Instance-level config: task display, twin detection, note visibility, etc.
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Rolling activity log. Pruned to 48h on each write.
CREATE TABLE IF NOT EXISTS activity_log (
id SERIAL PRIMARY KEY,
room_id TEXT NOT NULL,
event_type TEXT NOT NULL,
event_data JSONB,
user_name TEXT,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
service_date DATE NOT NULL,
booking_ref TEXT
);
CREATE INDEX IF NOT EXISTS activity_log_date_idx ON activity_log (service_date, occurred_at DESC);
-- Lightweight NewBook change signals from the poller. No booking data stored.
CREATE TABLE IF NOT EXISTS newbook_pings (
id SERIAL PRIMARY KEY,
booking_ids TEXT[] NOT NULL,
event_types TEXT[] NOT NULL,
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '5 minutes'
);
CREATE INDEX IF NOT EXISTS newbook_pings_expires_idx ON newbook_pings (expires_at);
`)
await seedDefaultConfig()
}
async function seedDefaultConfig() {
const defaults = {
task_display: {},
twin_detection: {
enabled: true,
custom_field_ids: [],
keywords: ['twin', 'two single', '2 single', 'single beds', 'sofabed', 'sofa bed'],
exclude_keywords: ['twin room', 'twin suite'],
},
extra_bed_detection: {
enabled: true,
keywords: ['extra bed', 'extra cot', 'rollaway', 'roll away', 'fold out'],
},
excluded_categories: [],
hide_excluded_categories: false,
visible_note_types: [],
checkout_notification_timeout: 30,
default_checkout_time: process.env.DEFAULT_CHECKOUT_TIME || '11:00',
}
for (const [key, value] of Object.entries(defaults)) {
await pool.query(
`INSERT INTO config (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING`,
[key, JSON.stringify(value)]
)
}
}