Maintenance log book app — initial scaffold

Multi-department fault log: NewBook-synced room locations + manual
locations with categories, six-state task flow (submitted/in progress/
hold-parts/hold-later/temporary fix/fixed), photos per stage, priorities
with unusable flag and per-task NewBook out-of-order push, costs on
resolve, comment/audit thread, recurring task templates with
note-to-template carryover, asset register, contractor register with
document attachments, staff/contractor allocation, occupancy-aware
summary filter, searchable history with CSV export, email notifications.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-03 21:28:57 +00:00
commit 6ca395097e
47 changed files with 6727 additions and 0 deletions

60
seed-app.js Normal file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env node
// Run from maintenance/ dir: DATABASE_URL=... node seed-app.js
import pg from 'pg'
const { Pool } = pg
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
await pool.query(`
INSERT INTO apps (slug, name, description, base_path, icon, theme_color, category, internal_host, internal_port)
VALUES ('maintenance', 'Maintenance', 'Maintenance log book — faults, recurring tasks, assets and contractors', '/maintenance', 'Wrench', '#b45309', 'Operations', '10.10.10.121', 3080)
ON CONFLICT (slug) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description,
base_path = EXCLUDED.base_path,
icon = EXCLUDED.icon,
theme_color = EXCLUDED.theme_color,
category = EXCLUDED.category,
internal_host = EXCLUDED.internal_host,
internal_port = EXCLUDED.internal_port
`)
// Seed capabilities
await pool.query(`
INSERT INTO app_capabilities (app_id, slug, name, description, sort_order)
SELECT a.id, c.slug, c.name, c.description, c.sort_order
FROM apps a, (VALUES
('view', 'View Tasks', 'View the maintenance log and history', 1),
('report', 'Report Faults', 'Create tasks, add photos and comments', 2),
('update', 'Update Tasks', 'Change task state, reassign, edit details', 3),
('resolve', 'Resolve Tasks', 'Mark tasks temporary fixed or fixed', 4),
('costs', 'View Costs', 'See and enter repair cost values', 5),
('manage_locations', 'Manage Locations', 'Manage locations, categories and NewBook sync', 6),
('manage_assets', 'Manage Assets', 'Create and edit the asset register', 7),
('manage_contractors', 'Manage Contractors', 'Manage contractors and their documents', 8),
('manage_templates', 'Manage Recurring', 'Create and edit recurring task templates', 9),
('settings', 'Settings', 'Configure maintenance app settings', 10)
) AS c(slug, name, description, sort_order)
WHERE a.slug = 'maintenance'
ON CONFLICT (app_id, slug) DO NOTHING
`)
// Grant view + report to Staff role if they have no maintenance caps yet
await pool.query(`
INSERT INTO role_capabilities (role_id, cap_id)
SELECT r.id, ac.id
FROM roles r
JOIN app_capabilities ac ON ac.slug IN ('view', 'report')
JOIN apps a ON a.id = ac.app_id AND a.slug = 'maintenance'
WHERE r.name = 'Staff'
AND NOT EXISTS (
SELECT 1 FROM role_capabilities rc2
JOIN app_capabilities ac2 ON rc2.cap_id = ac2.id
JOIN apps a2 ON ac2.app_id = a2.id AND a2.slug = 'maintenance'
WHERE rc2.role_id = r.id
)
ON CONFLICT DO NOTHING
`)
console.log('maintenance app seeded.')
await pool.end()