ics calndar export

deepsource-autofix-76c6eb20
Dan Gowans 2022-09-16 13:05:36 -04:00
parent f0f9c6dfe5
commit 08f5731867
5 changed files with 395 additions and 18 deletions

View File

@ -1,7 +1,17 @@
import ical, { ICalEventStatus } from "ical-generator";
import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js";
import * as configFunctions from "../../helpers/functions.config.js";
const timeStringSplitRegex = /[ :-]/;
function escapeHTML(stringToEscape) {
return stringToEscape.replace(/[^\d A-Za-z]/g, (c) => "&#" + c.codePointAt(0) + ";");
}
export const handler = (request, response) => {
const urlRoot = "http://" +
request.hostname +
(configFunctions.getProperty("application.httpPort") === 80
? ""
: ":" + configFunctions.getProperty("application.httpPort")) +
configFunctions.getProperty("reverseProxy.urlPrefix");
const workOrderMilestones = getWorkOrderMilestones({
workOrderMilestoneDateFilter: "recent",
workOrderTypeIds: request.query.workOrderTypeIds,
@ -9,21 +19,153 @@ export const handler = (request, response) => {
.workOrderMilestoneTypeIds
}, { includeWorkOrders: true, orderBy: "date" });
const calendar = ical({
name: "Work Order Milestone Calendar"
name: "Work Order Milestone Calendar",
url: urlRoot + "/workOrders"
});
calendar.prodId({
company: "cityssm.github.io",
product: configFunctions.getProperty("application.applicationName")
});
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));
let summary = (milestone.workOrderMilestoneTypeId
? milestone.workOrderMilestoneType
: milestone.workOrderMilestoneDescription).trim();
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
let occupantCount = 0;
for (const lotOccupancy of milestone.workOrder
.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);
}
}
const workOrderURL = urlRoot + "/workOrders/" + milestone.workOrderId;
const eventData = {
start: milestoneDate,
created: new Date(milestone.recordCreate_timeMillis),
stamp: new Date(milestone.recordCreate_timeMillis),
lastModified: new Date(milestone.recordUpdate_timeMillis),
lastModified: new Date(Math.max(milestone.recordUpdate_timeMillis, milestone.workOrder.recordUpdate_timeMillis)),
allDay: !milestone.workOrderMilestoneTime,
summary: milestone.workOrderMilestoneDescription
summary,
url: workOrderURL
};
const calendarEvent = calendar.createEvent(eventData);
let descriptionHTML = "<h1>Milestone Description</h1>" +
"<p>" +
escapeHTML(milestone.workOrderMilestoneDescription) +
"</p>" +
"<h2>Work Order #" +
milestone.workOrder.workOrderNumber +
"</h2>" +
("<p>" +
escapeHTML(milestone.workOrder.workOrderDescription) +
"</p>") +
('<p><a href="' + workOrderURL + '">' + workOrderURL + "</a></p>");
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
descriptionHTML +=
"<h2>Related " +
escapeHTML(configFunctions.getProperty("aliases.occupancies")) +
"</h2>" +
'<table border="1"><thead><tr>' +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.occupancy")) +
" Type</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
"</th>") +
"<th>Start Date</th>" +
"<th>End Date</th>" +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.occupants")) +
"</th>") +
"</tr></thead>" +
"<tbody>";
for (const occupancy of milestone.workOrder
.workOrderLotOccupancies) {
descriptionHTML +=
"<tr>" +
("<td>" +
'<a href="' +
urlRoot +
"/lotOccupancies/" +
occupancy.lotOccupancyId +
'">' +
escapeHTML(occupancy.occupancyType) +
"</a></td>") +
("<td>" +
(occupancy.lotName
? escapeHTML(occupancy.lotName)
: "(Not Set)") +
"</td>") +
("<td>" + occupancy.occupancyStartDateString + "</td>") +
"<td>" +
(occupancy.occupancyEndDate
? occupancy.occupancyEndDateString
: "(No End Date)") +
"</td>" +
"<td>";
for (const occupant of occupancy.lotOccupancyOccupants) {
descriptionHTML +=
escapeHTML(occupant.occupantName) + "<br />";
}
descriptionHTML += "</td>" + "</tr>";
}
descriptionHTML += "</tbody></table>";
}
if (milestone.workOrder.workOrderLots.length > 0) {
descriptionHTML +=
"<h2>Related " +
escapeHTML(configFunctions.getProperty("aliases.lots")) +
"</h2>" +
'<table border="1"><thead><tr>' +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
" Type</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.map")) +
"</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
" Type" +
"</th>") +
"<th>Status</th>" +
"</tr></thead>" +
"<tbody>";
for (const lot of milestone.workOrder.workOrderLots) {
descriptionHTML +=
"<tr>" +
("<td>" +
'<a href="' +
urlRoot +
"/lots/" +
lot.lotId +
'">' +
escapeHTML(lot.lotName) +
"</a></td>") +
("<td>" + escapeHTML(lot.mapName) + "</td>") +
("<td>" + escapeHTML(lot.lotType) + "</td>") +
("<td>" + escapeHTML(lot.lotStatus) + "</td>") +
"</tr>";
}
descriptionHTML += "</tbody></table>";
}
calendarEvent.description({
plain: workOrderURL,
html: descriptionHTML
});
if (milestone.workOrderMilestoneCompletionDate) {
calendarEvent.status(ICalEventStatus.CONFIRMED);
}
@ -31,6 +173,9 @@ export const handler = (request, response) => {
calendarEvent.createCategory({
name: milestone.workOrderMilestoneType
});
calendarEvent.createCategory({
name: milestone.workOrder.workOrderType
});
}
if (milestone.workOrder.workOrderLots.length > 0) {
const lotNames = [];
@ -40,17 +185,33 @@ export const handler = (request, response) => {
calendarEvent.location(lotNames.join(", "));
}
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
let organizerSet = false;
for (const lotOccupancy of milestone.workOrder
.workOrderLotOccupancies) {
for (const occupants of lotOccupancy.lotOccupancyOccupants) {
for (const occupant of lotOccupancy.lotOccupancyOccupants) {
if (organizerSet) {
calendarEvent.createAttendee({
name: occupants.occupantName,
email: "no-reply@example.com"
name: occupant.occupantName,
email: "no-reply@127.0.0.1"
});
}
else {
calendarEvent.organizer({
name: occupant.occupantName,
email: "no-reply@127.0.0.1"
});
organizerSet = true;
}
}
}
}
else {
calendarEvent.organizer({
name: milestone.recordCreate_userName,
email: "no-reply@127.0.0.1"
});
}
}
calendar.serve(response);
};
export default handler;

