/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */ import ical, { ICalEventStatus } from 'ical-generator'; import getWorkOrderMilestones from '../../database/getWorkOrderMilestones.js'; import { getConfigProperty } from '../../helpers/functions.config.js'; import { getPrintConfig } from '../../helpers/functions.print.js'; const calendarCompany = 'cityssm.github.io'; const calendarProduct = getConfigProperty('application.applicationName'); const timeStringSplitRegex = /[ :-]/; function escapeHTML(stringToEscape) { return stringToEscape.replaceAll(/[^\d a-z]/gi, (c) => `${c.codePointAt(0)};`); } function getUrlRoot(request) { return `http://${request.hostname}${getConfigProperty('application.httpPort') === 80 ? '' : `:${getConfigProperty('application.httpPort')}`}${getConfigProperty('reverseProxy.urlPrefix')}`; } function getWorkOrderUrl(request, milestone) { return `${getUrlRoot(request)}/workOrders/${milestone.workOrderId}`; } function buildEventSummary(milestone) { let summary = (milestone.workOrderMilestoneCompletionDate ? '✔ ' : '') + ((milestone.workOrderMilestoneTypeId ?? -1) === -1 ? milestone.workOrderMilestoneDescription ?? '' : milestone.workOrderMilestoneType ?? '').trim(); let occupantCount = 0; for (const lotOccupancy of milestone.workOrderLotOccupancies ?? []) { for (const occupant of lotOccupancy.lotOccupancyOccupants ?? []) { occupantCount += 1; if (occupantCount === 1) { if (summary !== '') { summary += ': '; } summary += `${occupant.occupantName ?? ''} ${occupant.occupantFamilyName ?? ''}`; } } } if (occupantCount > 1) { summary += ` plus ${(occupantCount - 1).toString()}`; } return summary; } // eslint-disable-next-line @typescript-eslint/naming-convention function buildEventDescriptionHTML_occupancies(request, milestone) { let descriptionHTML = ''; if (milestone.workOrderLotOccupancies.length > 0) { const urlRoot = getUrlRoot(request); descriptionHTML = `
| ${escapeHTML(getConfigProperty('aliases.occupancy'))} Type | ${escapeHTML(getConfigProperty('aliases.lot'))} | Start Date | End Date | ${escapeHTML(getConfigProperty('aliases.occupants'))} |
|---|---|---|---|---|
| ${escapeHTML(occupancy.occupancyType ?? '')} | ${occupancy.lotName ? escapeHTML(occupancy.lotName) : '(Not Set)'} | ${occupancy.occupancyStartDateString} | ${occupancy.occupancyEndDate ? occupancy.occupancyEndDateString : '(No End Date)'} | `;
for (const occupant of occupancy.lotOccupancyOccupants ?? []) {
descriptionHTML += `${escapeHTML(occupant.lotOccupantType ?? '')}: ${escapeHTML(occupant.occupantName ?? '')} ${escapeHTML(occupant.occupantFamilyName ?? '')} `; } descriptionHTML += ' |
| ${escapeHTML(getConfigProperty('aliases.lot'))} | ${escapeHTML(getConfigProperty('aliases.map'))} | ${escapeHTML(getConfigProperty('aliases.lot'))} Type | Status |
|---|---|---|---|
| ${escapeHTML(lot.lotName ?? '')} | ${escapeHTML(lot.mapName ?? '')} | ${escapeHTML(lot.lotType ?? '')} | ${escapeHTML(lot.lotStatus ?? '')} |
${escapeHTML(printConfig.title)}
${urlRoot}/print/${printName}/?workOrderId=${milestone.workOrderId.toString()}
${escapeHTML(milestone.workOrderMilestoneDescription ?? '')}
${escapeHTML(milestone.workOrderDescription ?? '')}
${workOrderUrl}
`; descriptionHTML += buildEventDescriptionHTML_occupancies(request, milestone); descriptionHTML += buildEventDescriptionHTML_lots(request, milestone); descriptionHTML += buildEventDescriptionHTML_prints(request, milestone); return descriptionHTML; } function buildEventCategoryList(milestone) { const categories = []; if (milestone.workOrderMilestoneTypeId) { categories.push(milestone.workOrderMilestoneType ?? '', milestone.workOrderType ?? ''); } if (milestone.workOrderMilestoneCompletionDate) { categories.push('Completed'); } return categories; } function buildEventLocation(milestone) { const lotNames = []; if (milestone.workOrderLots.length > 0) { for (const lot of milestone.workOrderLots ?? []) { lotNames.push(`${lot.mapName ?? ''}: ${lot.lotName ?? ''}`); } } return lotNames.join(', '); } export default async function handler(request, response) { const urlRoot = getUrlRoot(request); /* * Get work order milestones */ const workOrderMilestoneFilters = { workOrderTypeIds: request.query.workOrderTypeIds, workOrderMilestoneTypeIds: request.query.workOrderMilestoneTypeIds }; if (request.query.workOrderId) { workOrderMilestoneFilters.workOrderId = request.query.workOrderId; workOrderMilestoneFilters.workOrderMilestoneDateFilter = 'notBlank'; } else { workOrderMilestoneFilters.workOrderMilestoneDateFilter = 'recent'; } const workOrderMilestones = await getWorkOrderMilestones(workOrderMilestoneFilters, { includeWorkOrders: true, orderBy: 'date' }); /* * Create calendar object */ const calendar = ical({ name: 'Work Order Milestone Calendar', url: `${urlRoot}/workOrders` }); if (request.query.workOrderId && workOrderMilestones.length > 0) { calendar.name(`Work Order #${workOrderMilestones[0].workOrderNumber}`); calendar.url(`${urlRoot}/workOrders/${workOrderMilestones[0].workOrderId?.toString()}`); } calendar.prodId({ company: calendarCompany, product: calendarProduct }); /* * Loop through milestones */ for (const milestone of workOrderMilestones) { const milestoneTimePieces = `${milestone.workOrderMilestoneDateString} ${milestone.workOrderMilestoneTimeString}`.split(timeStringSplitRegex); const milestoneDate = new Date(Number.parseInt(milestoneTimePieces[0], 10), Number.parseInt(milestoneTimePieces[1], 10) - 1, Number.parseInt(milestoneTimePieces[2], 10), Number.parseInt(milestoneTimePieces[3], 10), Number.parseInt(milestoneTimePieces[4], 10)); const milestoneEndDate = new Date(milestoneDate.getTime()); milestoneEndDate.setHours(milestoneEndDate.getHours() + 1); // Build summary (title in Outlook) const summary = buildEventSummary(milestone); // Build URL const workOrderUrl = getWorkOrderUrl(request, milestone); // Create event const eventData = { start: milestoneDate, created: new Date(milestone.recordCreate_timeMillis ?? 0), stamp: new Date(milestone.recordCreate_timeMillis ?? 0), lastModified: new Date(Math.max(milestone.recordUpdate_timeMillis ?? 0, milestone.workOrderRecordUpdate_timeMillis ?? 0)), allDay: !milestone.workOrderMilestoneTime, summary, url: workOrderUrl }; if (!eventData.allDay) { eventData.end = milestoneEndDate; } const calendarEvent = calendar.createEvent(eventData); // Build description const descriptionHTML = buildEventDescriptionHTML(request, milestone); calendarEvent.description({ plain: workOrderUrl, html: descriptionHTML }); // Set status if (milestone.workOrderMilestoneCompletionDate) { calendarEvent.status(ICalEventStatus.CONFIRMED); } // Add categories const categories = buildEventCategoryList(milestone); for (const category of categories) { calendarEvent.createCategory({ name: category }); } // Set location const location = buildEventLocation(milestone); calendarEvent.location(location); // Set organizer / attendees if (milestone.workOrderLotOccupancies.length > 0) { let organizerSet = false; for (const lotOccupancy of milestone.workOrderLotOccupancies ?? []) { for (const occupant of lotOccupancy.lotOccupancyOccupants ?? []) { if (organizerSet) { calendarEvent.createAttendee({ name: `${occupant.occupantName ?? ''} ${occupant.occupantFamilyName ?? ''}`, email: getConfigProperty('settings.workOrders.calendarEmailAddress') }); } else { calendarEvent.organizer({ name: `${occupant.occupantName ?? ''} ${occupant.occupantFamilyName ?? ''}`, email: getConfigProperty('settings.workOrders.calendarEmailAddress') }); organizerSet = true; } } } } else { calendarEvent.organizer({ name: milestone.recordCreate_userName ?? '', email: getConfigProperty('settings.workOrders.calendarEmailAddress') }); } } response.setHeader('Content-Disposition', `inline; filename=calendar.ics`); response.setHeader('Content-Type', 'text/calendar; charset=utf-8'); response.send(calendar.toString()); }