From 2b38da7bea32cb6357c9b2288d53ca5b3dcee41f Mon Sep 17 00:00:00 2001 From: Dan Gowans Date: Mon, 12 Sep 2022 15:50:44 -0400 Subject: [PATCH] work order development - add lot occupancies - add lots - display milestones - complete milestones - reopen milestones --- handlers/workOrders-get/edit.js | 10 +- handlers/workOrders-get/edit.ts | 15 +- .../workOrders-post/doAddWorkOrderLot.d.ts | 3 + handlers/workOrders-post/doAddWorkOrderLot.js | 19 + handlers/workOrders-post/doAddWorkOrderLot.ts | 31 + .../doAddWorkOrderLotOccupancy.d.ts | 3 + .../doAddWorkOrderLotOccupancy.js | 20 + .../doAddWorkOrderLotOccupancy.ts | 32 + .../doCompleteWorkOrderMilestone.d.ts | 3 + .../doCompleteWorkOrderMilestone.js | 13 + .../doCompleteWorkOrderMilestone.ts | 25 + .../doReopenWorkOrderMilestone.d.ts | 3 + .../doReopenWorkOrderMilestone.js | 11 + .../doReopenWorkOrderMilestone.ts | 23 + .../addWorkOrderLotOccupancy.js | 39 +- .../addWorkOrderLotOccupancy.ts | 63 +- .../completeWorkOrderMiletstone.d.ts | 8 + .../completeWorkOrderMiletstone.js | 22 + .../completeWorkOrderMiletstone.ts | 58 ++ helpers/lotOccupancyDB/getLotOccupancies.d.ts | 2 + helpers/lotOccupancyDB/getLotOccupancies.js | 10 + helpers/lotOccupancyDB/getLotOccupancies.ts | 17 + .../reopenWorkOrderMilestone.d.ts | 3 + .../reopenWorkOrderMilestone.js | 18 + .../reopenWorkOrderMilestone.ts | 36 + public-typescript/workOrderEdit.js | 542 ++++++++++++- public-typescript/workOrderEdit.ts | 728 +++++++++++++++++- public/html/workOrder-addLot.html | 56 ++ public/html/workOrder-addLotOccupancy.html | 55 ++ public/javascripts/workOrderEdit.min.js | 2 +- routes/workOrders.js | 8 + routes/workOrders.ts | 31 + views/workOrder-edit.ejs | 279 ++++--- 33 files changed, 1963 insertions(+), 225 deletions(-) create mode 100644 handlers/workOrders-post/doAddWorkOrderLot.d.ts create mode 100644 handlers/workOrders-post/doAddWorkOrderLot.js create mode 100644 handlers/workOrders-post/doAddWorkOrderLot.ts create mode 100644 handlers/workOrders-post/doAddWorkOrderLotOccupancy.d.ts create mode 100644 handlers/workOrders-post/doAddWorkOrderLotOccupancy.js create mode 100644 handlers/workOrders-post/doAddWorkOrderLotOccupancy.ts create mode 100644 handlers/workOrders-post/doCompleteWorkOrderMilestone.d.ts create mode 100644 handlers/workOrders-post/doCompleteWorkOrderMilestone.js create mode 100644 handlers/workOrders-post/doCompleteWorkOrderMilestone.ts create mode 100644 handlers/workOrders-post/doReopenWorkOrderMilestone.d.ts create mode 100644 handlers/workOrders-post/doReopenWorkOrderMilestone.js create mode 100644 handlers/workOrders-post/doReopenWorkOrderMilestone.ts create mode 100644 helpers/lotOccupancyDB/completeWorkOrderMiletstone.d.ts create mode 100644 helpers/lotOccupancyDB/completeWorkOrderMiletstone.js create mode 100644 helpers/lotOccupancyDB/completeWorkOrderMiletstone.ts create mode 100644 helpers/lotOccupancyDB/reopenWorkOrderMilestone.d.ts create mode 100644 helpers/lotOccupancyDB/reopenWorkOrderMilestone.js create mode 100644 helpers/lotOccupancyDB/reopenWorkOrderMilestone.ts create mode 100644 public/html/workOrder-addLot.html create mode 100644 public/html/workOrder-addLotOccupancy.html diff --git a/handlers/workOrders-get/edit.js b/handlers/workOrders-get/edit.js index 2d53fdca..4f5bc25d 100644 --- a/handlers/workOrders-get/edit.js +++ b/handlers/workOrders-get/edit.js @@ -1,4 +1,4 @@ -import { getWorkOrderTypes } from "../../helpers/functions.cache.js"; +import { getLotStatuses, 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) => { @@ -9,14 +9,18 @@ export const handler = (request, response) => { } if (workOrder.workOrderCloseDate) { return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + - "/workOrders/" + workOrder.workOrderId.toString() + "/?error=workOrderIsClosed"); + "/workOrders/" + + workOrder.workOrderId.toString() + + "/?error=workOrderIsClosed"); } const workOrderTypes = getWorkOrderTypes(); + const lotStatuses = getLotStatuses(); response.render("workOrder-edit", { headTitle: "Work Order #" + workOrder.workOrderNumber, workOrder, isCreate: false, - workOrderTypes + workOrderTypes, + lotStatuses }); }; export default handler; diff --git a/handlers/workOrders-get/edit.ts b/handlers/workOrders-get/edit.ts index f4cb7953..c986543d 100644 --- a/handlers/workOrders-get/edit.ts +++ b/handlers/workOrders-get/edit.ts @@ -1,5 +1,9 @@ import type { RequestHandler } from "express"; -import { getWorkOrderTypes } from "../../helpers/functions.cache.js"; + +import { + getLotStatuses, + getWorkOrderTypes +} from "../../helpers/functions.cache.js"; import * as configFunctions from "../../helpers/functions.config.js"; @@ -18,17 +22,22 @@ export const handler: RequestHandler = (request, response) => { if (workOrder.workOrderCloseDate) { return response.redirect( configFunctions.getProperty("reverseProxy.urlPrefix") + - "/workOrders/" + workOrder.workOrderId.toString() + "/?error=workOrderIsClosed" + "/workOrders/" + + workOrder.workOrderId.toString() + + "/?error=workOrderIsClosed" ); } const workOrderTypes = getWorkOrderTypes(); + const lotStatuses = getLotStatuses(); + response.render("workOrder-edit", { headTitle: "Work Order #" + workOrder.workOrderNumber, workOrder, isCreate: false, - workOrderTypes + workOrderTypes, + lotStatuses }); }; diff --git a/handlers/workOrders-post/doAddWorkOrderLot.d.ts b/handlers/workOrders-post/doAddWorkOrderLot.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLot.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/workOrders-post/doAddWorkOrderLot.js b/handlers/workOrders-post/doAddWorkOrderLot.js new file mode 100644 index 00000000..cc05bbdb --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLot.js @@ -0,0 +1,19 @@ +import { addWorkOrderLot } from "../../helpers/lotOccupancyDB/addWorkOrderLot.js"; +import { getLots } from "../../helpers/lotOccupancyDB/getLots.js"; +export const handler = async (request, response) => { + const success = addWorkOrderLot({ + workOrderId: request.body.workOrderId, + lotId: request.body.lotId + }, request.session); + const workOrderLots = getLots({ + workOrderId: request.body.workOrderId + }, { + limit: -1, + offset: 0 + }).lots; + response.json({ + success, + workOrderLots + }); +}; +export default handler; diff --git a/handlers/workOrders-post/doAddWorkOrderLot.ts b/handlers/workOrders-post/doAddWorkOrderLot.ts new file mode 100644 index 00000000..d671e381 --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLot.ts @@ -0,0 +1,31 @@ +import type { RequestHandler } from "express"; + +import { addWorkOrderLot } from "../../helpers/lotOccupancyDB/addWorkOrderLot.js"; +import { getLots } from "../../helpers/lotOccupancyDB/getLots.js"; + +export const handler: RequestHandler = async (request, response) => { + const success = addWorkOrderLot( + { + workOrderId: request.body.workOrderId, + lotId: request.body.lotId + }, + request.session + ); + + const workOrderLots = getLots( + { + workOrderId: request.body.workOrderId + }, + { + limit: -1, + offset: 0 + } + ).lots; + + response.json({ + success, + workOrderLots + }); +}; + +export default handler; diff --git a/handlers/workOrders-post/doAddWorkOrderLotOccupancy.d.ts b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/workOrders-post/doAddWorkOrderLotOccupancy.js b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.js new file mode 100644 index 00000000..e6e3b2af --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.js @@ -0,0 +1,20 @@ +import { addWorkOrderLotOccupancy } from "../../helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js"; +import { getLotOccupancies } from "../../helpers/lotOccupancyDB/getLotOccupancies.js"; +export const handler = async (request, response) => { + const success = addWorkOrderLotOccupancy({ + workOrderId: request.body.workOrderId, + lotOccupancyId: 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; diff --git a/handlers/workOrders-post/doAddWorkOrderLotOccupancy.ts b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.ts new file mode 100644 index 00000000..d8fc276a --- /dev/null +++ b/handlers/workOrders-post/doAddWorkOrderLotOccupancy.ts @@ -0,0 +1,32 @@ +import type { RequestHandler } from "express"; + +import { addWorkOrderLotOccupancy } from "../../helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js"; +import { getLotOccupancies } from "../../helpers/lotOccupancyDB/getLotOccupancies.js"; + +export const handler: RequestHandler = async (request, response) => { + const success = addWorkOrderLotOccupancy( + { + workOrderId: request.body.workOrderId, + lotOccupancyId: 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; diff --git a/handlers/workOrders-post/doCompleteWorkOrderMilestone.d.ts b/handlers/workOrders-post/doCompleteWorkOrderMilestone.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/workOrders-post/doCompleteWorkOrderMilestone.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/workOrders-post/doCompleteWorkOrderMilestone.js b/handlers/workOrders-post/doCompleteWorkOrderMilestone.js new file mode 100644 index 00000000..0e18c572 --- /dev/null +++ b/handlers/workOrders-post/doCompleteWorkOrderMilestone.js @@ -0,0 +1,13 @@ +import { completeWorkOrderMilestone } from "../../helpers/lotOccupancyDB/completeWorkOrderMiletstone.js"; +import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js"; +export const handler = async (request, response) => { + const success = completeWorkOrderMilestone({ + workOrderMilestoneId: request.body.workOrderMilestoneId + }, request.session); + const workOrderMilestones = getWorkOrderMilestones(request.body.workOrderId); + response.json({ + success, + workOrderMilestones + }); +}; +export default handler; diff --git a/handlers/workOrders-post/doCompleteWorkOrderMilestone.ts b/handlers/workOrders-post/doCompleteWorkOrderMilestone.ts new file mode 100644 index 00000000..324c0550 --- /dev/null +++ b/handlers/workOrders-post/doCompleteWorkOrderMilestone.ts @@ -0,0 +1,25 @@ +import type { RequestHandler } from "express"; + +import { completeWorkOrderMilestone } from "../../helpers/lotOccupancyDB/completeWorkOrderMiletstone.js"; + +import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js"; + +export const handler: RequestHandler = async (request, response) => { + const success = completeWorkOrderMilestone( + { + workOrderMilestoneId: request.body.workOrderMilestoneId + }, + request.session + ); + + const workOrderMilestones = getWorkOrderMilestones( + request.body.workOrderId + ); + + response.json({ + success, + workOrderMilestones + }); +}; + +export default handler; diff --git a/handlers/workOrders-post/doReopenWorkOrderMilestone.d.ts b/handlers/workOrders-post/doReopenWorkOrderMilestone.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/workOrders-post/doReopenWorkOrderMilestone.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/workOrders-post/doReopenWorkOrderMilestone.js b/handlers/workOrders-post/doReopenWorkOrderMilestone.js new file mode 100644 index 00000000..8ea0cd7e --- /dev/null +++ b/handlers/workOrders-post/doReopenWorkOrderMilestone.js @@ -0,0 +1,11 @@ +import { reopenWorkOrderMilestone } from "../../helpers/lotOccupancyDB/reopenWorkOrderMilestone.js"; +import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js"; +export const handler = async (request, response) => { + const success = reopenWorkOrderMilestone(request.body.workOrderMilestoneId, request.session); + const workOrderMilestones = getWorkOrderMilestones(request.body.workOrderId); + response.json({ + success, + workOrderMilestones + }); +}; +export default handler; diff --git a/handlers/workOrders-post/doReopenWorkOrderMilestone.ts b/handlers/workOrders-post/doReopenWorkOrderMilestone.ts new file mode 100644 index 00000000..72dc1250 --- /dev/null +++ b/handlers/workOrders-post/doReopenWorkOrderMilestone.ts @@ -0,0 +1,23 @@ +import type { RequestHandler } from "express"; + +import { reopenWorkOrderMilestone } from "../../helpers/lotOccupancyDB/reopenWorkOrderMilestone.js"; + +import { getWorkOrderMilestones } from "../../helpers/lotOccupancyDB/getWorkOrderMilestones.js"; + +export const handler: RequestHandler = async (request, response) => { + const success = reopenWorkOrderMilestone( + request.body.workOrderMilestoneId, + request.session + ); + + const workOrderMilestones = getWorkOrderMilestones( + request.body.workOrderId + ); + + response.json({ + success, + workOrderMilestones + }); +}; + +export default handler; diff --git a/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js b/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js index 30527029..809fdc5f 100644 --- a/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js +++ b/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.js @@ -3,14 +3,37 @@ import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; export const addWorkOrderLotOccupancy = (workOrderLotOccupancyForm, requestSession) => { const database = sqlite(databasePath); const rightNowMillis = Date.now(); - const result = database - .prepare("insert into WorkOrderLotOccupancies (" + - "workOrderId, lotOccupancyId," + - " recordCreate_userName, recordCreate_timeMillis," + - " recordUpdate_userName, recordUpdate_timeMillis)" + - " values (?, ?, ?, ?, ?, ?)") - .run(workOrderLotOccupancyForm.workOrderId, workOrderLotOccupancyForm.lotOccupancyId, requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis); + const row = database + .prepare("select recordDelete_timeMillis" + + " from WorkOrderLotOccupancies" + + " where workOrderId = ?" + + " and lotOccupancyId = ?") + .get(workOrderLotOccupancyForm.workOrderId, workOrderLotOccupancyForm.lotOccupancyId); + if (row) { + if (row.recordDelete_timeMillis) { + database + .prepare("update WorkOrderLotOccupancies" + + " set recordCreate_userName = ?," + + " recordCreate_timeMillis = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?," + + " recordDelete_userName = null," + + " recordDelete_timeMillis = null" + + " where workOrderId = ?" + + " and lotOccupancyId = ?") + .run(requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis, workOrderLotOccupancyForm.workOrderId, workOrderLotOccupancyForm.lotOccupancyId); + } + } + else { + database + .prepare("insert into WorkOrderLotOccupancies (" + + "workOrderId, lotOccupancyId," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?)") + .run(workOrderLotOccupancyForm.workOrderId, workOrderLotOccupancyForm.lotOccupancyId, requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis); + } database.close(); - return result.changes > 0; + return true; }; export default addWorkOrderLotOccupancy; diff --git a/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.ts b/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.ts index 01652e39..34ccfe84 100644 --- a/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.ts +++ b/helpers/lotOccupancyDB/addWorkOrderLotOccupancy.ts @@ -17,26 +17,63 @@ export const addWorkOrderLotOccupancy = ( const rightNowMillis = Date.now(); - const result = database + const row: { recordDelete_timeMillis?: number } = database .prepare( - "insert into WorkOrderLotOccupancies (" + - "workOrderId, lotOccupancyId," + - " recordCreate_userName, recordCreate_timeMillis," + - " recordUpdate_userName, recordUpdate_timeMillis)" + - " values (?, ?, ?, ?, ?, ?)" + "select recordDelete_timeMillis" + + " from WorkOrderLotOccupancies" + + " where workOrderId = ?" + + " and lotOccupancyId = ?" ) - .run( + .get( workOrderLotOccupancyForm.workOrderId, - workOrderLotOccupancyForm.lotOccupancyId, - requestSession.user.userName, - rightNowMillis, - requestSession.user.userName, - rightNowMillis + workOrderLotOccupancyForm.lotOccupancyId ); + if (row) { + if (row.recordDelete_timeMillis) { + database + .prepare( + "update WorkOrderLotOccupancies" + + " set recordCreate_userName = ?," + + " recordCreate_timeMillis = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?," + + " recordDelete_userName = null," + + " recordDelete_timeMillis = null" + + " where workOrderId = ?" + + " and lotOccupancyId = ?" + ) + .run( + requestSession.user.userName, + rightNowMillis, + requestSession.user.userName, + rightNowMillis, + workOrderLotOccupancyForm.workOrderId, + workOrderLotOccupancyForm.lotOccupancyId + ); + } + } else { + database + .prepare( + "insert into WorkOrderLotOccupancies (" + + "workOrderId, lotOccupancyId," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?)" + ) + .run( + workOrderLotOccupancyForm.workOrderId, + workOrderLotOccupancyForm.lotOccupancyId, + requestSession.user.userName, + rightNowMillis, + requestSession.user.userName, + rightNowMillis + ); + } + database.close(); - return result.changes > 0; + return true; }; export default addWorkOrderLotOccupancy; diff --git a/helpers/lotOccupancyDB/completeWorkOrderMiletstone.d.ts b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.d.ts new file mode 100644 index 00000000..f9da97bf --- /dev/null +++ b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.d.ts @@ -0,0 +1,8 @@ +import type * as recordTypes from "../../types/recordTypes"; +interface CompleteWorkOrderMilestoneForm { + workOrderMilestoneId: string | number; + workOrderMilestoneCompletionDateString?: string; + workOrderMilestoneCompletionTimeString?: string; +} +export declare const completeWorkOrderMilestone: (milestoneForm: CompleteWorkOrderMilestoneForm, requestSession: recordTypes.PartialSession) => boolean; +export default completeWorkOrderMilestone; diff --git a/helpers/lotOccupancyDB/completeWorkOrderMiletstone.js b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.js new file mode 100644 index 00000000..07b3b1bd --- /dev/null +++ b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.js @@ -0,0 +1,22 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { dateStringToInteger, dateToInteger, dateToTimeInteger, timeStringToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +export const completeWorkOrderMilestone = (milestoneForm, requestSession) => { + const rightNow = new Date(); + const database = sqlite(databasePath); + const result = database + .prepare("update WorkOrderMilestones" + + " set workOrderMilestoneCompletionDate = ?," + + " workOrderMilestoneCompletionTime = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where workOrderMilestoneId = ?") + .run(milestoneForm.workOrderMilestoneCompletionDateString + ? dateStringToInteger(milestoneForm.workOrderMilestoneCompletionDateString) + : dateToInteger(rightNow), milestoneForm.workOrderMilestoneCompletionTimeString + ? timeStringToInteger(milestoneForm.workOrderMilestoneCompletionTimeString) + : dateToTimeInteger(rightNow), requestSession.user.userName, rightNow.getTime(), milestoneForm.workOrderMilestoneId); + database.close(); + return result.changes > 0; +}; +export default completeWorkOrderMilestone; diff --git a/helpers/lotOccupancyDB/completeWorkOrderMiletstone.ts b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.ts new file mode 100644 index 00000000..4152de7e --- /dev/null +++ b/helpers/lotOccupancyDB/completeWorkOrderMiletstone.ts @@ -0,0 +1,58 @@ +import sqlite from "better-sqlite3"; + +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import { + dateStringToInteger, + dateToInteger, + dateToTimeInteger, + timeStringToInteger +} from "@cityssm/expressjs-server-js/dateTimeFns.js"; + +import type * as recordTypes from "../../types/recordTypes"; + +interface CompleteWorkOrderMilestoneForm { + workOrderMilestoneId: string | number; + workOrderMilestoneCompletionDateString?: string; + workOrderMilestoneCompletionTimeString?: string; +} + +export const completeWorkOrderMilestone = ( + milestoneForm: CompleteWorkOrderMilestoneForm, + requestSession: recordTypes.PartialSession +): boolean => { + const rightNow = new Date(); + + const database = sqlite(databasePath); + + const result = database + .prepare( + "update WorkOrderMilestones" + + " set workOrderMilestoneCompletionDate = ?," + + " workOrderMilestoneCompletionTime = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where workOrderMilestoneId = ?" + ) + .run( + milestoneForm.workOrderMilestoneCompletionDateString + ? dateStringToInteger( + milestoneForm.workOrderMilestoneCompletionDateString + ) + : dateToInteger(rightNow), + milestoneForm.workOrderMilestoneCompletionTimeString + ? timeStringToInteger( + milestoneForm.workOrderMilestoneCompletionTimeString + ) + : dateToTimeInteger(rightNow), + requestSession.user.userName, + rightNow.getTime(), + milestoneForm.workOrderMilestoneId + ); + + database.close(); + + return result.changes > 0; +}; + +export default completeWorkOrderMilestone; diff --git a/helpers/lotOccupancyDB/getLotOccupancies.d.ts b/helpers/lotOccupancyDB/getLotOccupancies.d.ts index 6618220e..fe7300d6 100644 --- a/helpers/lotOccupancyDB/getLotOccupancies.d.ts +++ b/helpers/lotOccupancyDB/getLotOccupancies.d.ts @@ -4,12 +4,14 @@ interface GetLotOccupanciesFilters { lotId?: number | string; occupancyTime?: "" | "past" | "current" | "future"; occupancyStartDateString?: string; + occupancyEffectiveDateString?: string; occupantName?: string; occupancyTypeId?: number | string; mapId?: number | string; lotName?: string; lotTypeId?: number | string; workOrderId?: number | string; + notWorkOrderId?: number | string; } interface GetLotOccupanciesOptions { limit: -1 | number; diff --git a/helpers/lotOccupancyDB/getLotOccupancies.js b/helpers/lotOccupancyDB/getLotOccupancies.js index 9fe2de3b..b64e2686 100644 --- a/helpers/lotOccupancyDB/getLotOccupancies.js +++ b/helpers/lotOccupancyDB/getLotOccupancies.js @@ -57,6 +57,11 @@ export const getLotOccupancies = (filters, options, connectedDatabase) => { sqlWhereClause += " and o.occupancyStartDate = ?"; sqlParameters.push(dateStringToInteger(filters.occupancyStartDateString)); } + if (filters.occupancyEffectiveDateString) { + sqlWhereClause += + " and (o.occupancyStartDate <= ? and (o.occupancyEndDate is null or o.occupancyEndDate >= ?))"; + sqlParameters.push(dateStringToInteger(filters.occupancyEffectiveDateString), dateStringToInteger(filters.occupancyEffectiveDateString)); + } if (filters.mapId) { sqlWhereClause += " and l.mapId = ?"; sqlParameters.push(filters.mapId); @@ -70,6 +75,11 @@ export const getLotOccupancies = (filters, options, connectedDatabase) => { " and o.lotOccupancyId in (select lotOccupancyId from WorkOrderLotOccupancies where recordDelete_timeMillis is null and workOrderId = ?)"; sqlParameters.push(filters.workOrderId); } + if (filters.notWorkOrderId) { + sqlWhereClause += + " and o.lotOccupancyId not in (select lotOccupancyId from WorkOrderLotOccupancies where recordDelete_timeMillis is null and workOrderId = ?)"; + sqlParameters.push(filters.notWorkOrderId); + } const count = database .prepare("select count(*) as recordCount" + " from LotOccupancies o" + diff --git a/helpers/lotOccupancyDB/getLotOccupancies.ts b/helpers/lotOccupancyDB/getLotOccupancies.ts index 381564f0..601e1339 100644 --- a/helpers/lotOccupancyDB/getLotOccupancies.ts +++ b/helpers/lotOccupancyDB/getLotOccupancies.ts @@ -16,12 +16,14 @@ interface GetLotOccupanciesFilters { lotId?: number | string; occupancyTime?: "" | "past" | "current" | "future"; occupancyStartDateString?: string; + occupancyEffectiveDateString?: string; occupantName?: string; occupancyTypeId?: number | string; mapId?: number | string; lotName?: string; lotTypeId?: number | string; workOrderId?: number | string; + notWorkOrderId?: number | string; } interface GetLotOccupanciesOptions { @@ -107,6 +109,15 @@ export const getLotOccupancies = ( ); } + if (filters.occupancyEffectiveDateString) { + sqlWhereClause += + " and (o.occupancyStartDate <= ? and (o.occupancyEndDate is null or o.occupancyEndDate >= ?))"; + sqlParameters.push( + dateStringToInteger(filters.occupancyEffectiveDateString), + dateStringToInteger(filters.occupancyEffectiveDateString) + ); + } + if (filters.mapId) { sqlWhereClause += " and l.mapId = ?"; sqlParameters.push(filters.mapId); @@ -123,6 +134,12 @@ export const getLotOccupancies = ( sqlParameters.push(filters.workOrderId); } + if (filters.notWorkOrderId) { + sqlWhereClause += + " and o.lotOccupancyId not in (select lotOccupancyId from WorkOrderLotOccupancies where recordDelete_timeMillis is null and workOrderId = ?)"; + sqlParameters.push(filters.notWorkOrderId); + } + const count: number = database .prepare( "select count(*) as recordCount" + diff --git a/helpers/lotOccupancyDB/reopenWorkOrderMilestone.d.ts b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.d.ts new file mode 100644 index 00000000..3852c19c --- /dev/null +++ b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.d.ts @@ -0,0 +1,3 @@ +import type * as recordTypes from "../../types/recordTypes"; +export declare const reopenWorkOrderMilestone: (workOrderMilestoneId: number | string, requestSession: recordTypes.PartialSession) => boolean; +export default reopenWorkOrderMilestone; diff --git a/helpers/lotOccupancyDB/reopenWorkOrderMilestone.js b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.js new file mode 100644 index 00000000..02a15ae5 --- /dev/null +++ b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.js @@ -0,0 +1,18 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const reopenWorkOrderMilestone = (workOrderMilestoneId, requestSession) => { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update WorkOrderMilestones" + + " set workOrderMilestoneCompletionDate = null," + + " workOrderMilestoneCompletionTime = null," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where workOrderMilestoneId = ?" + + " and workOrderMilestoneCompletionDate is not null") + .run(requestSession.user.userName, rightNowMillis, workOrderMilestoneId); + database.close(); + return result.changes > 0; +}; +export default reopenWorkOrderMilestone; diff --git a/helpers/lotOccupancyDB/reopenWorkOrderMilestone.ts b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.ts new file mode 100644 index 00000000..1441a10a --- /dev/null +++ b/helpers/lotOccupancyDB/reopenWorkOrderMilestone.ts @@ -0,0 +1,36 @@ +import sqlite from "better-sqlite3"; + +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + +export const reopenWorkOrderMilestone = ( + workOrderMilestoneId: number | string, + requestSession: recordTypes.PartialSession +): boolean => { + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare( + "update WorkOrderMilestones" + + " set workOrderMilestoneCompletionDate = null," + + " workOrderMilestoneCompletionTime = null," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where workOrderMilestoneId = ?" + + " and workOrderMilestoneCompletionDate is not null" + ) + .run( + requestSession.user.userName, + rightNowMillis, + workOrderMilestoneId + ); + + database.close(); + + return result.changes > 0; +}; + +export default reopenWorkOrderMilestone; diff --git a/public-typescript/workOrderEdit.js b/public-typescript/workOrderEdit.js index 5ceb190f..7485f1a5 100644 --- a/public-typescript/workOrderEdit.js +++ b/public-typescript/workOrderEdit.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); (() => { + const los = exports.los; const urlPrefix = document.querySelector("main").dataset.urlPrefix; const workOrderId = document.querySelector("#workOrderEdit--workOrderId").value; const isCreate = workOrderId === ""; @@ -79,8 +80,56 @@ Object.defineProperty(exports, "__esModule", { value: true }); } }); }; + const addLot = (lotId, callbackFunction) => { + cityssm.postJSON(urlPrefix + "/workOrders/doAddWorkOrderLot", { + workOrderId, + lotId + }, (responseJSON) => { + if (responseJSON.success) { + workOrderLots = responseJSON.workOrderLots; + renderRelatedLotsAndOccupancies(); + } + else { + bulmaJS.alert({ + title: "Error Adding " + exports.aliases.lot, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + if (callbackFunction) { + callbackFunction(responseJSON.success); + } + }); + }; + const addLotOccupancy = (lotOccupancyId, callbackFunction) => { + cityssm.postJSON(urlPrefix + "/workOrders/doAddWorkOrderLotOccupancy", { + workOrderId, + lotOccupancyId + }, (responseJSON) => { + if (responseJSON.success) { + workOrderLotOccupancies = + responseJSON.workOrderLotOccupancies; + renderRelatedLotsAndOccupancies(); + } + else { + bulmaJS.alert({ + title: "Error Adding " + exports.aliases.occupancy, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + if (callbackFunction) { + callbackFunction(responseJSON.success); + } + }); + }; + const addLotFromLotOccupancy = (clickEvent) => { + const lotId = clickEvent.currentTarget.dataset + .lotId; + addLot(lotId); + }; const renderRelatedOccupancies = () => { - const occupanciesContainerElement = document.querySelector("#relatedTab--lotOccupancies"); + const occupanciesContainerElement = document.querySelector("#container--lotOccupancies"); document.querySelector(".tabs a[href='#relatedTab--lotOccupancies'] .tag").textContent = workOrderLotOccupancies.length.toString(); if (workOrderLotOccupancies.length === 0) { occupanciesContainerElement.innerHTML = @@ -114,6 +163,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); lotOccupancy.lotOccupancyId.toString(); const isActive = !(lotOccupancy.occupancyEndDate && lotOccupancy.occupancyEndDateString < currentDateString); + const hasLotRecord = lotOccupancy.lotId && + workOrderLots.some((lot) => { + return lotOccupancy.lotId === lot.lotId; + }); rowElement.innerHTML = '' + (isActive @@ -132,38 +185,65 @@ Object.defineProperty(exports, "__esModule", { value: true }); '">' + cityssm.escapeHTML(lotOccupancy.occupancyType) + "" + - "") + - ("" + - (lotOccupancy.lotId - ? cityssm.escapeHTML(lotOccupancy.lotName) - : '(No ' + - exports.aliases.lot + - ")") + - "") + - ("" + lotOccupancy.occupancyStartDateString + "") + - ("" + - (lotOccupancy.occupancyEndDate - ? lotOccupancy.occupancyEndDateString - : '(No End Date)') + - "") + - ("" + - (lotOccupancy.lotOccupancyOccupants.length === 0 - ? '(No ' + - cityssm.escapeHTML(exports.aliases.occupants) + - ")" - : cityssm.escapeHTML(lotOccupancy.lotOccupancyOccupants[0] - .occupantName) + - (lotOccupancy.lotOccupancyOccupants.length > 1 - ? " plus " + - (lotOccupancy.lotOccupancyOccupants.length - - 1) - : "")) + - "") + - ("" + - '" + ""); + if (lotOccupancy.lotId) { + rowElement.insertAdjacentHTML("beforeend", "" + + cityssm.escapeHTML(lotOccupancy.lotName) + + (hasLotRecord + ? "" + : ' ") + + ""); + } + else { + rowElement.insertAdjacentHTML("beforeend", "" + + '(No ' + + exports.aliases.lot + + ")" + + ""); + } + rowElement.insertAdjacentHTML("beforeend", "" + + lotOccupancy.occupancyStartDateString + + "" + + ("" + + (lotOccupancy.occupancyEndDate + ? lotOccupancy.occupancyEndDateString + : '(No End Date)') + + "") + + ("" + + (lotOccupancy.lotOccupancyOccupants.length === 0 + ? '(No ' + + cityssm.escapeHTML(exports.aliases.occupants) + + ")" + : cityssm.escapeHTML(lotOccupancy.lotOccupancyOccupants[0] + .occupantName) + + (lotOccupancy.lotOccupancyOccupants.length > 1 + ? " plus " + + (lotOccupancy.lotOccupancyOccupants + .length - + 1) + : "")) + + "") + + ("" + + '" + + "")); + if (lotOccupancy.lotId && !hasLotRecord) { + rowElement + .querySelector(".button--addLot") + .addEventListener("click", addLotFromLotOccupancy); + } rowElement .querySelector(".button--deleteLotOccupancy") .addEventListener("click", deleteLotOccupancy); @@ -180,8 +260,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); lotId }, (responseJSON) => { if (responseJSON.success) { - workOrderLots = - responseJSON.workOrderLots; + workOrderLots = responseJSON.workOrderLots; renderRelatedLotsAndOccupancies(); } else { @@ -212,7 +291,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); }); }; const renderRelatedLots = () => { - const lotsContainerElement = document.querySelector("#relatedTab--lots"); + const lotsContainerElement = document.querySelector("#container--lots"); document.querySelector(".tabs a[href='#relatedTab--lots'] .tag").textContent = workOrderLots.length.toString(); if (workOrderLots.length === 0) { lotsContainerElement.innerHTML = @@ -269,5 +348,398 @@ Object.defineProperty(exports, "__esModule", { value: true }); renderRelatedLots(); }; renderRelatedLotsAndOccupancies(); + document + .querySelector("#button--addLotOccupancy") + .addEventListener("click", () => { + let searchFormElement; + let searchResultsContainerElement; + const doAddLotOccupancy = (clickEvent) => { + const rowElement = clickEvent.currentTarget.closest("tr"); + const lotOccupancyId = rowElement.dataset.lotOccupancyId; + addLotOccupancy(lotOccupancyId, (success) => { + if (success) { + rowElement.remove(); + } + }); + }; + const doSearch = (event) => { + if (event) { + event.preventDefault(); + } + searchResultsContainerElement.innerHTML = + '

' + + '
' + + "Searching..." + + "

"; + cityssm.postJSON(urlPrefix + "/lotOccupancies/doSearchLotOccupancies", searchFormElement, (responseJSON) => { + if (responseJSON.lotOccupancies.length === 0) { + searchResultsContainerElement.innerHTML = + '
' + + '

There are no records that meet the search criteria.

' + + "
"; + return; + } + searchResultsContainerElement.innerHTML = + '' + + "" + + "" + + '' + + ("") + + ("") + + "" + + "" + + ("") + + "" + + "" + + "" + + "
" + + exports.aliases.occupancy + + " Type" + exports.aliases.lot + "Start DateEnd Date" + exports.aliases.occupants + "
"; + for (const lotOccupancy of responseJSON.lotOccupancies) { + const rowElement = document.createElement("tr"); + rowElement.className = + "container--lotOccupancy"; + rowElement.dataset.lotOccupancyId = + lotOccupancy.lotOccupancyId.toString(); + rowElement.innerHTML = + '' + + '" + + "" + + ('' + + cityssm.escapeHTML(lotOccupancy.occupancyType) + + ""); + if (lotOccupancy.lotId) { + rowElement.insertAdjacentHTML("beforeend", "" + + cityssm.escapeHTML(lotOccupancy.lotName) + + ""); + } + else { + rowElement.insertAdjacentHTML("beforeend", "" + + '(No ' + + exports.aliases.lot + + ")" + + ""); + } + rowElement.insertAdjacentHTML("beforeend", "" + + lotOccupancy.occupancyStartDateString + + "" + + ("" + + (lotOccupancy.occupancyEndDate + ? lotOccupancy.occupancyEndDateString + : '(No End Date)') + + "") + + ("" + + (lotOccupancy.lotOccupancyOccupants + .length === 0 + ? '(No ' + + cityssm.escapeHTML(exports.aliases.occupants) + + ")" + : cityssm.escapeHTML(lotOccupancy + .lotOccupancyOccupants[0] + .occupantName) + + (lotOccupancy + .lotOccupancyOccupants + .length > 1 + ? " plus " + + (lotOccupancy + .lotOccupancyOccupants + .length - + 1) + : "")) + + "")); + rowElement + .querySelector(".button--addLotOccupancy") + .addEventListener("click", doAddLotOccupancy); + searchResultsContainerElement + .querySelector("tbody") + .append(rowElement); + } + }); + }; + cityssm.openHtmlModal("workOrder-addLotOccupancy", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + searchFormElement = modalElement.querySelector("form"); + searchResultsContainerElement = + modalElement.querySelector("#resultsContainer--lotOccupancyAdd"); + modalElement.querySelector("#lotOccupancySearch--notWorkOrderId").value = workOrderId; + modalElement.querySelector("#lotOccupancySearch--occupancyEffectiveDateString").value = document.querySelector("#workOrderEdit--workOrderOpenDateString").value; + doSearch(); + }, + onshown: (modalElement) => { + bulmaJS.toggleHtmlClipped(); + modalElement + .querySelector("#lotOccupancySearch--occupantName") + .addEventListener("change", doSearch); + modalElement + .querySelector("#lotOccupancySearch--lotName") + .addEventListener("change", doSearch); + searchFormElement.addEventListener("submit", doSearch); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + document + .querySelector("#button--addLot") + .addEventListener("click", () => { + let searchFormElement; + let searchResultsContainerElement; + const doAddLot = (clickEvent) => { + const rowElement = clickEvent.currentTarget.closest("tr"); + const lotId = rowElement.dataset.lotId; + addLot(lotId, (success) => { + if (success) { + rowElement.remove(); + } + }); + }; + const doSearch = (event) => { + if (event) { + event.preventDefault(); + } + searchResultsContainerElement.innerHTML = + '

' + + '
' + + "Searching..." + + "

"; + cityssm.postJSON(urlPrefix + "/lots/doSearchLots", searchFormElement, (responseJSON) => { + if (responseJSON.lots.length === 0) { + searchResultsContainerElement.innerHTML = + '
' + + '

There are no records that meet the search criteria.

' + + "
"; + return; + } + searchResultsContainerElement.innerHTML = + '' + + "" + + "" + + '' + + ("") + + ("") + + ("") + + "" + + "" + + "" + + "" + + "
" + exports.aliases.lot + "" + exports.aliases.map + "" + exports.aliases.lot + " TypeStatus
"; + for (const lot of responseJSON.lots) { + const rowElement = document.createElement("tr"); + rowElement.className = "container--lot"; + rowElement.dataset.lotId = lot.lotId.toString(); + rowElement.innerHTML = + '' + + '" + + "" + + ('' + + cityssm.escapeHTML(lot.lotName) + + "") + + "" + + cityssm.escapeHTML(lot.mapName) + + "" + + ("" + + cityssm.escapeHTML(lot.lotType) + + "") + + ("" + + cityssm.escapeHTML(lot.lotStatus) + + ""); + rowElement + .querySelector(".button--addLot") + .addEventListener("click", doAddLot); + searchResultsContainerElement + .querySelector("tbody") + .append(rowElement); + } + }); + }; + cityssm.openHtmlModal("workOrder-addLot", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + searchFormElement = modalElement.querySelector("form"); + searchResultsContainerElement = + modalElement.querySelector("#resultsContainer--lotAdd"); + modalElement.querySelector("#lotSearch--notWorkOrderId").value = workOrderId; + const lotStatusElement = modalElement.querySelector("#lotSearch--lotStatusId"); + for (const lotStatus of exports.lotStatuses) { + const optionElement = document.createElement("option"); + optionElement.value = + lotStatus.lotStatusId.toString(); + optionElement.textContent = lotStatus.lotStatus; + lotStatusElement.append(optionElement); + } + doSearch(); + }, + onshown: (modalElement) => { + bulmaJS.toggleHtmlClipped(); + modalElement + .querySelector("#lotSearch--lotName") + .addEventListener("change", doSearch); + modalElement + .querySelector("#lotSearch--lotStatusId") + .addEventListener("change", doSearch); + searchFormElement.addEventListener("submit", doSearch); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + } + if (!isCreate) { + let workOrderMilestones = exports.workOrderMilestones; + delete exports.workOrderMilestones; + const completeMilestone = (clickEvent) => { + clickEvent.preventDefault(); + const workOrderMilestoneId = clickEvent.currentTarget.closest(".container--milestone").dataset.workOrderMilestoneId; + const doComplete = () => { + cityssm.postJSON(urlPrefix + "/workOrders/doCompleteWorkOrderMilestone", { + workOrderId, + workOrderMilestoneId + }, (responseJSON) => { + if (responseJSON.success) { + workOrderMilestones = responseJSON.workOrderMilestones; + renderMilestones(); + } + else { + bulmaJS.alert({ + title: "Error Completing Milestone", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + bulmaJS.confirm({ + title: "Complete Milestone", + message: "Are you sure you want to complete this milestone?", + contextualColorName: "warning", + okButton: { + text: "Yes, Complete Milestone", + callbackFunction: doComplete + } + }); + }; + const reopenMilestone = (clickEvent) => { + clickEvent.preventDefault(); + const workOrderMilestoneId = clickEvent.currentTarget.closest(".container--milestone").dataset.workOrderMilestoneId; + const doReopen = () => { + cityssm.postJSON(urlPrefix + "/workOrders/doReopenWorkOrderMilestone", { + workOrderId, + workOrderMilestoneId + }, (responseJSON) => { + if (responseJSON.success) { + workOrderMilestones = responseJSON.workOrderMilestones; + renderMilestones(); + } + else { + bulmaJS.alert({ + title: "Error Reopening Milestone", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + bulmaJS.confirm({ + title: "Reopen Milestone", + message: "Are you sure you want to remove the completion status from this milestone, and reopen it?", + contextualColorName: "warning", + okButton: { + text: "Yes, Reopen Milestone", + callbackFunction: doReopen + } + }); + }; + const deleteMilestone = (clickEvent) => { + clickEvent.preventDefault(); + }; + const renderMilestones = () => { + const milestonesPanelElement = document.querySelector("#panel--milestones"); + const panelBlockElementsToDelete = milestonesPanelElement.querySelectorAll(".panel-block"); + for (const panelBlockToDelete of panelBlockElementsToDelete) { + panelBlockToDelete.remove(); + } + for (const milestone of workOrderMilestones) { + const panelBlockElement = document.createElement("div"); + panelBlockElement.className = + "panel-block is-block container--milestone"; + panelBlockElement.dataset.workOrderMilestoneId = + milestone.workOrderMilestoneId.toString(); + panelBlockElement.innerHTML = + '
' + + ('
' + + (milestone.workOrderMilestoneCompletionDate + ? '' + + '' + + "" + : '") + + "
") + + ('
' + + (milestone.workOrderMilestoneTypeId + ? "" + + cityssm.escapeHTML(milestone.workOrderMilestoneType) + + "
" + : "") + + milestone.workOrderMilestoneDateString + + "
" + + '' + + cityssm.escapeHTML(milestone.workOrderMilestoneDescription) + + "" + + "
") + + ('
' + + '" + + "
") + + "
"; + if (milestone.workOrderMilestoneCompletionDate) { + panelBlockElement + .querySelector(".button--reopenMilestone") + .addEventListener("click", reopenMilestone); + } + else { + panelBlockElement + .querySelector(".button--completeMilestone") + .addEventListener("click", completeMilestone); + } + panelBlockElement + .querySelector(".button--deleteMilestone") + .addEventListener("click", deleteMilestone); + milestonesPanelElement.append(panelBlockElement); + } + bulmaJS.init(milestonesPanelElement); + }; + renderMilestones(); } })(); diff --git a/public-typescript/workOrderEdit.ts b/public-typescript/workOrderEdit.ts index a5d2a739..28e82abc 100644 --- a/public-typescript/workOrderEdit.ts +++ b/public-typescript/workOrderEdit.ts @@ -2,12 +2,17 @@ import type { cityssmGlobal } from "@cityssm/bulma-webapp-js/src/types"; import type { BulmaJS } from "@cityssm/bulma-js/types"; + +import type * as globalTypes from "../types/globalTypes"; import type * as recordTypes from "../types/recordTypes"; +import { response } from "express"; declare const cityssm: cityssmGlobal; declare const bulmaJS: BulmaJS; (() => { + const los = exports.los as globalTypes.LOS; + const urlPrefix = document.querySelector("main").dataset.urlPrefix; const workOrderId = ( @@ -124,9 +129,82 @@ declare const bulmaJS: BulmaJS; }); }; + const addLot = ( + lotId: number | string, + callbackFunction?: (success?: boolean) => void + ) => { + cityssm.postJSON( + urlPrefix + "/workOrders/doAddWorkOrderLot", + { + workOrderId, + lotId + }, + (responseJSON: { + success: boolean; + errorMessage?: string; + workOrderLots?: recordTypes.Lot[]; + }) => { + if (responseJSON.success) { + workOrderLots = responseJSON.workOrderLots; + renderRelatedLotsAndOccupancies(); + } else { + bulmaJS.alert({ + title: "Error Adding " + exports.aliases.lot, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + + if (callbackFunction) { + callbackFunction(responseJSON.success); + } + } + ); + }; + + const addLotOccupancy = ( + lotOccupancyId: number | string, + callbackFunction?: (success?: boolean) => void + ) => { + cityssm.postJSON( + urlPrefix + "/workOrders/doAddWorkOrderLotOccupancy", + { + workOrderId, + lotOccupancyId + }, + (responseJSON: { + success: boolean; + errorMessage?: string; + workOrderLotOccupancies?: recordTypes.LotOccupancy[]; + }) => { + if (responseJSON.success) { + workOrderLotOccupancies = + responseJSON.workOrderLotOccupancies; + renderRelatedLotsAndOccupancies(); + } else { + bulmaJS.alert({ + title: "Error Adding " + exports.aliases.occupancy, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + + if (callbackFunction) { + callbackFunction(responseJSON.success); + } + } + ); + }; + + const addLotFromLotOccupancy = (clickEvent: Event) => { + const lotId = (clickEvent.currentTarget as HTMLElement).dataset + .lotId; + addLot(lotId); + }; + const renderRelatedOccupancies = () => { const occupanciesContainerElement = document.querySelector( - "#relatedTab--lotOccupancies" + "#container--lotOccupancies" ) as HTMLElement; document.querySelector( @@ -173,6 +251,12 @@ declare const bulmaJS: BulmaJS; lotOccupancy.occupancyEndDateString < currentDateString ); + const hasLotRecord = + lotOccupancy.lotId && + workOrderLots.some((lot) => { + return lotOccupancy.lotId === lot.lotId; + }); + rowElement.innerHTML = '' + (isActive @@ -191,41 +275,81 @@ declare const bulmaJS: BulmaJS; '">' + cityssm.escapeHTML(lotOccupancy.occupancyType) + "" + - "") + - ("" + - (lotOccupancy.lotId - ? cityssm.escapeHTML(lotOccupancy.lotName) - : '(No ' + - exports.aliases.lot + - ")") + - "") + - ("" + lotOccupancy.occupancyStartDateString + "") + - ("" + - (lotOccupancy.occupancyEndDate - ? lotOccupancy.occupancyEndDateString - : '(No End Date)') + - "") + - ("" + - (lotOccupancy.lotOccupancyOccupants.length === 0 - ? '(No ' + - cityssm.escapeHTML(exports.aliases.occupants) + - ")" - : cityssm.escapeHTML( - lotOccupancy.lotOccupancyOccupants[0] - .occupantName - ) + - (lotOccupancy.lotOccupancyOccupants.length > 1 - ? " plus " + - (lotOccupancy.lotOccupancyOccupants.length - - 1) - : "")) + - "") + - ("" + - '" + ""); + if (lotOccupancy.lotId) { + rowElement.insertAdjacentHTML( + "beforeend", + "" + + cityssm.escapeHTML(lotOccupancy.lotName) + + (hasLotRecord + ? "" + : ' ") + + "" + ); + } else { + rowElement.insertAdjacentHTML( + "beforeend", + "" + + '(No ' + + exports.aliases.lot + + ")" + + "" + ); + } + + rowElement.insertAdjacentHTML( + "beforeend", + "" + + lotOccupancy.occupancyStartDateString + + "" + + ("" + + (lotOccupancy.occupancyEndDate + ? lotOccupancy.occupancyEndDateString + : '(No End Date)') + + "") + + ("" + + (lotOccupancy.lotOccupancyOccupants.length === 0 + ? '(No ' + + cityssm.escapeHTML( + exports.aliases.occupants + ) + + ")" + : cityssm.escapeHTML( + lotOccupancy.lotOccupancyOccupants[0] + .occupantName + ) + + (lotOccupancy.lotOccupancyOccupants.length > 1 + ? " plus " + + (lotOccupancy.lotOccupancyOccupants + .length - + 1) + : "")) + + "") + + ("" + + '" + + "") + ); + + if (lotOccupancy.lotId && !hasLotRecord) { + rowElement + .querySelector(".button--addLot") + .addEventListener("click", addLotFromLotOccupancy); + } + rowElement .querySelector(".button--deleteLotOccupancy") .addEventListener("click", deleteLotOccupancy); @@ -256,8 +380,7 @@ declare const bulmaJS: BulmaJS; workOrderLots?: recordTypes.Lot[]; }) => { if (responseJSON.success) { - workOrderLots = - responseJSON.workOrderLots; + workOrderLots = responseJSON.workOrderLots; renderRelatedLotsAndOccupancies(); } else { bulmaJS.alert({ @@ -293,7 +416,7 @@ declare const bulmaJS: BulmaJS; const renderRelatedLots = () => { const lotsContainerElement = document.querySelector( - "#relatedTab--lots" + "#container--lots" ) as HTMLElement; document.querySelector( @@ -364,6 +487,348 @@ declare const bulmaJS: BulmaJS; }; renderRelatedLotsAndOccupancies(); + + document + .querySelector("#button--addLotOccupancy") + .addEventListener("click", () => { + let searchFormElement: HTMLFormElement; + let searchResultsContainerElement: HTMLElement; + + const doAddLotOccupancy = (clickEvent: Event) => { + const rowElement = ( + clickEvent.currentTarget as HTMLElement + ).closest("tr"); + + const lotOccupancyId = rowElement.dataset.lotOccupancyId; + + addLotOccupancy(lotOccupancyId, (success) => { + if (success) { + rowElement.remove(); + } + }); + }; + + const doSearch = (event?: Event) => { + if (event) { + event.preventDefault(); + } + + searchResultsContainerElement.innerHTML = + '

' + + '
' + + "Searching..." + + "

"; + + cityssm.postJSON( + urlPrefix + "/lotOccupancies/doSearchLotOccupancies", + searchFormElement, + (responseJSON: { + lotOccupancies: recordTypes.LotOccupancy[]; + }) => { + if (responseJSON.lotOccupancies.length === 0) { + searchResultsContainerElement.innerHTML = + '
' + + '

There are no records that meet the search criteria.

' + + "
"; + + return; + } + + searchResultsContainerElement.innerHTML = + '' + + "" + + "" + + '' + + ("") + + ("") + + "" + + "" + + ("") + + "" + + "" + + "" + + "
" + + exports.aliases.occupancy + + " Type" + exports.aliases.lot + "Start DateEnd Date" + exports.aliases.occupants + "
"; + + for (const lotOccupancy of responseJSON.lotOccupancies) { + const rowElement = document.createElement("tr"); + rowElement.className = + "container--lotOccupancy"; + rowElement.dataset.lotOccupancyId = + lotOccupancy.lotOccupancyId.toString(); + + rowElement.innerHTML = + '' + + '" + + "" + + ('' + + cityssm.escapeHTML( + lotOccupancy.occupancyType + ) + + ""); + + if (lotOccupancy.lotId) { + rowElement.insertAdjacentHTML( + "beforeend", + "" + + cityssm.escapeHTML( + lotOccupancy.lotName + ) + + "" + ); + } else { + rowElement.insertAdjacentHTML( + "beforeend", + "" + + '(No ' + + exports.aliases.lot + + ")" + + "" + ); + } + + rowElement.insertAdjacentHTML( + "beforeend", + "" + + lotOccupancy.occupancyStartDateString + + "" + + ("" + + (lotOccupancy.occupancyEndDate + ? lotOccupancy.occupancyEndDateString + : '(No End Date)') + + "") + + ("" + + (lotOccupancy.lotOccupancyOccupants + .length === 0 + ? '(No ' + + cityssm.escapeHTML( + exports.aliases.occupants + ) + + ")" + : cityssm.escapeHTML( + lotOccupancy + .lotOccupancyOccupants[0] + .occupantName + ) + + (lotOccupancy + .lotOccupancyOccupants + .length > 1 + ? " plus " + + (lotOccupancy + .lotOccupancyOccupants + .length - + 1) + : "")) + + "") + ); + + rowElement + .querySelector(".button--addLotOccupancy") + .addEventListener( + "click", + doAddLotOccupancy + ); + + searchResultsContainerElement + .querySelector("tbody") + .append(rowElement); + } + } + ); + }; + + cityssm.openHtmlModal("workOrder-addLotOccupancy", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + + searchFormElement = modalElement.querySelector("form"); + searchResultsContainerElement = + modalElement.querySelector( + "#resultsContainer--lotOccupancyAdd" + ); + + ( + modalElement.querySelector( + "#lotOccupancySearch--notWorkOrderId" + ) as HTMLInputElement + ).value = workOrderId; + + ( + modalElement.querySelector( + "#lotOccupancySearch--occupancyEffectiveDateString" + ) as HTMLInputElement + ).value = ( + document.querySelector( + "#workOrderEdit--workOrderOpenDateString" + ) as HTMLInputElement + ).value; + + doSearch(); + }, + onshown: (modalElement) => { + bulmaJS.toggleHtmlClipped(); + + modalElement + .querySelector("#lotOccupancySearch--occupantName") + .addEventListener("change", doSearch); + modalElement + .querySelector("#lotOccupancySearch--lotName") + .addEventListener("change", doSearch); + + searchFormElement.addEventListener("submit", doSearch); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + + document + .querySelector("#button--addLot") + .addEventListener("click", () => { + let searchFormElement: HTMLFormElement; + let searchResultsContainerElement: HTMLElement; + + const doAddLot = (clickEvent: Event) => { + const rowElement = ( + clickEvent.currentTarget as HTMLElement + ).closest("tr"); + + const lotId = rowElement.dataset.lotId; + + addLot(lotId, (success) => { + if (success) { + rowElement.remove(); + } + }); + }; + + const doSearch = (event?: Event) => { + if (event) { + event.preventDefault(); + } + + searchResultsContainerElement.innerHTML = + '

' + + '
' + + "Searching..." + + "

"; + + cityssm.postJSON( + urlPrefix + "/lots/doSearchLots", + searchFormElement, + (responseJSON: { lots: recordTypes.Lot[] }) => { + if (responseJSON.lots.length === 0) { + searchResultsContainerElement.innerHTML = + '
' + + '

There are no records that meet the search criteria.

' + + "
"; + + return; + } + + searchResultsContainerElement.innerHTML = + '' + + "" + + "" + + '' + + ("") + + ("") + + ("") + + "" + + "" + + "" + + "" + + "
" + exports.aliases.lot + "" + exports.aliases.map + "" + exports.aliases.lot + " TypeStatus
"; + + for (const lot of responseJSON.lots) { + const rowElement = document.createElement("tr"); + rowElement.className = "container--lot"; + rowElement.dataset.lotId = lot.lotId.toString(); + + rowElement.innerHTML = + '' + + '" + + "" + + ('' + + cityssm.escapeHTML(lot.lotName) + + "") + + "" + + cityssm.escapeHTML(lot.mapName) + + "" + + ("" + + cityssm.escapeHTML(lot.lotType) + + "") + + ("" + + cityssm.escapeHTML(lot.lotStatus) + + ""); + + rowElement + .querySelector(".button--addLot") + .addEventListener("click", doAddLot); + + searchResultsContainerElement + .querySelector("tbody") + .append(rowElement); + } + } + ); + }; + + cityssm.openHtmlModal("workOrder-addLot", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + + searchFormElement = modalElement.querySelector("form"); + searchResultsContainerElement = + modalElement.querySelector( + "#resultsContainer--lotAdd" + ); + + ( + modalElement.querySelector( + "#lotSearch--notWorkOrderId" + ) as HTMLInputElement + ).value = workOrderId; + + const lotStatusElement = modalElement.querySelector( + "#lotSearch--lotStatusId" + ) as HTMLSelectElement; + + for (const lotStatus of exports.lotStatuses as recordTypes.LotStatus[]) { + const optionElement = + document.createElement("option"); + optionElement.value = + lotStatus.lotStatusId.toString(); + optionElement.textContent = lotStatus.lotStatus; + lotStatusElement.append(optionElement); + } + + doSearch(); + }, + onshown: (modalElement) => { + bulmaJS.toggleHtmlClipped(); + + modalElement + .querySelector("#lotSearch--lotName") + .addEventListener("change", doSearch); + + modalElement + .querySelector("#lotSearch--lotStatusId") + .addEventListener("change", doSearch); + + searchFormElement.addEventListener("submit", doSearch); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); } /* @@ -373,4 +838,193 @@ declare const bulmaJS: BulmaJS; /* * Milestones */ + + if (!isCreate) { + let workOrderMilestones = + exports.workOrderMilestones as recordTypes.WorkOrderMilestone[]; + delete exports.workOrderMilestones; + + const completeMilestone = (clickEvent: Event) => { + clickEvent.preventDefault(); + + const workOrderMilestoneId = ( + (clickEvent.currentTarget as HTMLElement).closest( + ".container--milestone" + ) as HTMLElement + ).dataset.workOrderMilestoneId; + + const doComplete = () => { + cityssm.postJSON(urlPrefix + "/workOrders/doCompleteWorkOrderMilestone", { + workOrderId, + workOrderMilestoneId + }, + (responseJSON: {success: boolean; errorMessage?: string; workOrderMilestones?: recordTypes.WorkOrderMilestone[];}) => { + if (responseJSON.success) { + workOrderMilestones = responseJSON.workOrderMilestones; + renderMilestones(); + } else { + bulmaJS.alert({ + title: "Error Completing Milestone", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + + bulmaJS.confirm({ + title: "Complete Milestone", + message: "Are you sure you want to complete this milestone?", + contextualColorName: "warning", + okButton: { + text: "Yes, Complete Milestone", + callbackFunction: doComplete + } + }); + }; + + const reopenMilestone = (clickEvent: Event) => { + clickEvent.preventDefault(); + + const workOrderMilestoneId = ( + (clickEvent.currentTarget as HTMLElement).closest( + ".container--milestone" + ) as HTMLElement + ).dataset.workOrderMilestoneId; + + const doReopen = () => { + cityssm.postJSON(urlPrefix + "/workOrders/doReopenWorkOrderMilestone", { + workOrderId, + workOrderMilestoneId + }, + (responseJSON: {success: boolean; errorMessage?: string; workOrderMilestones?: recordTypes.WorkOrderMilestone[];}) => { + if (responseJSON.success) { + workOrderMilestones = responseJSON.workOrderMilestones; + renderMilestones(); + } else { + bulmaJS.alert({ + title: "Error Reopening Milestone", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + + bulmaJS.confirm({ + title: "Reopen Milestone", + message: "Are you sure you want to remove the completion status from this milestone, and reopen it?", + contextualColorName: "warning", + okButton: { + text: "Yes, Reopen Milestone", + callbackFunction: doReopen + } + }); + }; + + const deleteMilestone = (clickEvent: Event) => { + clickEvent.preventDefault(); + }; + + const renderMilestones = () => { + // Clear milestones panel + + const milestonesPanelElement = document.querySelector( + "#panel--milestones" + ) as HTMLElement; + + const panelBlockElementsToDelete = + milestonesPanelElement.querySelectorAll(".panel-block"); + + for (const panelBlockToDelete of panelBlockElementsToDelete) { + panelBlockToDelete.remove(); + } + + for (const milestone of workOrderMilestones) { + const panelBlockElement = document.createElement("div"); + panelBlockElement.className = + "panel-block is-block container--milestone"; + + panelBlockElement.dataset.workOrderMilestoneId = + milestone.workOrderMilestoneId.toString(); + + panelBlockElement.innerHTML = + '
' + + ('
' + + (milestone.workOrderMilestoneCompletionDate + ? '' + + '' + + "" + : '") + + "
") + + ('
' + + (milestone.workOrderMilestoneTypeId + ? "" + + cityssm.escapeHTML( + milestone.workOrderMilestoneType + ) + + "
" + : "") + + milestone.workOrderMilestoneDateString + + "
" + + '' + + cityssm.escapeHTML( + milestone.workOrderMilestoneDescription + ) + + "" + + "
") + + ('
' + + '" + + "
") + + "
"; + + if (milestone.workOrderMilestoneCompletionDate) { + panelBlockElement + .querySelector(".button--reopenMilestone") + .addEventListener("click", reopenMilestone); + } else { + panelBlockElement + .querySelector(".button--completeMilestone") + .addEventListener("click", completeMilestone); + } + + panelBlockElement + .querySelector(".button--deleteMilestone") + .addEventListener("click", deleteMilestone); + + milestonesPanelElement.append(panelBlockElement); + } + + bulmaJS.init(milestonesPanelElement); + }; + + renderMilestones(); + } })(); diff --git a/public/html/workOrder-addLot.html b/public/html/workOrder-addLot.html new file mode 100644 index 00000000..66f2d1b7 --- /dev/null +++ b/public/html/workOrder-addLot.html @@ -0,0 +1,56 @@ + \ No newline at end of file diff --git a/public/html/workOrder-addLotOccupancy.html b/public/html/workOrder-addLotOccupancy.html new file mode 100644 index 00000000..7b9a0e7c --- /dev/null +++ b/public/html/workOrder-addLotOccupancy.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/public/javascripts/workOrderEdit.min.js b/public/javascripts/workOrderEdit.min.js index 0e04ec49..0202b950 100644 --- a/public/javascripts/workOrderEdit.min.js +++ b/public/javascripts/workOrderEdit.min.js @@ -1 +1 @@ -"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const t=document.querySelector("main").dataset.urlPrefix,e=document.querySelector("#workOrderEdit--workOrderId").value,s=""===e;if(document.querySelector("#form--workOrderEdit").addEventListener("submit",e=>{e.preventDefault(),cityssm.postJSON(t+"/workOrders/"+(s?"doCreateWorkOrder":"doUpdateWorkOrder"),e.currentTarget,e=>{e.success?s?window.location.href=t+"/workOrders/"+e.workOrderId+"/edit":bulmaJS.alert({message:"Work Order Updated Successfully",contextualColorName:"success"}):bulmaJS.alert({title:"Error Updating Work Order",message:e.errorMessage,contextualColorName:"danger"})})}),!s){let s=exports.workOrderLots;delete exports.workOrderLots;let a=exports.workOrderLotOccupancies;delete exports.workOrderLotOccupancies;const o=s=>{const o=s.currentTarget.closest(".container--lotOccupancy").dataset.lotOccupancyId;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:()=>{cityssm.postJSON(t+"/workOrders/doDeleteWorkOrderLotOccupancy",{workOrderId:e,lotOccupancyId:o},t=>{t.success?(a=t.workOrderLotOccupancies,n()):bulmaJS.alert({title:"Error Deleting Relationship",message:t.errorMessage,contextualColorName:"danger"})})}}})},r=()=>{const e=document.querySelector("#relatedTab--lotOccupancies");if(document.querySelector(".tabs a[href='#relatedTab--lotOccupancies'] .tag").textContent=a.length.toString(),0===a.length)return void(e.innerHTML='

There are no '+exports.aliases.occupancies.toLowerCase()+" associated with this work order.

");e.innerHTML='
'+exports.aliases.occupancy+" Type"+exports.aliases.lot+"Start DateEnd Date"+exports.aliases.occupants+'
';const s=cityssm.dateToString(new Date);for(const r of a){const a=document.createElement("tr");a.className="container--lotOccupancy",a.dataset.lotOccupancyId=r.lotOccupancyId.toString();const c=!(r.occupancyEndDate&&r.occupancyEndDateString'+(c?'':'')+''+cityssm.escapeHTML(r.occupancyType)+""+(r.lotId?cityssm.escapeHTML(r.lotName):'(No '+exports.aliases.lot+")")+""+r.occupancyStartDateString+""+(r.occupancyEndDate?r.occupancyEndDateString:'(No End Date)')+""+(0===r.lotOccupancyOccupants.length?'(No '+cityssm.escapeHTML(exports.aliases.occupants)+")":cityssm.escapeHTML(r.lotOccupancyOccupants[0].occupantName)+(r.lotOccupancyOccupants.length>1?" plus "+(r.lotOccupancyOccupants.length-1):""))+'',a.querySelector(".button--deleteLotOccupancy").addEventListener("click",o),e.querySelector("tbody").append(a)}},c=a=>{const o=a.currentTarget.closest(".container--lot").dataset.lotId;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:()=>{cityssm.postJSON(t+"/workOrders/doDeleteWorkOrderLot",{workOrderId:e,lotId:o},t=>{t.success?(s=t.workOrderLots,n()):bulmaJS.alert({title:"Error Deleting Relationship",message:t.errorMessage,contextualColorName:"danger"})})}}})},l=()=>{const e=document.querySelector("#relatedTab--lots");if(document.querySelector(".tabs a[href='#relatedTab--lots'] .tag").textContent=s.length.toString(),0!==s.length){e.innerHTML='
'+exports.aliases.lot+""+exports.aliases.map+""+exports.aliases.lot+' TypeStatus
';for(const a of s){const s=document.createElement("tr");s.className="container--lot",s.dataset.lotId=a.lotId.toString(),s.innerHTML=''+cityssm.escapeHTML(a.lotName)+""+cityssm.escapeHTML(a.mapName)+""+cityssm.escapeHTML(a.lotType)+""+cityssm.escapeHTML(a.lotStatus)+'',s.querySelector(".button--deleteLot").addEventListener("click",c),e.querySelector("tbody").append(s)}}else e.innerHTML='

There are no '+exports.aliases.lots.toLowerCase()+" associated with this work order.

"},n=()=>{r(),l()};n()}})(); \ No newline at end of file +"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=exports.los,t=document.querySelector("main").dataset.urlPrefix,s=document.querySelector("#workOrderEdit--workOrderId").value,o=""===s;if(document.querySelector("#form--workOrderEdit").addEventListener("submit",e=>{e.preventDefault(),cityssm.postJSON(t+"/workOrders/"+(o?"doCreateWorkOrder":"doUpdateWorkOrder"),e.currentTarget,e=>{e.success?o?window.location.href=t+"/workOrders/"+e.workOrderId+"/edit":bulmaJS.alert({message:"Work Order Updated Successfully",contextualColorName:"success"}):bulmaJS.alert({title:"Error Updating Work Order",message:e.errorMessage,contextualColorName:"danger"})})}),!o){let o=exports.workOrderLots;delete exports.workOrderLots;let a=exports.workOrderLotOccupancies;delete exports.workOrderLotOccupancies;const r=e=>{const o=e.currentTarget.closest(".container--lotOccupancy").dataset.lotOccupancyId;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:()=>{cityssm.postJSON(t+"/workOrders/doDeleteWorkOrderLotOccupancy",{workOrderId:s,lotOccupancyId:o},e=>{e.success?(a=e.workOrderLotOccupancies,u()):bulmaJS.alert({title:"Error Deleting Relationship",message:e.errorMessage,contextualColorName:"danger"})})}}})},c=(e,a)=>{cityssm.postJSON(t+"/workOrders/doAddWorkOrderLot",{workOrderId:s,lotId:e},e=>{e.success?(o=e.workOrderLots,u()):bulmaJS.alert({title:"Error Adding "+exports.aliases.lot,message:e.errorMessage,contextualColorName:"danger"}),a&&a(e.success)})},n=(e,o)=>{cityssm.postJSON(t+"/workOrders/doAddWorkOrderLotOccupancy",{workOrderId:s,lotOccupancyId:e},e=>{e.success?(a=e.workOrderLotOccupancies,u()):bulmaJS.alert({title:"Error Adding "+exports.aliases.occupancy,message:e.errorMessage,contextualColorName:"danger"}),o&&o(e.success)})},l=e=>{const t=e.currentTarget.dataset.lotId;c(t)},i=()=>{const e=document.querySelector("#container--lotOccupancies");if(document.querySelector(".tabs a[href='#relatedTab--lotOccupancies'] .tag").textContent=a.length.toString(),0===a.length)return void(e.innerHTML='

There are no '+exports.aliases.occupancies.toLowerCase()+" associated with this work order.

");e.innerHTML='
'+exports.aliases.occupancy+" Type"+exports.aliases.lot+"Start DateEnd Date"+exports.aliases.occupants+'
';const s=cityssm.dateToString(new Date);for(const c of a){const a=document.createElement("tr");a.className="container--lotOccupancy",a.dataset.lotOccupancyId=c.lotOccupancyId.toString();const n=!(c.occupancyEndDate&&c.occupancyEndDateStringc.lotId===e.lotId);a.innerHTML=''+(n?'':'')+''+cityssm.escapeHTML(c.occupancyType)+"",c.lotId?a.insertAdjacentHTML("beforeend",""+cityssm.escapeHTML(c.lotName)+(i?"":' ')+""):a.insertAdjacentHTML("beforeend",'(No '+exports.aliases.lot+")"),a.insertAdjacentHTML("beforeend",""+c.occupancyStartDateString+""+(c.occupancyEndDate?c.occupancyEndDateString:'(No End Date)')+""+(0===c.lotOccupancyOccupants.length?'(No '+cityssm.escapeHTML(exports.aliases.occupants)+")":cityssm.escapeHTML(c.lotOccupancyOccupants[0].occupantName)+(c.lotOccupancyOccupants.length>1?" plus "+(c.lotOccupancyOccupants.length-1):""))+''),c.lotId&&!i&&a.querySelector(".button--addLot").addEventListener("click",l),a.querySelector(".button--deleteLotOccupancy").addEventListener("click",r),e.querySelector("tbody").append(a)}},d=e=>{const a=e.currentTarget.closest(".container--lot").dataset.lotId;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:()=>{cityssm.postJSON(t+"/workOrders/doDeleteWorkOrderLot",{workOrderId:s,lotId:a},e=>{e.success?(o=e.workOrderLots,u()):bulmaJS.alert({title:"Error Deleting Relationship",message:e.errorMessage,contextualColorName:"danger"})})}}})},p=()=>{const e=document.querySelector("#container--lots");if(document.querySelector(".tabs a[href='#relatedTab--lots'] .tag").textContent=o.length.toString(),0!==o.length){e.innerHTML='
'+exports.aliases.lot+""+exports.aliases.map+""+exports.aliases.lot+' TypeStatus
';for(const s of o){const o=document.createElement("tr");o.className="container--lot",o.dataset.lotId=s.lotId.toString(),o.innerHTML=''+cityssm.escapeHTML(s.lotName)+""+cityssm.escapeHTML(s.mapName)+""+cityssm.escapeHTML(s.lotType)+""+cityssm.escapeHTML(s.lotStatus)+'',o.querySelector(".button--deleteLot").addEventListener("click",d),e.querySelector("tbody").append(o)}}else e.innerHTML='

There are no '+exports.aliases.lots.toLowerCase()+" associated with this work order.

"},u=()=>{i(),p()};u(),document.querySelector("#button--addLotOccupancy").addEventListener("click",()=>{let o,a;const r=e=>{const t=e.currentTarget.closest("tr"),s=t.dataset.lotOccupancyId;n(s,e=>{e&&t.remove()})},c=e=>{e&&e.preventDefault(),a.innerHTML='


Searching...

',cityssm.postJSON(t+"/lotOccupancies/doSearchLotOccupancies",o,e=>{if(0!==e.lotOccupancies.length){a.innerHTML='
'+exports.aliases.occupancy+" Type"+exports.aliases.lot+"Start DateEnd Date"+exports.aliases.occupants+"
";for(const t of e.lotOccupancies){const e=document.createElement("tr");e.className="container--lotOccupancy",e.dataset.lotOccupancyId=t.lotOccupancyId.toString(),e.innerHTML=''+cityssm.escapeHTML(t.occupancyType)+"",t.lotId?e.insertAdjacentHTML("beforeend",""+cityssm.escapeHTML(t.lotName)+""):e.insertAdjacentHTML("beforeend",'(No '+exports.aliases.lot+")"),e.insertAdjacentHTML("beforeend",""+t.occupancyStartDateString+""+(t.occupancyEndDate?t.occupancyEndDateString:'(No End Date)')+""+(0===t.lotOccupancyOccupants.length?'(No '+cityssm.escapeHTML(exports.aliases.occupants)+")":cityssm.escapeHTML(t.lotOccupancyOccupants[0].occupantName)+(t.lotOccupancyOccupants.length>1?" plus "+(t.lotOccupancyOccupants.length-1):""))+""),e.querySelector(".button--addLotOccupancy").addEventListener("click",r),a.querySelector("tbody").append(e)}}else a.innerHTML='

There are no records that meet the search criteria.

'})};cityssm.openHtmlModal("workOrder-addLotOccupancy",{onshow:t=>{e.populateAliases(t),o=t.querySelector("form"),a=t.querySelector("#resultsContainer--lotOccupancyAdd"),t.querySelector("#lotOccupancySearch--notWorkOrderId").value=s,t.querySelector("#lotOccupancySearch--occupancyEffectiveDateString").value=document.querySelector("#workOrderEdit--workOrderOpenDateString").value,c()},onshown:e=>{bulmaJS.toggleHtmlClipped(),e.querySelector("#lotOccupancySearch--occupantName").addEventListener("change",c),e.querySelector("#lotOccupancySearch--lotName").addEventListener("change",c),o.addEventListener("submit",c)},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})}),document.querySelector("#button--addLot").addEventListener("click",()=>{let o,a;const r=e=>{const t=e.currentTarget.closest("tr"),s=t.dataset.lotId;c(s,e=>{e&&t.remove()})},n=e=>{e&&e.preventDefault(),a.innerHTML='


Searching...

',cityssm.postJSON(t+"/lots/doSearchLots",o,e=>{if(0!==e.lots.length){a.innerHTML='
'+exports.aliases.lot+""+exports.aliases.map+""+exports.aliases.lot+" TypeStatus
";for(const t of e.lots){const e=document.createElement("tr");e.className="container--lot",e.dataset.lotId=t.lotId.toString(),e.innerHTML=''+cityssm.escapeHTML(t.lotName)+""+cityssm.escapeHTML(t.mapName)+""+cityssm.escapeHTML(t.lotType)+""+cityssm.escapeHTML(t.lotStatus)+"",e.querySelector(".button--addLot").addEventListener("click",r),a.querySelector("tbody").append(e)}}else a.innerHTML='

There are no records that meet the search criteria.

'})};cityssm.openHtmlModal("workOrder-addLot",{onshow:t=>{e.populateAliases(t),o=t.querySelector("form"),a=t.querySelector("#resultsContainer--lotAdd"),t.querySelector("#lotSearch--notWorkOrderId").value=s;const r=t.querySelector("#lotSearch--lotStatusId");for(const e of exports.lotStatuses){const t=document.createElement("option");t.value=e.lotStatusId.toString(),t.textContent=e.lotStatus,r.append(t)}n()},onshown:e=>{bulmaJS.toggleHtmlClipped(),e.querySelector("#lotSearch--lotName").addEventListener("change",n),e.querySelector("#lotSearch--lotStatusId").addEventListener("change",n),o.addEventListener("submit",n)},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})})}if(!o){let e=exports.workOrderMilestones;delete exports.workOrderMilestones;const o=o=>{o.preventDefault();const a=o.currentTarget.closest(".container--milestone").dataset.workOrderMilestoneId;bulmaJS.confirm({title:"Complete Milestone",message:"Are you sure you want to complete this milestone?",contextualColorName:"warning",okButton:{text:"Yes, Complete Milestone",callbackFunction:()=>{cityssm.postJSON(t+"/workOrders/doCompleteWorkOrderMilestone",{workOrderId:s,workOrderMilestoneId:a},t=>{t.success?(e=t.workOrderMilestones,c()):bulmaJS.alert({title:"Error Completing Milestone",message:t.errorMessage,contextualColorName:"danger"})})}}})},a=o=>{o.preventDefault();const a=o.currentTarget.closest(".container--milestone").dataset.workOrderMilestoneId;bulmaJS.confirm({title:"Reopen Milestone",message:"Are you sure you want to remove the completion status from this milestone, and reopen it?",contextualColorName:"warning",okButton:{text:"Yes, Reopen Milestone",callbackFunction:()=>{cityssm.postJSON(t+"/workOrders/doReopenWorkOrderMilestone",{workOrderId:s,workOrderMilestoneId:a},t=>{t.success?(e=t.workOrderMilestones,c()):bulmaJS.alert({title:"Error Reopening Milestone",message:t.errorMessage,contextualColorName:"danger"})})}}})},r=e=>{e.preventDefault()},c=()=>{const t=document.querySelector("#panel--milestones"),s=t.querySelectorAll(".panel-block");for(const e of s)e.remove();for(const s of e){const e=document.createElement("div");e.className="panel-block is-block container--milestone",e.dataset.workOrderMilestoneId=s.workOrderMilestoneId.toString(),e.innerHTML='
'+(s.workOrderMilestoneCompletionDate?'':'')+'
'+(s.workOrderMilestoneTypeId?""+cityssm.escapeHTML(s.workOrderMilestoneType)+"
":"")+s.workOrderMilestoneDateString+'
'+cityssm.escapeHTML(s.workOrderMilestoneDescription)+'
',s.workOrderMilestoneCompletionDate?e.querySelector(".button--reopenMilestone").addEventListener("click",a):e.querySelector(".button--completeMilestone").addEventListener("click",o),e.querySelector(".button--deleteMilestone").addEventListener("click",r),t.append(e)}bulmaJS.init(t)};c()}})(); \ No newline at end of file diff --git a/routes/workOrders.js b/routes/workOrders.js index 43ce6e7c..489c170d 100644 --- a/routes/workOrders.js +++ b/routes/workOrders.js @@ -6,8 +6,12 @@ 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_doAddWorkOrderLotOccupancy from "../handlers/workOrders-post/doAddWorkOrderLotOccupancy.js"; import handler_doDeleteWorkOrderLotOccupancy from "../handlers/workOrders-post/doDeleteWorkOrderLotOccupancy.js"; +import handler_doAddWorkOrderLot from "../handlers/workOrders-post/doAddWorkOrderLot.js"; import handler_doDeleteWorkOrderLot from "../handlers/workOrders-post/doDeleteWorkOrderLot.js"; +import handler_doCompleteWorkOrderMilestone from "../handlers/workOrders-post/doCompleteWorkOrderMilestone.js"; +import handler_doReopenWorkOrderMilestone from "../handlers/workOrders-post/doReopenWorkOrderMilestone.js"; export const router = Router(); router.get("/", handler_search); router.post("/doSearchWorkOrders", handler_doSearchWorkOrders); @@ -15,6 +19,10 @@ 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("/doAddWorkOrderLotOccupancy", permissionHandlers.updatePostHandler, handler_doAddWorkOrderLotOccupancy); router.post("/doDeleteWorkOrderLotOccupancy", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLotOccupancy); +router.post("/doAddWorkOrderLot", permissionHandlers.updatePostHandler, handler_doAddWorkOrderLot); router.post("/doDeleteWorkOrderLot", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLot); +router.post("/doCompleteWorkOrderMilestone", permissionHandlers.updatePostHandler, handler_doCompleteWorkOrderMilestone); +router.post("/doReopenWorkOrderMilestone", permissionHandlers.updatePostHandler, handler_doReopenWorkOrderMilestone); export default router; diff --git a/routes/workOrders.ts b/routes/workOrders.ts index d4f44cde..17b63aca 100644 --- a/routes/workOrders.ts +++ b/routes/workOrders.ts @@ -10,9 +10,16 @@ import handler_doReopenWorkOrder from "../handlers/workOrders-post/doReopenWorkO import handler_edit from "../handlers/workOrders-get/edit.js"; import handler_doUpdateWorkOrder from "../handlers/workOrders-post/doUpdateWorkOrder.js"; + +import handler_doAddWorkOrderLotOccupancy from "../handlers/workOrders-post/doAddWorkOrderLotOccupancy.js"; import handler_doDeleteWorkOrderLotOccupancy from "../handlers/workOrders-post/doDeleteWorkOrderLotOccupancy.js"; + +import handler_doAddWorkOrderLot from "../handlers/workOrders-post/doAddWorkOrderLot.js"; import handler_doDeleteWorkOrderLot from "../handlers/workOrders-post/doDeleteWorkOrderLot.js"; +import handler_doCompleteWorkOrderMilestone from "../handlers/workOrders-post/doCompleteWorkOrderMilestone.js"; +import handler_doReopenWorkOrderMilestone from "../handlers/workOrders-post/doReopenWorkOrderMilestone.js"; + export const router = Router(); router.get("/", handler_search); @@ -39,16 +46,40 @@ router.post( handler_doUpdateWorkOrder ); +router.post( + "/doAddWorkOrderLotOccupancy", + permissionHandlers.updatePostHandler, + handler_doAddWorkOrderLotOccupancy +); + router.post( "/doDeleteWorkOrderLotOccupancy", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLotOccupancy ); +router.post( + "/doAddWorkOrderLot", + permissionHandlers.updatePostHandler, + handler_doAddWorkOrderLot +); + router.post( "/doDeleteWorkOrderLot", permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLot ); +router.post( + "/doCompleteWorkOrderMilestone", + permissionHandlers.updatePostHandler, + handler_doCompleteWorkOrderMilestone +); + +router.post( + "/doReopenWorkOrderMilestone", + permissionHandlers.updatePostHandler, + handler_doReopenWorkOrderMilestone +); + export default router; diff --git a/views/workOrder-edit.ejs b/views/workOrder-edit.ejs index 082888de..aa726297 100644 --- a/views/workOrder-edit.ejs +++ b/views/workOrder-edit.ejs @@ -38,143 +38,170 @@ <% } %> -
- -
-
-
-
+
+
+ + +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
- +
- + +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
-
-
- -
-
- +
+ + <% if (!isCreate) { %> + + <% } %> +
+
+ + + <% if (!isCreate) { %> +
+

Related <%= configFunctions.getProperty("aliases.lots") %>

+
+ <% + const tabToSelect = (workOrder.workOrderLotOccupancies.length > 0 || workOrder.workOrderLots.length === 0 ? "lotOccupancies" : "lots"); + %> + +
+
" id="relatedTab--lotOccupancies"> +
+ +
+
+
+
" id="relatedTab--lots"> +
+ +
+
+
+
+
+
+ <% } %> +
+ <% if (!isCreate) { %> +
+
+
+
+
+
+

Milestones

+
+
+
+
+
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- - <% if (!isCreate) { %> - - <% } %> -
-
- <% if (!isCreate) { %> -
-
-
-
-
-

Related <%= configFunctions.getProperty("aliases.lots") %>

-
-
-
-
- -
-
-
-
-
- <% - const tabToSelect = (workOrder.workOrderLotOccupancies.length > 0 || workOrder.workOrderLots.length === 0 ? "lotOccupancies" : "lots"); - %> - -
-
" id="relatedTab--lotOccupancies"> -
-
" id="relatedTab--lots"> -
-
-
-
<% } %> - +
+ <%- include('_footerA'); -%> @@ -183,6 +210,8 @@ exports.workOrderLots = <%- JSON.stringify(workOrder.workOrderLots) %>; exports.workOrderLotOccupancies = <%- JSON.stringify(workOrder.workOrderLotOccupancies) %>; exports.workOrderMilestones = <%- JSON.stringify(workOrder.workOrderMilestones) %>; + + exports.lotStatuses = <%- JSON.stringify(lotStatuses) %>;