work order development

deepsource-autofix-76c6eb20
Dan Gowans 2022-09-09 16:11:53 -04:00
parent c4e6324112
commit ab87f933c7
34 changed files with 1282 additions and 19 deletions

View File

@ -1,3 +1,4 @@
import { getWorkOrderTypes } from "../../helpers/functions.cache.js";
import * as configFunctions from "../../helpers/functions.config.js";
import { getWorkOrder } from "../../helpers/lotOccupancyDB/getWorkOrder.js";
export const handler = (request, response) => {
@ -6,9 +7,16 @@ export const handler = (request, response) => {
return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") +
"/workOrders/?error=workOrderIdNotFound");
}
if (workOrder.workOrderCloseDate) {
return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") +
"/workOrders/" + workOrder.workOrderId.toString() + "/?error=workOrderIsClosed");
}
const workOrderTypes = getWorkOrderTypes();
response.render("workOrder-edit", {
headTitle: "Work Order #" + workOrder.workOrderNumber,
workOrder
workOrder,
isCreate: false,
workOrderTypes
});
};
export default handler;

View File

@ -1,4 +1,5 @@
import type { RequestHandler } from "express";
import { getWorkOrderTypes } from "../../helpers/functions.cache.js";
import * as configFunctions from "../../helpers/functions.config.js";
@ -14,9 +15,20 @@ export const handler: RequestHandler = (request, response) => {
);
}
if (workOrder.workOrderCloseDate) {
return response.redirect(
configFunctions.getProperty("reverseProxy.urlPrefix") +
"/workOrders/" + workOrder.workOrderId.toString() + "/?error=workOrderIsClosed"
);
}
const workOrderTypes = getWorkOrderTypes();
response.render("workOrder-edit", {
headTitle: "Work Order #" + workOrder.workOrderNumber,
workOrder
workOrder,
isCreate: false,
workOrderTypes
});
};

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,16 @@
import { deleteWorkOrderLot } from "../../helpers/lotOccupancyDB/deleteWorkOrderLot.js";
import { getLots } from "../../helpers/lotOccupancyDB/getLots.js";
export const handler = async (request, response) => {
const success = deleteWorkOrderLot(request.body.workOrderId, request.body.lotId, request.session);
const workOrderLots = getLots({
workOrderId: request.body.workOrderId
}, {
limit: -1,
offset: 0
}).lots;
response.json({
success,
workOrderLots
});
};
export default handler;

View File

