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 }