/* eslint-disable unicorn/filename-case */ import ical, { ICalEventData, ICalEventStatus } from "ical-generator"; import { getWorkOrderMilestones, WorkOrderMilestoneFilters } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js"; import type { RequestHandler, Request } from "express"; import * as configFunctions from "../../helpers/functions.config.js"; import { getPrintConfig } from "../../helpers/functions.print.js"; import type * as recordTypes from "../../types/recordTypes"; const calendarCompany = "cityssm.github.io"; const calendarProduct = configFunctions.getProperty("application.applicationName"); const timeStringSplitRegex = /[ :-]/; function escapeHTML(stringToEscape: string) { return stringToEscape.replace(/[^\d A-Za-z]/g, (c) => "" + c.codePointAt(0) + ";"); } function getUrlRoot(request: Request): string { return ( "http://" + request.hostname + (configFunctions.getProperty("application.httpPort") === 80 ? "" : ":" + configFunctions.getProperty("application.httpPort")) + configFunctions.getProperty("reverseProxy.urlPrefix") ); } function getWorkOrderUrl(request: Request, milestone: recordTypes.WorkOrderMilestone) { return getUrlRoot(request) + "/workOrders/" + milestone.workOrderId; } function buildEventSummary(milestone: recordTypes.WorkOrderMilestone): string { let summary = (milestone.workOrderMilestoneCompletionDate ? "✔ " : "") + (milestone.workOrderMilestoneTypeId ? milestone.workOrderMilestoneType : milestone.workOrderMilestoneDescription ).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; } } } if (occupantCount > 1) { summary += " plus " + (occupantCount - 1); } return summary; } function buildEventDescriptionHTML_occupancies( request: Request, milestone: recordTypes.WorkOrderMilestone ): string { let descriptionHTML = ""; if (milestone.workOrderLotOccupancies.length > 0) { const urlRoot = getUrlRoot(request); descriptionHTML = `
| ${escapeHTML(configFunctions.getProperty("aliases.occupancy"))} Type | ${escapeHTML(configFunctions.getProperty("aliases.lot"))} | Start Date | End Date | ${escapeHTML(configFunctions.getProperty("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) +
" "; } descriptionHTML += " |
| ${escapeHTML(configFunctions.getProperty("aliases.lot"))} Type | ${escapeHTML(configFunctions.getProperty("aliases.map"))} | ${escapeHTML(configFunctions.getProperty("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) +
"
${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: recordTypes.WorkOrderMilestone): string[] { const categories: string[] = []; if (milestone.workOrderMilestoneTypeId) { categories.push(milestone.workOrderMilestoneType, milestone.workOrderType); } if (milestone.workOrderMilestoneCompletionDate) { categories.push("Completed"); } return categories; } function buildEventLocation(milestone: recordTypes.WorkOrderMilestone): string { const lotNames = []; if (milestone.workOrderLots.length > 0) { for (const lot of milestone.workOrderLots) { lotNames.push(lot.mapName + ": " + lot.lotName); } } return lotNames.join(", "); } export const handler: RequestHandler = (request, response) => { const urlRoot = getUrlRoot(request); /* * Get work order milestones */ const workOrderMilestoneFilters: WorkOrderMilestoneFilters = { workOrderTypeIds: request.query.workOrderTypeIds as string, workOrderMilestoneTypeIds: request.query.workOrderMilestoneTypeIds as string }; if (request.query.workOrderId) { workOrderMilestoneFilters.workOrderId = request.query.workOrderId as string; } else { workOrderMilestoneFilters.workOrderMilestoneDateFilter = "recent"; } const workOrderMilestones = 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); } 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: ICalEventData = { start: milestoneDate, created: new Date(milestone.recordCreate_timeMillis), stamp: new Date(milestone.recordCreate_timeMillis), lastModified: new Date( Math.max( milestone.recordUpdate_timeMillis, milestone.workOrderRecordUpdate_timeMillis ) ), 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, email: configFunctions.getProperty( "settings.workOrders.calendarEmailAddress" ) }); } else { calendarEvent.organizer({ name: occupant.occupantName, email: configFunctions.getProperty( "settings.workOrders.calendarEmailAddress" ) }); organizerSet = true; } } } } else { calendarEvent.organizer({ name: milestone.recordCreate_userName, email: configFunctions.getProperty("settings.workOrders.calendarEmailAddress") }); } } calendar.serve(response); }; export default handler;