@ -0,0 +1,29 @@
import type { RequestHandler } from "express";
import { deleteWorkOrderLot } from "../../helpers/lotOccupancyDB/deleteWorkOrderLot.js";
import { getLots } from "../../helpers/lotOccupancyDB/getLots.js";
export const handler: RequestHandler = async (request, response) => {
const success = deleteWorkOrderLot(
request.body.workOrderId,
request.body.lotId,
request.session
);
const workOrderLots = getLots(
{
workOrderId: request.body.workOrderId
},
{
limit: -1,
offset: 0
}
).lots;
response.json({
success,
workOrderLots
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,17 @@
import { deleteWorkOrderLotOccupancy } from "../../helpers/lotOccupancyDB/deleteWorkOrderLotOccupancy.js";
import { getLotOccupancies } from "../../helpers/lotOccupancyDB/getLotOccupancies.js";
export const handler = async (request, response) => {
const success = deleteWorkOrderLotOccupancy(request.body.workOrderId, request.body.lotOccupancyId, request.session);
const workOrderLotOccupancies = getLotOccupancies({
workOrderId: request.body.workOrderId
}, {
limit: -1,
offset: 0,
includeOccupants: true
}).lotOccupancies;
response.json({
success,
workOrderLotOccupancies
});
};
export default handler;

View File

@ -0,0 +1,30 @@
import type { RequestHandler } from "express";
import { deleteWorkOrderLotOccupancy } from "../../helpers/lotOccupancyDB/deleteWorkOrderLotOccupancy.js";
import { getLotOccupancies } from "../../helpers/lotOccupancyDB/getLotOccupancies.js";
export const handler: RequestHandler = async (request, response) => {
const success = deleteWorkOrderLotOccupancy(
request.body.workOrderId,
request.body.lotOccupancyId,
request.session
);
const workOrderLotOccupancies = getLotOccupancies(
{
workOrderId: request.body.workOrderId
},
{
limit: -1,
offset: 0,
includeOccupants: true
}
).lotOccupancies;
response.json({
success,
workOrderLotOccupancies
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,9 @@
import { reopenWorkOrder } from "../../helpers/lotOccupancyDB/reopenWorkOrder.js";
export const handler = async (request, response) => {
const success = reopenWorkOrder(request.body.workOrderId, request.session);
response.json({
success,
workOrderId: request.body.workOrderId
});
};
export default handler;

View File

@ -0,0 +1,14 @@
import type { RequestHandler } from "express";
import { reopenWorkOrder } from "../../helpers/lotOccupancyDB/reopenWorkOrder.js";
export const handler: RequestHandler = async (request, response) => {
const success = reopenWorkOrder(request.body.workOrderId, request.session);
response.json({
success,
workOrderId: request.body.workOrderId
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,9 @@
import { updateWorkOrder } from "../../helpers/lotOccupancyDB/updateWorkOrder.js";
export const handler = async (request, response) => {
const success = updateWorkOrder(request.body, request.session);
response.json({
success,
workOrderId: request.body.workOrderId
});
};
export default handler;

View File

@ -0,0 +1,14 @@
import type { RequestHandler } from "express";
import { updateWorkOrder } from "../../helpers/lotOccupancyDB/updateWorkOrder.js";
export const handler: RequestHandler = async (request, response) => {
const success = updateWorkOrder(request.body, request.session);
response.json({
success,
workOrderId: request.body.workOrderId
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type * as recordTypes from "../../types/recordTypes";
export declare const deleteWorkOrderLot: (workOrderId: number | string, lotId: number | string, requestSession: recordTypes.PartialSession) => boolean;
export default deleteWorkOrderLot;

View File

@ -0,0 +1,16 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const deleteWorkOrderLot = (workOrderId, lotId, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update WorkOrderLots" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where workOrderId = ?" +
" and lotId = ?")
.run(requestSession.user.userName, rightNowMillis, workOrderId, lotId);
database.close();
return result.changes > 0;
};
export default deleteWorkOrderLot;

View File

@ -0,0 +1,31 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
export const deleteWorkOrderLot = (
workOrderId: number | string,
lotId: number | string,
requestSession: recordTypes.PartialSession
): boolean => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare(
"update WorkOrderLots" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where workOrderId = ?" +
" and lotId = ?"
)
.run(requestSession.user.userName, rightNowMillis, workOrderId, lotId);
database.close();
return result.changes > 0;
};
export default deleteWorkOrderLot;

View File

@ -0,0 +1,3 @@
import type * as recordTypes from "../../types/recordTypes";
export declare const deleteWorkOrderLotOccupancy: (workOrderId: number | string, lotOccupancyId: number | string, requestSession: recordTypes.PartialSession) => boolean;
export default deleteWorkOrderLotOccupancy;

View File

@ -0,0 +1,16 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const deleteWorkOrderLotOccupancy = (workOrderId, lotOccupancyId, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update WorkOrderLotOccupancies" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where workOrderId = ?" +
" and lotOccupancyId = ?")
.run(requestSession.user.userName, rightNowMillis, workOrderId, lotOccupancyId);
database.close();
return result.changes > 0;
};
export default deleteWorkOrderLotOccupancy;

View File

@ -0,0 +1,31 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
export const deleteWorkOrderLotOccupancy = (
workOrderId: number | string,
lotOccupancyId: number | string,
requestSession: recordTypes.PartialSession
): boolean => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare(
"update WorkOrderLotOccupancies" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where workOrderId = ?" +
" and lotOccupancyId = ?"
)
.run(requestSession.user.userName, rightNowMillis, workOrderId, lotOccupancyId);
database.close();
return result.changes > 0;
};
export default deleteWorkOrderLotOccupancy;

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,273 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const workOrderId = document.querySelector("#workOrderEdit--workOrderId").value;
const isCreate = workOrderId === "";
document
.querySelector("#form--workOrderEdit")
.addEventListener("submit", (submitEvent) => {
submitEvent.preventDefault();
cityssm.postJSON(urlPrefix +
"/workOrders/" +
(isCreate ? "doCreateWorkOrder" : "doUpdateWorkOrder"), submitEvent.currentTarget, (responseJSON) => {
if (responseJSON.success) {
if (isCreate) {
window.location.href =
urlPrefix +
"/workOrders/" +
responseJSON.workOrderId +
"/edit";
}
else {
bulmaJS.alert({
message: "Work Order Updated Successfully",
contextualColorName: "success"
});
}
}
else {
bulmaJS.alert({
title: "Error Updating Work Order",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
});
if (!isCreate) {
let workOrderLots = exports.workOrderLots;
delete exports.workOrderLots;
let workOrderLotOccupancies = exports.workOrderLotOccupancies;
delete exports.workOrderLotOccupancies;
const deleteLotOccupancy = (clickEvent) => {
const lotOccupancyId = clickEvent.currentTarget.closest(".container--lotOccupancy").dataset.lotOccupancyId;
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/workOrders/doDeleteWorkOrderLotOccupancy", {
workOrderId,
lotOccupancyId
}, (responseJSON) => {
if (responseJSON.success) {
workOrderLotOccupancies =
responseJSON.workOrderLotOccupancies;
renderRelatedLotsAndOccupancies();
}
else {
bulmaJS.alert({
title: "Error Deleting Relationship",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
bulmaJS.confirm({
title: "Delete " +
exports.aliases.lot +
" " +
exports.aliases.occupancy +
" Relationship",
message: "Are you sure you want to remove the relationship to this " +
exports.aliases.lot.toLowerCase() +
" " +
exports.aliases.occupancy.toLowerCase() +
" record from this work order? Note that the record will remain.",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete Relationship",
callbackFunction: doDelete
}
});
};
const renderRelatedOccupancies = () => {
const occupanciesContainerElement = document.querySelector("#relatedTab--lotOccupancies");
document.querySelector(".tabs a[href='#relatedTab--lotOccupancies'] .tag").textContent = workOrderLotOccupancies.length.toString();
if (workOrderLotOccupancies.length === 0) {
occupanciesContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no ' +
exports.aliases.occupancies.toLowerCase() +
" associated with this work order.</p>" +
"</div>";
return;
}
occupanciesContainerElement.innerHTML =
'<table class="table is-fullwidth is-striped is-hoverable">' +
"<thead>" +
"<tr>" +
'<th class="has-width-1"></th>' +
("<th>" + exports.aliases.occupancy + " Type</th>") +
("<th>" + exports.aliases.lot + "</th>") +
"<th>Start Date</th>" +
"<th>End Date</th>" +
("<th>" + exports.aliases.occupants + "</th>") +
'<th class="has-width-1"></th>' +
"</tr>" +
"</thead>" +
"<tbody></tbody>" +
"</table>";
const currentDateString = cityssm.dateToString(new Date());
for (const lotOccupancy of workOrderLotOccupancies) {
const rowElement = document.createElement("tr");
rowElement.className = "container--lotOccupancy";
rowElement.dataset.lotOccupancyId =
lotOccupancy.lotOccupancyId.toString();
const isActive = !(lotOccupancy.occupancyEndDate &&
lotOccupancy.occupancyEndDateString < currentDateString);
rowElement.innerHTML =
'<td class="has-text-centered">' +
(isActive
? '<i class="fas fa-play" title="Current ' +
cityssm.escapeHTML(exports.aliases.occupancy) +
'"></i>'
: '<i class="fas fa-stop" title="Previous ' +
cityssm.escapeHTML(exports.aliases.occupancy) +
'"></i>') +
"</td>" +
("<td>" +
'<a class="has-text-weight-bold" href="' +
cityssm.escapeHTML(urlPrefix) +
"/lotOccupancies/" +
lotOccupancy.lotOccupancyId +
'">' +
cityssm.escapeHTML(lotOccupancy.occupancyType) +
"</a>" +
"</td>") +
("<td>" +
(lotOccupancy.lotId
? cityssm.escapeHTML(lotOccupancy.lotName)
: '<span class="has-text-grey">(No ' +
exports.aliases.lot +
")</span>") +
"</td>") +
("<td>" + lotOccupancy.occupancyStartDateString + "</td>") +
("<td>" +
(lotOccupancy.occupancyEndDate
? lotOccupancy.occupancyEndDateString
: '<span class="has-text-grey">(No End Date)</span>') +
"</td>") +
("<td>" +
(lotOccupancy.lotOccupancyOccupants.length === 0
? '<span class="has-text-grey">(No ' +
cityssm.escapeHTML(exports.aliases.occupants) +
")</span>"
: cityssm.escapeHTML(lotOccupancy.lotOccupancyOccupants[0]
.occupantName) +
(lotOccupancy.lotOccupancyOccupants.length > 1
? " plus " +
(lotOccupancy.lotOccupancyOccupants.length -
1)
: "")) +
"</td>") +
("<td>" +
'<button class="button is-small is-light is-danger button--deleteLotOccupancy" data-tooltip="Delete Relationship" type="button">' +
'<i class="fas fa-trash" aria-hidden="true"></i>' +
"</button>" +
"</td>");
rowElement
.querySelector(".button--deleteLotOccupancy")
.addEventListener("click", deleteLotOccupancy);
occupanciesContainerElement
.querySelector("tbody")
.append(rowElement);
}
};
const deleteLot = (clickEvent) => {
const lotId = clickEvent.currentTarget.closest(".container--lot").dataset.lotId;
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/workOrders/doDeleteWorkOrderLot", {
workOrderId,
lotId
}, (responseJSON) => {
if (responseJSON.success) {
workOrderLots =
responseJSON.workOrderLots;
renderRelatedLotsAndOccupancies();
}
else {
bulmaJS.alert({
title: "Error Deleting Relationship",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
bulmaJS.confirm({
title: "Delete " +
exports.aliases.lot +
" " +
exports.aliases.occupancy +
" Relationship",
message: "Are you sure you want to remove the relationship to this " +
exports.aliases.lot.toLowerCase() +
" " +
exports.aliases.occupancy.toLowerCase() +
" record from this work order? Note that the record will remain.",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete Relationship",
callbackFunction: doDelete
}
});
};
const renderRelatedLots = () => {
const lotsContainerElement = document.querySelector("#relatedTab--lots");
document.querySelector(".tabs a[href='#relatedTab--lots'] .tag").textContent = workOrderLots.length.toString();
if (workOrderLots.length === 0) {
lotsContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no ' +
exports.aliases.lots.toLowerCase() +
" associated with this work order.</p>" +
"</div>";
return;
}
lotsContainerElement.innerHTML =
'<table class="table is-fullwidth is-striped is-hoverable">' +
"<thead>" +
"<tr>" +
("<th>" + exports.aliases.lot + "</th>") +
("<th>" + exports.aliases.map + "</th>") +
("<th>" + exports.aliases.lot + " Type</th>") +
"<th>Status</th>" +
'<th class="has-width-1"></th>' +
"</tr>" +
"</thead>" +
"<tbody></tbody>" +
"</table>";
for (const lot of workOrderLots) {
const rowElement = document.createElement("tr");
rowElement.className = "container--lot";
rowElement.dataset.lotId = lot.lotId.toString();
rowElement.innerHTML =
"<td>" +
'<a class="has-text-weight-bold" href="' +
cityssm.escapeHTML(urlPrefix) +
"/lots/" +
lot.lotId +
'">' +
cityssm.escapeHTML(lot.lotName) +
"</a>" +
"</td>" +
("<td>" + cityssm.escapeHTML(lot.mapName) + "</td>") +
("<td>" + cityssm.escapeHTML(lot.lotType) + "</td>") +
("<td>" + cityssm.escapeHTML(lot.lotStatus) + "</td>") +
("<td>" +
'<button class="button is-small is-light is-danger button--deleteLot" data-tooltip="Delete Relationship" type="button">' +
'<i class="fas fa-trash" aria-hidden="true"></i>' +
"</button>" +
"</td>");
rowElement
.querySelector(".button--deleteLot")
.addEventListener("click", deleteLot);
lotsContainerElement.querySelector("tbody").append(rowElement);
}
};
const renderRelatedLotsAndOccupancies = () => {
renderRelatedOccupancies();
renderRelatedLots();
};
renderRelatedLotsAndOccupancies();
}
})();

View File

@ -0,0 +1,376 @@
/* eslint-disable unicorn/prefer-module */
import type { cityssmGlobal } from "@cityssm/bulma-webapp-js/src/types";
import type { BulmaJS } from "@cityssm/bulma-js/types";
import type * as recordTypes from "../types/recordTypes";
declare const cityssm: cityssmGlobal;
declare const bulmaJS: BulmaJS;
(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const workOrderId = (
document.querySelector(
"#workOrderEdit--workOrderId"
) as HTMLInputElement
).value;
const isCreate = workOrderId === "";
document
.querySelector("#form--workOrderEdit")
.addEventListener("submit", (submitEvent) => {
submitEvent.preventDefault();
cityssm.postJSON(
urlPrefix +
"/workOrders/" +
(isCreate ? "doCreateWorkOrder" : "doUpdateWorkOrder"),
submitEvent.currentTarget,
(responseJSON: {
success: boolean;
workOrderId?: number;
errorMessage?: string;
}) => {
if (responseJSON.success) {
if (isCreate) {
window.location.href =
urlPrefix +
"/workOrders/" +
responseJSON.workOrderId +
"/edit";
} else {
bulmaJS.alert({
message: "Work Order Updated Successfully",
contextualColorName: "success"
});
}
} else {
bulmaJS.alert({
title: "Error Updating Work Order",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
}
);
});
/*
* Related Lots
*/
if (!isCreate) {
let workOrderLots: recordTypes.Lot[] = exports.workOrderLots;
delete exports.workOrderLots;
let workOrderLotOccupancies: recordTypes.LotOccupancy[] =
exports.workOrderLotOccupancies;
delete exports.workOrderLotOccupancies;
const deleteLotOccupancy = (clickEvent: Event) => {
const lotOccupancyId = (
(clickEvent.currentTarget as HTMLElement).closest(
".container--lotOccupancy"
) as HTMLElement
).dataset.lotOccupancyId;
const doDelete = () => {
cityssm.postJSON(
urlPrefix + "/workOrders/doDeleteWorkOrderLotOccupancy",
{
workOrderId,
lotOccupancyId
},
(responseJSON: {
success: boolean;
errorMessage?: string;
workOrderLotOccupancies?: recordTypes.LotOccupancy[];
}) => {
if (responseJSON.success) {
workOrderLotOccupancies =
responseJSON.workOrderLotOccupancies;
renderRelatedLotsAndOccupancies();
} else {
bulmaJS.alert({
title: "Error Deleting Relationship",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
}
);
};
bulmaJS.confirm({
title:
"Delete " +
exports.aliases.lot +
" " +
exports.aliases.occupancy +
" Relationship",
message:
"Are you sure you want to remove the relationship to this " +
exports.aliases.lot.toLowerCase() +
" " +
exports.aliases.occupancy.toLowerCase() +
" record from this work order? Note that the record will remain.",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete Relationship",
callbackFunction: doDelete
}
});
};
const renderRelatedOccupancies = () => {
const occupanciesContainerElement = document.querySelector(
"#relatedTab--lotOccupancies"
) as HTMLElement;
document.querySelector(
".tabs a[href='#relatedTab--lotOccupancies'] .tag"
).textContent = workOrderLotOccupancies.length.toString();
if (workOrderLotOccupancies.length === 0) {
occupanciesContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no ' +
exports.aliases.occupancies.toLowerCase() +
" associated with this work order.</p>" +
"</div>";
return;
}
occupanciesContainerElement.innerHTML =
'<table class="table is-fullwidth is-striped is-hoverable">' +
"<thead>" +
"<tr>" +
'<th class="has-width-1"></th>' +
("<th>" + exports.aliases.occupancy + " Type</th>") +
("<th>" + exports.aliases.lot + "</th>") +
"<th>Start Date</th>" +
"<th>End Date</th>" +
("<th>" + exports.aliases.occupants + "</th>") +
'<th class="has-width-1"></th>' +
"</tr>" +
"</thead>" +
"<tbody></tbody>" +
"</table>";
const currentDateString = cityssm.dateToString(new Date());
for (const lotOccupancy of workOrderLotOccupancies) {
const rowElement = document.createElement("tr");
rowElement.className = "container--lotOccupancy";
rowElement.dataset.lotOccupancyId =
lotOccupancy.lotOccupancyId.toString();
const isActive = !(
lotOccupancy.occupancyEndDate &&
lotOccupancy.occupancyEndDateString < currentDateString
);
rowElement.innerHTML =
'<td class="has-text-centered">' +
(isActive
? '<i class="fas fa-play" title="Current ' +
cityssm.escapeHTML(exports.aliases.occupancy) +
'"></i>'
: '<i class="fas fa-stop" title="Previous ' +
cityssm.escapeHTML(exports.aliases.occupancy) +
'"></i>') +
"</td>" +
("<td>" +
'<a class="has-text-weight-bold" href="' +
cityssm.escapeHTML(urlPrefix) +
"/lotOccupancies/" +
lotOccupancy.lotOccupancyId +
'">' +
cityssm.escapeHTML(lotOccupancy.occupancyType) +
"</a>" +
"</td>") +
("<td>" +
(lotOccupancy.lotId
? cityssm.escapeHTML(lotOccupancy.lotName)
: '<span class="has-text-grey">(No ' +
exports.aliases.lot +
")</span>") +
"</td>") +
("<td>" + lotOccupancy.occupancyStartDateString + "</td>") +
("<td>" +
(lotOccupancy.occupancyEndDate
? lotOccupancy.occupancyEndDateString
: '<span class="has-text-grey">(No End Date)</span>') +
"</td>") +
("<td>" +
(lotOccupancy.lotOccupancyOccupants.length === 0
? '<span class="has-text-grey">(No ' +
cityssm.escapeHTML(exports.aliases.occupants) +
")</span>"
: cityssm.escapeHTML(
lotOccupancy.lotOccupancyOccupants[0]
.occupantName
) +
(lotOccupancy.lotOccupancyOccupants.length > 1
? " plus " +
(lotOccupancy.lotOccupancyOccupants.length -
1)
: "")) +
"</td>") +
("<td>" +
'<button class="button is-small is-light is-danger button--deleteLotOccupancy" data-tooltip="Delete Relationship" type="button">' +
'<i class="fas fa-trash" aria-hidden="true"></i>' +
"</button>" +
"</td>");
rowElement
.querySelector(".button--deleteLotOccupancy")
.addEventListener("click", deleteLotOccupancy);
occupanciesContainerElement
.querySelector("tbody")
.append(rowElement);
}
};
const deleteLot = (clickEvent: Event) => {
const lotId = (
(clickEvent.currentTarget as HTMLElement).closest(
".container--lot"
) as HTMLElement
).dataset.lotId;
const doDelete = () => {
cityssm.postJSON(
urlPrefix + "/workOrders/doDeleteWorkOrderLot",
{
workOrderId,
lotId
},
(responseJSON: {
success: boolean;
errorMessage?: string;
workOrderLots?: recordTypes.Lot[];
}) => {
if (responseJSON.success) {
workOrderLots =
responseJSON.workOrderLots;
renderRelatedLotsAndOccupancies();
} else {
bulmaJS.alert({
title: "Error Deleting Relationship",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
}
);
};
bulmaJS.confirm({
title:
"Delete " +
exports.aliases.lot +
" " +
exports.aliases.occupancy +
" Relationship",
message:
"Are you sure you want to remove the relationship to this " +
exports.aliases.lot.toLowerCase() +
" " +
exports.aliases.occupancy.toLowerCase() +
" record from this work order? Note that the record will remain.",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete Relationship",
callbackFunction: doDelete
}
});
};
const renderRelatedLots = () => {
const lotsContainerElement = document.querySelector(
"#relatedTab--lots"
) as HTMLElement;
document.querySelector(
".tabs a[href='#relatedTab--lots'] .tag"
).textContent = workOrderLots.length.toString();
if (workOrderLots.length === 0) {
lotsContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no ' +
exports.aliases.lots.toLowerCase() +
" associated with this work order.</p>" +
"</div>";
return;
}
lotsContainerElement.innerHTML =
'<table class="table is-fullwidth is-striped is-hoverable">' +
"<thead>" +
"<tr>" +
("<th>" + exports.aliases.lot + "</th>") +
("<th>" + exports.aliases.map + "</th>") +
("<th>" + exports.aliases.lot + " Type</th>") +
"<th>Status</th>" +
'<th class="has-width-1"></th>' +
"</tr>" +
"</thead>" +
"<tbody></tbody>" +
"</table>";
for (const lot of workOrderLots) {
const rowElement = document.createElement("tr");
rowElement.className = "container--lot";
rowElement.dataset.lotId = lot.lotId.toString();
rowElement.innerHTML =
"<td>" +
'<a class="has-text-weight-bold" href="' +
cityssm.escapeHTML(urlPrefix) +
"/lots/" +
lot.lotId +
'">' +
cityssm.escapeHTML(lot.lotName) +
"</a>" +
"</td>" +
("<td>" + cityssm.escapeHTML(lot.mapName) + "</td>") +
("<td>" + cityssm.escapeHTML(lot.lotType) + "</td>") +
("<td>" + cityssm.escapeHTML(lot.lotStatus) + "</td>") +
("<td>" +
'<button class="button is-small is-light is-danger button--deleteLot" data-tooltip="Delete Relationship" type="button">' +
'<i class="fas fa-trash" aria-hidden="true"></i>' +
"</button>" +
"</td>");
rowElement
.querySelector(".button--deleteLot")
.addEventListener("click", deleteLot);
lotsContainerElement.querySelector("tbody").append(rowElement);
}
};
const renderRelatedLotsAndOccupancies = () => {
renderRelatedOccupancies();
renderRelatedLots();
};
renderRelatedLotsAndOccupancies();
}
/*
* Comments
*/
/*
* Milestones
*/
})();

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const reopenWorkOrderButtonElement = document.querySelector("#button--reopenWorkOrder");
if (reopenWorkOrderButtonElement) {
reopenWorkOrderButtonElement.addEventListener("click", () => {
const workOrderId = reopenWorkOrderButtonElement.dataset.workOrderId;
const doReopen = () => {
cityssm.postJSON(urlPrefix + "/workOrders/doReopenWorkOrder", {
workOrderId
}, (responseJSON) => {
if (responseJSON.success) {
window.location.href =
urlPrefix +
"/workOrders/" +
workOrderId +
"/edit/?t=" +
Date.now();
}
else {
bulmaJS.alert({
title: "Error Reopening Work Order",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
bulmaJS.confirm({
title: "Reopen Work Order",
message: "Are you sure you want to remove the close date from this work order and reopen it?",
contextualColorName: "warning",
okButton: {
text: "Yes, Reopen Work Order",
callbackFunction: doReopen
}
});
});
}
})();

View File

@ -0,0 +1,61 @@
/* eslint-disable unicorn/prefer-module */
import type { cityssmGlobal } from "@cityssm/bulma-webapp-js/src/types";
import type { BulmaJS } from "@cityssm/bulma-js/types";
declare const cityssm: cityssmGlobal;
declare const bulmaJS: BulmaJS;
(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const reopenWorkOrderButtonElement = document.querySelector(
"#button--reopenWorkOrder"
) as HTMLButtonElement;
if (reopenWorkOrderButtonElement) {
reopenWorkOrderButtonElement.addEventListener("click", () => {
const workOrderId =
reopenWorkOrderButtonElement.dataset.workOrderId;
const doReopen = () => {
cityssm.postJSON(
urlPrefix + "/workOrders/doReopenWorkOrder",
{
workOrderId
},
(responseJSON: {
success: boolean;
errorMessage?: string;
}) => {
if (responseJSON.success) {
window.location.href =
urlPrefix +
"/workOrders/" +
workOrderId +
"/edit/?t=" +
Date.now();
} else {
bulmaJS.alert({
title: "Error Reopening Work Order",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
}
);
};
bulmaJS.confirm({
title: "Reopen Work Order",
message:
"Are you sure you want to remove the close date from this work order and reopen it?",
contextualColorName: "warning",
okButton: {
text: "Yes, Reopen Work Order",
callbackFunction: doReopen
}
});
});
}
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=document.querySelector("main").dataset.urlPrefix,r=document.querySelector("#button--reopenWorkOrder");r&&r.addEventListener("click",()=>{const o=r.dataset.workOrderId;bulmaJS.confirm({title:"Reopen Work Order",message:"Are you sure you want to remove the close date from this work order and reopen it?",contextualColorName:"warning",okButton:{text:"Yes, Reopen Work Order",callbackFunction:()=>{cityssm.postJSON(e+"/workOrders/doReopenWorkOrder",{workOrderId:o},r=>{r.success?window.location.href=e+"/workOrders/"+o+"/edit/?t="+Date.now():bulmaJS.alert({title:"Error Reopening Work Order",message:r.errorMessage,contextualColorName:"danger"})})}}})})})();

View File

@ -3,10 +3,18 @@ import * as permissionHandlers from "../handlers/permissions.js";
import handler_search from "../handlers/workOrders-get/search.js";
import handler_doSearchWorkOrders from "../handlers/workOrders-post/doSearchWorkOrders.js";
import handler_view from "../handlers/workOrders-get/view.js";
import handler_doReopenWorkOrder from "../handlers/workOrders-post/doReopenWorkOrder.js";
import handler_edit from "../handlers/workOrders-get/edit.js";
import handler_doUpdateWorkOrder from "../handlers/workOrders-post/doUpdateWorkOrder.js";
import handler_doDeleteWorkOrderLotOccupancy from "../handlers/workOrders-post/doDeleteWorkOrderLotOccupancy.js";
import handler_doDeleteWorkOrderLot from "../handlers/workOrders-post/doDeleteWorkOrderLot.js";
export const router = Router();
router.get("/", handler_search);
router.post("/doSearchWorkOrders", handler_doSearchWorkOrders);
router.get("/:workOrderId", handler_view);
router.post("/doReopenWorkOrder", permissionHandlers.updatePostHandler, handler_doReopenWorkOrder);
router.get("/:workOrderId/edit", permissionHandlers.updateGetHandler, handler_edit);
router.post("/doUpdateWorkOrder", permissionHandlers.updatePostHandler, handler_doUpdateWorkOrder);
router.post("/doDeleteWorkOrderLotOccupancy", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLotOccupancy);
router.post("/doDeleteWorkOrderLot", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLot);
export default router;

View File

@ -6,8 +6,12 @@ import handler_search from "../handlers/workOrders-get/search.js";
import handler_doSearchWorkOrders from "../handlers/workOrders-post/doSearchWorkOrders.js";
import handler_view from "../handlers/workOrders-get/view.js";
import handler_doReopenWorkOrder from "../handlers/workOrders-post/doReopenWorkOrder.js";
import handler_edit from "../handlers/workOrders-get/edit.js";
import handler_doUpdateWorkOrder from "../handlers/workOrders-post/doUpdateWorkOrder.js";
import handler_doDeleteWorkOrderLotOccupancy from "../handlers/workOrders-post/doDeleteWorkOrderLotOccupancy.js";
import handler_doDeleteWorkOrderLot from "../handlers/workOrders-post/doDeleteWorkOrderLot.js";
export const router = Router();
@ -17,8 +21,34 @@ router.post("/doSearchWorkOrders", handler_doSearchWorkOrders);
router.get("/:workOrderId", handler_view);
router.get("/:workOrderId/edit",
router.post(
"/doReopenWorkOrder",
permissionHandlers.updatePostHandler,
handler_doReopenWorkOrder
);
router.get(
"/:workOrderId/edit",
permissionHandlers.updateGetHandler,
handler_edit);
handler_edit
);
router.post(
"/doUpdateWorkOrder",
permissionHandlers.updatePostHandler,
handler_doUpdateWorkOrder
);
router.post(
"/doDeleteWorkOrderLotOccupancy",
permissionHandlers.updatePostHandler,
handler_doDeleteWorkOrderLotOccupancy
);
router.post(
"/doDeleteWorkOrderLot",
permissionHandlers.updatePostHandler,
handler_doDeleteWorkOrderLot
);
export default router;

View File

@ -52,7 +52,7 @@ export interface Lot extends Record {
lotId?: number;
lotName?: string;
lotTypeId?: number;
lotType?: LotType | string;
lotType?: string;
mapId?: number;
mapName?: string;
map?: Map;
@ -61,7 +61,7 @@ export interface Lot extends Record {
lotLatitude?: number;
lotLongitude?: number;
lotStatusId?: number;
lotStatus?: LotStatus | string;
lotStatus?: string;
lotOccupancyCount?: number;
lotOccupancies?: LotOccupancy[];
lotComments?: LotComment[];

View File

@ -70,7 +70,7 @@ export interface Lot extends Record {
lotName?: string;
lotTypeId?: number;
lotType?: LotType | string;
lotType?: string;
mapId?: number;
mapName?: string;
@ -82,7 +82,7 @@ export interface Lot extends Record {
lotLongitude?: number;
lotStatusId?: number;
lotStatus?: LotStatus | string;
lotStatus?: string;
lotOccupancyCount?: number;
lotOccupancies?: LotOccupancy[];

View File

@ -9,6 +9,13 @@
<span>Work Orders</span>
</a>
</li>
<% if (isCreate) { %>
<li class="is-active">
<a href="#" aria-current="page">
Create a New Work Order
</a>
</li>
<% } else { %>
<li>
<a href="<%= urlPrefix %>/workOrders/<%= workOrder.workOrderId %>">
Work Order #<%= workOrder.workOrderNumber || "(No Number)" %>
@ -19,9 +26,164 @@
Update Work Order
</a>
</li>
<% } %>
</ul>
</nav>
<h1 class="title is-1">
<% if (isCreate) { %>
Create a New Work Order
<% } else { %>
Work Order #<%= workOrder.workOrderNumber || "(No Number)" %>
<% } %>
</h1>
<form id="form--workOrderEdit">
<input id="workOrderEdit--workOrderId" name="workOrderId" type="hidden" value="<%= workOrder.workOrderId %>" />
<div class="panel">
<div class="panel-block is-block">
<div class="columns">
<div class="column">
<div class="field">
<label class="label" for="workOrderEdit--workOrderNumber">Work Order Number</label>
<div class="control">
<input class="input" id="workOrderEdit--workOrderNumber" name="workOrderNumber" type="text" value="<%= workOrder.workOrderNumber %>" maxlength="50" required />
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="workOrderEdit--workOrderTypeId">Work Order Type</label>
<div class="control">
<div class="select is-fullwidth">
<select id="workOrderEdit--workOrderTypeId" name="workOrderTypeId" required>
<% if (isCreate) { %>
<option value="">(Select Type)</option>
<% } %>
<% for (const workOrderType of workOrderTypes) { %>
<option value="<%= workOrderType.workOrderTypeId %>" <%= (workOrder.workOrderTypeId === workOrderType.workOrderTypeId ? " selected" : "") %>>
<%= workOrderType.workOrderType %>
</option>
<% } %>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field">
<label class="label" for="workOrderEdit--workOrderDescription">Description</label>
<div class="control">
<textarea class="textarea" id="workOrderEdit--workOrderDescription" name="workOrderDescription"><%= workOrder.workOrderDescription %></textarea>
</div>
</div>
<div class="columns">
<div class="column">
<div class="field">
<label class="label" for="workOrderEdit--workOrderOpenDateString">Open Date</label>
<div class="control">
<input class="input" id="workOrderEdit--workOrderOpenDateString" name="workOrderOpenDateString" type="date" value="<%= workOrder.workOrderOpenDateString %>" required />
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="workOrderEdit--workOrderCloseDateString">Close Date</label>
<div class="control">
<input class="input" id="workOrderEdit--workOrderCloseDateString" name="workOrderCloseDateString" type="date" value="<%= workOrder.workOrderCloseDateString %>" disabled readonly />
</div>
</div>
</div>
</div>
</div>
<div class="panel-block is-justify-content-flex-end">
<button class="button is-success" type="submit">
<span class="icon is-small"><i class="fas fa-save" aria-hidden="true"></i></span>
<span>
<%= (isCreate ? "Create" : "Update") %>
Work Order
</span>
</button>
<% if (!isCreate) { %>
<div class="dropdown is-right ml-2">
<div class="dropdown-trigger">
<button class="button" type="button">
<span>More Options</span>
<span class="icon is-small"><i class="fas fa-caret-down" aria-hidden="true"></i></span>
</button>
</div>
<div class="dropdown-menu">
<div class="dropdown-content">
<a class="dropdown-item" href="#">
<span class="icon is-small">
<i class="fas fa-trash has-text-danger" aria-hidden="true"></i>
</span>
<span>Delete Work Order</span>
</a>
</div>
</div>
</div>
<% } %>
</div>
</div>
<% if (!isCreate) { %>
<div class="panel">
<div class="panel-heading">
<div class="level">
<div class="level-left">
<div class="level-item">
<h2 class="title has-text-weight-bold is-5">Related <%= configFunctions.getProperty("aliases.lots") %></h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addWorkOrderLot" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add <%= configFunctions.getProperty("aliases.lots") %></span>
</button>
</div>
</div>
</div>
</div>
<div class="panel-block is-block">
<%
const tabToSelect = (workOrder.workOrderLotOccupancies.length > 0 || workOrder.workOrderLots.length === 0 ? "lotOccupancies" : "lots");
%>
<div class="tabs is-boxed">
<ul>
<li class="<%= (tabToSelect === "lotOccupancies" ? "is-active" : "") %>">
<a href="#relatedTab--lotOccupancies">
<span><%= configFunctions.getProperty("aliases.occupancies") %></span>
<span class="ml-2 tag"></span>
</a>
</li>
<li class="<%= (tabToSelect === "lots" ? "is-active" : "") %>">
<a href="#relatedTab--lots">
<span><%= configFunctions.getProperty("aliases.lots") %></span>
<span class="ml-2 tag"></span>
</a>
</li>
</ul>
</div>
<div class="tab-container">
<div class="<%= (tabToSelect === "lotOccupancies" ? "" : "is-hidden") %>" id="relatedTab--lotOccupancies">
</div>
<div class="<%= (tabToSelect === "lots" ? "" : "is-hidden") %>" id="relatedTab--lots">
</div>
</div>
</div>
</div>
<% } %>
</form>
<%- include('_footerA'); -%>
<script>
exports.workOrderComments = <%- JSON.stringify(workOrder.workOrderComments) %>;
exports.workOrderLots = <%- JSON.stringify(workOrder.workOrderLots) %>;
exports.workOrderLotOccupancies = <%- JSON.stringify(workOrder.workOrderLotOccupancies) %>;
exports.workOrderMilestones = <%- JSON.stringify(workOrder.workOrderMilestones) %>;
</script>
<script src="<% urlPrefix %>/javascripts/workOrderEdit.min.js"></script>
<%- include('_footerB'); -%>

View File

@ -21,7 +21,7 @@
Work Order #<%= workOrder.workOrderNumber || "(No Number)" %>
</h1>
<% if (user.userProperties.canUpdate) { %>
<% if (user.userProperties.canUpdate && !workOrder.workOrderCloseDate) { %>
<div class="fixed-container is-fixed-bottom-right mx-4 my-4 has-text-right is-hidden-print">
<a class="button is-circle is-primary has-tooltip-left" data-tooltip="Update Work Order" href="<%= urlPrefix %>/workOrders/<%= workOrder.workOrderId %>/edit">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
@ -65,6 +65,13 @@
</div>
</div>
</div>
<% if (user.userProperties.canUpdate && workOrder.workOrderCloseDate) { %>
<div class="panel-block is-justify-content-flex-end">
<button class="button is-warning" id="button--reopenWorkOrder" data-work-order-id="<%= workOrder.workOrderId %>" type="button">
Reopen Work Order
</button>
</div>
<% } %>
</div>
<div class="panel">
@ -101,6 +108,7 @@
</p>
</div>
<% } else { %>
<% const currentDate = dateTimeFunctions.dateToInteger(new Date()); %>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
@ -207,6 +215,34 @@
<div class="column is-4">
<div class="panel">
<h2 class="panel-heading">Milestones</h2>
<% for (const milestone of workOrder.workOrderMilestones) { %>
<div class="panel-block is-block">
<div class="columns">
<div class="column is-narrow">
<% if (milestone.workOrderMilestoneCompletionDate) { %>
<span class="icon is-small" data-tooltip="Completed <%= milestone.workOrderMilestoneCompletionDateString %>">
<i class="fas fa-check" aria-label="Completed <%= milestone.workOrderMilestoneCompletionDateString %>"></i>
</span>
<% } else { %>
<span class="icon is-small">
<i class="far fa-square has-text-grey" aria-label="Incomplete"></i>
</span>
<% } %>
</div>
<div class="column">
<% if (milestone.workOrderMilestoneTypeId) { %>
<strong><%= milestone.workOrderMilestoneType %></strong><br />
<% } %>
<%= milestone.workOrderMilestoneDateString %>
<% if (milestone.workOrderMilestoneTime !== 0) { %>
<%= milestone.workOrderMilestoneTimeString %>
<% } %>
<br />
<span class="is-size-7"><%= milestone.workOrderMilestoneDescription %></span>
</div>
</div>
</div>
<% } %>
</div>
</div>
<% } %>
@ -214,4 +250,6 @@
<%- include('_footerA'); -%>
<script src="<%= urlPrefix %>/javascripts/workOrderView.min.js"></script>
<%- include('_footerB'); -%>