Remove NewBook room blocking — app never writes to NewBook

Unsellable flag is in-app visibility only; staff mark rooms out of
order in NewBook through their own process. NewBook use is now
read-only (room sync + occupancy filter).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-03 21:37:52 +00:00
parent 6ca395097e
commit 3289a31027
9 changed files with 11 additions and 114 deletions

View file

@ -101,7 +101,6 @@ export async function initDb() {
priority TEXT NOT NULL DEFAULT 'medium', -- low | medium | high | urgent
status TEXT NOT NULL DEFAULT 'submitted',
unusable BOOLEAN NOT NULL DEFAULT FALSE,
newbook_blocked BOOLEAN NOT NULL DEFAULT FALSE,
hold_until DATE,
due_date DATE,
assigned_type TEXT NOT NULL DEFAULT 'staff', -- staff | contractor
@ -140,7 +139,7 @@ export async function initDb() {
CREATE TABLE IF NOT EXISTS task_events (
id SERIAL PRIMARY KEY,
task_id INT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
event_type TEXT NOT NULL, -- created | status_change | reassigned | comment | photo | cost | newbook_block | newbook_unblock | reopened | edited
event_type TEXT NOT NULL, -- created | status_change | reassigned | comment | photo | cost | reopened | edited
from_status TEXT,
to_status TEXT,
note TEXT,
@ -178,8 +177,6 @@ async function seedDefaults() {
urgent_notify_email: '',
notify_on_assign: true,
notify_on_urgent: true,
newbook_block_status: 'Maintenance',
newbook_unblock_status: 'Dirty',
}
for (const [key, value] of Object.entries(defaults)) {
await pool.query(

View file

@ -65,7 +65,3 @@ export async function fetchBookings(fromDate, toDate) {
return res?.data ?? []
}
// Update room status in NewBook. NewBook expects 'status' parameter (not 'site_status').
export async function updateSiteStatus(siteId, status) {
return callApi('sites_update', { site_id: siteId, status })
}

View file

@ -1,7 +1,7 @@
import { requireAuth, requireCap, hasCap } from '../auth.js'
import { pool, getConfig } from '../db.js'
import { createTask, logEvent, TRANSITIONS, PRIORITIES, STATUSES } from '../lib/task-core.js'
import { fetchBookings, updateSiteStatus } from '../lib/newbook.js'
import { fetchBookings } from '../lib/newbook.js'
import { notifyAssignment } from '../lib/mailer.js'
const PRIORITY_ORDER = `CASE t.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END`
@ -266,43 +266,6 @@ export async function taskRoutes(app) {
return { ok: true, added_to_template: addedToTemplate }
})
// POST /api/tasks/:id/newbook-block — set the room out of order in NewBook
app.post('/api/tasks/:id/newbook-block', { preHandler: requireCap('update') }, async (req, reply) => {
return toggleNewbookBlock(req, reply, true)
})
// POST /api/tasks/:id/newbook-unblock — release the room in NewBook
app.post('/api/tasks/:id/newbook-unblock', { preHandler: requireCap('update') }, async (req, reply) => {
return toggleNewbookBlock(req, reply, false)
})
async function toggleNewbookBlock(req, reply, block) {
const { rows } = await pool.query(
`SELECT t.*, l.newbook_site_id, l.source FROM tasks t JOIN locations l ON l.id = t.location_id WHERE t.id = $1`,
[req.params.id]
)
if (!rows.length) return reply.status(404).send({ error: 'Task not found' })
const task = rows[0]
if (task.source !== 'newbook' || !task.newbook_site_id) {
return reply.status(400).send({ error: 'Task location is not a NewBook room' })
}
const config = await getConfig()
const status = block ? (config.newbook_block_status || 'Maintenance') : (config.newbook_unblock_status || 'Dirty')
try {
await updateSiteStatus(task.newbook_site_id, status)
} catch (err) {
return reply.status(502).send({ error: `NewBook error: ${err.message}` })
}
await pool.query('UPDATE tasks SET newbook_blocked = $1, updated_at = NOW() WHERE id = $2', [block, task.id])
await logEvent(task.id, block ? 'newbook_block' : 'newbook_unblock', {
note: `Room ${block ? 'blocked' : 'released'} in NewBook (status: ${status})`,
userName: req.user.name,
})
return { ok: true, newbook_blocked: block }
}
// GET /api/occupancy — today's in-house NewBook site ids (for the unoccupied filter UI)
app.get('/api/occupancy', { preHandler: requireCap('view') }, async (req, reply) => {
try {