View File

@ -7,9 +7,26 @@ import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrde
import type { RequestHandler } from "express";
import { dateIntegerToString } from "@cityssm/expressjs-server-js/dateTimeFns.js";
import * as configFunctions from "../../helpers/functions.config.js";
const timeStringSplitRegex = /[ :-]/;
function escapeHTML(stringToEscape: string) {
return stringToEscape.replace(
/[^\d A-Za-z]/g,
(c) => "&#" + c.codePointAt(0) + ";"
);
}
export const handler: RequestHandler = (request, response) => {
const urlRoot =
"http://" +
request.hostname +
(configFunctions.getProperty("application.httpPort") === 80
? ""
: ":" + configFunctions.getProperty("application.httpPort")) +
configFunctions.getProperty("reverseProxy.urlPrefix");
const workOrderMilestones = getWorkOrderMilestones(
{
workOrderMilestoneDateFilter: "recent",
@ -21,7 +38,13 @@ export const handler: RequestHandler = (request, response) => {
);
const calendar = ical({
name: "Work Order Milestone Calendar"
name: "Work Order Milestone Calendar",
url: urlRoot + "/workOrders"
});
calendar.prodId({
company: "cityssm.github.io",
product: configFunctions.getProperty("application.applicationName")
});
for (const milestone of workOrderMilestones) {
@ -39,26 +62,200 @@ export const handler: RequestHandler = (request, response) => {
Number.parseInt(milestoneTimePieces[4], 10)
);
// Build summary (title in Outlook)
let summary = (
milestone.workOrderMilestoneTypeId
? milestone.workOrderMilestoneType
: milestone.workOrderMilestoneDescription
).trim();
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
let occupantCount = 0;
for (const lotOccupancy of milestone.workOrder
.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);
}
}
// Build URL
const workOrderURL = urlRoot + "/workOrders/" + milestone.workOrderId;
// Create event
const eventData: ICalEventData = {
start: milestoneDate,
created: new Date(milestone.recordCreate_timeMillis),
stamp: new Date(milestone.recordCreate_timeMillis),
lastModified: new Date(milestone.recordUpdate_timeMillis),
lastModified: new Date(
Math.max(
milestone.recordUpdate_timeMillis,
milestone.workOrder.recordUpdate_timeMillis
)
),
allDay: !milestone.workOrderMilestoneTime,
summary: milestone.workOrderMilestoneDescription
summary,
url: workOrderURL
};
const calendarEvent = calendar.createEvent(eventData);
// Build description
let descriptionHTML =
"<h1>Milestone Description</h1>" +
"<p>" +
escapeHTML(milestone.workOrderMilestoneDescription) +
"</p>" +
"<h2>Work Order #" +
milestone.workOrder.workOrderNumber +
"</h2>" +
("<p>" +
escapeHTML(milestone.workOrder.workOrderDescription) +
"</p>") +
('<p><a href="' + workOrderURL + '">' + workOrderURL + "</a></p>");
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
descriptionHTML +=
"<h2>Related " +
escapeHTML(configFunctions.getProperty("aliases.occupancies")) +
"</h2>" +
'<table border="1"><thead><tr>' +
("<th>" +
escapeHTML(
configFunctions.getProperty("aliases.occupancy")
) +
" Type</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
"</th>") +
"<th>Start Date</th>" +
"<th>End Date</th>" +
("<th>" +
escapeHTML(
configFunctions.getProperty("aliases.occupants")
) +
"</th>") +
"</tr></thead>" +
"<tbody>";
for (const occupancy of milestone.workOrder
.workOrderLotOccupancies) {
descriptionHTML +=
"<tr>" +
("<td>" +
'<a href="' +
urlRoot +
"/lotOccupancies/" +
occupancy.lotOccupancyId +
'">' +
escapeHTML(occupancy.occupancyType) +
"</a></td>") +
("<td>" +
(occupancy.lotName
? escapeHTML(occupancy.lotName)
: "(Not Set)") +
"</td>") +
("<td>" + occupancy.occupancyStartDateString + "</td>") +
"<td>" +
(occupancy.occupancyEndDate
? occupancy.occupancyEndDateString
: "(No End Date)") +
"</td>" +
"<td>";
for (const occupant of occupancy.lotOccupancyOccupants) {
descriptionHTML +=
escapeHTML(occupant.occupantName) + "<br />";
}
descriptionHTML += "</td>" + "</tr>";
}
descriptionHTML += "</tbody></table>";
}
if (milestone.workOrder.workOrderLots.length > 0) {
descriptionHTML +=
"<h2>Related " +
escapeHTML(configFunctions.getProperty("aliases.lots")) +
"</h2>" +
'<table border="1"><thead><tr>' +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
" Type</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.map")) +
"</th>") +
("<th>" +
escapeHTML(configFunctions.getProperty("aliases.lot")) +
" Type" +
"</th>") +
"<th>Status</th>" +
"</tr></thead>" +
"<tbody>";
for (const lot of milestone.workOrder.workOrderLots) {
descriptionHTML +=
"<tr>" +
("<td>" +
'<a href="' +
urlRoot +
"/lots/" +
lot.lotId +
'">' +
escapeHTML(lot.lotName) +
"</a></td>") +
("<td>" + escapeHTML(lot.mapName) + "</td>") +
("<td>" + escapeHTML(lot.lotType) + "</td>") +
("<td>" + escapeHTML(lot.lotStatus) + "</td>") +
"</tr>";
}
descriptionHTML += "</tbody></table>";
}
calendarEvent.description({
plain: workOrderURL,
html: descriptionHTML
});
// Set status
if (milestone.workOrderMilestoneCompletionDate) {
calendarEvent.status(ICalEventStatus.CONFIRMED);
}
// Add categories
if (milestone.workOrderMilestoneTypeId) {
calendarEvent.createCategory({
name: milestone.workOrderMilestoneType
});
calendarEvent.createCategory({
name: milestone.workOrder.workOrderType
});
}
// Set location
if (milestone.workOrder.workOrderLots.length > 0) {
const lotNames = [];
@ -69,17 +266,33 @@ export const handler: RequestHandler = (request, response) => {
calendarEvent.location(lotNames.join(", "));
}
// Set organizer / attendees
if (milestone.workOrder.workOrderLotOccupancies.length > 0) {
let organizerSet = false;
for (const lotOccupancy of milestone.workOrder
.workOrderLotOccupancies) {
for (const occupants of lotOccupancy.lotOccupancyOccupants) {
for (const occupant of lotOccupancy.lotOccupancyOccupants) {
if (organizerSet) {
calendarEvent.createAttendee({
name: occupants.occupantName,
email: "no-reply@example.com"
name: occupant.occupantName,
email: "no-reply@127.0.0.1"
});
} else {
calendarEvent.organizer({
name: occupant.occupantName,
email: "no-reply@127.0.0.1"
});
organizerSet = true;
}
}
}
} else {
calendarEvent.organizer({
name: milestone.recordCreate_userName,
email: "no-reply@127.0.0.1"
});
}
}
calendar.serve(response);

View File

@ -9,7 +9,8 @@ const baseSQL = "select w.workOrderId," +
" w.workOrderTypeId, t.workOrderType," +
" w.workOrderNumber, w.workOrderDescription," +
" w.workOrderOpenDate, userFn_dateIntegerToString(w.workOrderOpenDate) as workOrderOpenDateString," +
" w.workOrderCloseDate, userFn_dateIntegerToString(w.workOrderCloseDate) as workOrderCloseDateString" +
" w.workOrderCloseDate, userFn_dateIntegerToString(w.workOrderCloseDate) as workOrderCloseDateString," +
" w.recordUpdate_timeMillis" +
" from WorkOrders w" +
" left join WorkOrderTypes t on w.workOrderTypeId = t.workOrderTypeId" +
" where w.recordDelete_timeMillis is null";

View File

@ -25,7 +25,8 @@ const baseSQL =
" w.workOrderTypeId, t.workOrderType," +
" w.workOrderNumber, w.workOrderDescription," +
" w.workOrderOpenDate, userFn_dateIntegerToString(w.workOrderOpenDate) as workOrderOpenDateString," +
" w.workOrderCloseDate, userFn_dateIntegerToString(w.workOrderCloseDate) as workOrderCloseDateString" +
" w.workOrderCloseDate, userFn_dateIntegerToString(w.workOrderCloseDate) as workOrderCloseDateString," +
" w.recordUpdate_timeMillis" +
" from WorkOrders w" +
" left join WorkOrderTypes t on w.workOrderTypeId = t.workOrderTypeId" +
" where w.recordDelete_timeMillis is null";

View File

@ -27,6 +27,7 @@
</h1>
<div class="panel" id="panel--icsFilters">
<h2 class="panel-heading">Outlook Calendar (ICS) Integration</h2>
<div class="panel-block is-block">
<div class="columns">
<div class="column">