maintenance/backend/src/lib/task-core.js
jtricerolph d0ff70eb86 Summary: folder tabs, priority filter badges, sort dropdown; add Hold — Booked
- Status filter replaced by folder tabs (All open / Overdue / In progress /
  Temp fix / On hold) with live count badges; tabs, priority and category
  filters now applied client-side so counts always match
- Priority filter badges with counts; sort is now a dropdown list
- New hold_booked status (contractor booked), grouped with in progress, with
  optional booked date stored in hold_until; hold_until cleared when leaving
  the date-bearing holds

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 13:25:19 +00:00

83 lines
4 KiB
JavaScript

import { pool, getConfig } from '../db.js'
import { notifyAssignment, notifyUrgent } from './mailer.js'
import { notifyAssignmentPush } from './push.js'
export const PRIORITIES = ['low', 'medium', 'high', 'urgent']
export const STATUSES = ['submitted', 'in_progress', 'hold_parts', 'hold_booked', 'hold_scheduled', 'temporary_fix', 'fixed']
// Legal state transitions. temporary_fix still counts as open.
export const TRANSITIONS = {
submitted: ['in_progress', 'hold_parts', 'hold_booked', 'hold_scheduled', 'temporary_fix', 'fixed'],
in_progress: ['submitted', 'hold_parts', 'hold_booked', 'hold_scheduled', 'temporary_fix', 'fixed'],
hold_parts: ['submitted', 'in_progress', 'hold_booked', 'hold_scheduled', 'temporary_fix', 'fixed'],
hold_booked: ['submitted', 'in_progress', 'hold_parts', 'hold_scheduled', 'temporary_fix', 'fixed'],
hold_scheduled: ['submitted', 'in_progress', 'hold_parts', 'hold_booked', 'temporary_fix', 'fixed'],
temporary_fix: ['submitted', 'in_progress', 'fixed'],
fixed: ['submitted'], // reopen only
}
export async function logEvent(taskId, eventType, { fromStatus = null, toStatus = null, note = null, userName = null, photoId = null } = {}) {
await pool.query(
`INSERT INTO task_events (task_id, event_type, from_status, to_status, note, user_name, photo_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[taskId, eventType, fromStatus, toStatus, note, userName, photoId]
)
}
// Shared by the tasks route and the recurring-template scheduler.
// input.assigned_* fall back to the configured defaults when absent.
export async function createTask(input, actor) {
const config = await getConfig()
let assignedType = input.assigned_type || config.default_assigned_type || 'staff'
let assignedTo = input.assigned_to ?? null
let assignedToName = input.assigned_to_name ?? null
let contractorId = input.contractor_id ?? null
if (!input.assigned_type && assignedTo == null && contractorId == null) {
if (assignedType === 'contractor' && config.default_contractor_id) {
contractorId = config.default_contractor_id
} else if (config.default_assignee) {
assignedType = 'staff'
assignedTo = config.default_assignee
assignedToName = config.default_assignee_name || config.default_assignee
}
}
if (assignedType === 'contractor') { assignedTo = null; assignedToName = null }
else contractorId = null
const { rows } = await pool.query(
`INSERT INTO tasks (title, description, location_id, asset_id, template_id, priority, unusable,
due_date, assigned_type, assigned_to, assigned_to_name, contractor_id,
created_by, created_by_name)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING *`,
[
input.title, input.description || null, input.location_id, input.asset_id || null,
input.template_id || null, input.priority || 'medium', input.unusable === true,
input.due_date || null, assignedType, assignedTo, assignedToName, contractorId,
actor.email, actor.name,
]
)
const task = rows[0]
await logEvent(task.id, 'created', { toStatus: 'submitted', userName: actor.name })
const { rows: locRows } = await pool.query('SELECT name FROM locations WHERE id = $1', [task.location_id])
const locationName = locRows[0]?.name || `#${task.location_id}`
// Notifications are fire-and-forget; mailer swallows errors.
if (config.notify_on_assign) {
if (task.assigned_type === 'staff' && task.assigned_to && task.assigned_to !== actor.email) {
notifyAssignment(task, locationName, task.assigned_to)
notifyAssignmentPush(task, locationName)
} else if (task.assigned_type === 'contractor' && task.contractor_id) {
const { rows: c } = await pool.query('SELECT email FROM contractors WHERE id = $1', [task.contractor_id])
if (c[0]?.email) notifyAssignment(task, locationName, c[0].email)
}
}
if (config.notify_on_urgent && task.priority === 'urgent' && config.urgent_notify_email) {
notifyUrgent(task, locationName, config.urgent_notify_email)
}
return task
}