diff --git a/handlers/lots-get/new.js b/handlers/lots-get/new.js index 52c68400..d0baa9c5 100644 --- a/handlers/lots-get/new.js +++ b/handlers/lots-get/new.js @@ -1,11 +1,21 @@ import * as configFunctions from "../../helpers/functions.config.js"; import { getMaps } from "../../helpers/lotOccupancyDB/getMaps.js"; import * as cacheFunctions from "../../helpers/functions.cache.js"; -export const handler = (_request, response) => { +export const handler = (request, response) => { const lot = { lotOccupancies: [] }; const maps = getMaps(); + if (request.query.mapId) { + const mapId = Number.parseInt(request.query.mapId, 10); + const map = maps.find((possibleMap) => { + return mapId === possibleMap.mapId; + }); + if (map) { + lot.mapId = map.mapId; + lot.mapName = map.mapName; + } + } const lotTypes = cacheFunctions.getLotTypes(); const lotStatuses = cacheFunctions.getLotStatuses(); response.render("lot-edit", { diff --git a/handlers/lots-get/new.ts b/handlers/lots-get/new.ts index e149851d..7592e302 100644 --- a/handlers/lots-get/new.ts +++ b/handlers/lots-get/new.ts @@ -7,12 +7,26 @@ import * as cacheFunctions from "../../helpers/functions.cache.js"; import * as recordTypes from "../../types/recordTypes"; -export const handler: RequestHandler = (_request, response) => { +export const handler: RequestHandler = (request, response) => { const lot: recordTypes.Lot = { lotOccupancies: [] }; const maps = getMaps(); + + if (request.query.mapId) { + const mapId = Number.parseInt(request.query.mapId as string, 10); + + const map = maps.find((possibleMap) => { + return mapId === possibleMap.mapId; + }); + + if (map) { + lot.mapId = map.mapId; + lot.mapName = map.mapName; + } + } + const lotTypes = cacheFunctions.getLotTypes(); const lotStatuses = cacheFunctions.getLotStatuses(); diff --git a/handlers/maps-get/edit.js b/handlers/maps-get/edit.js index 9dedc347..d7a04bbf 100644 --- a/handlers/maps-get/edit.js +++ b/handlers/maps-get/edit.js @@ -1,18 +1,27 @@ import * as configFunctions from "../../helpers/functions.config.js"; import { getMap } from "../../helpers/lotOccupancyDB/getMap.js"; import { getMapSVGs } from "../../helpers/functions.map.js"; +import { getLotTypeSummary } from "../../helpers/lotOccupancyDB/getLotTypeSummary.js"; +import { getLotStatusSummary } from "../../helpers/lotOccupancyDB/getLotStatusSummary.js"; export const handler = async (request, response) => { const map = getMap(request.params.mapId); if (!map) { - return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + - "/maps/?error=mapIdNotFound"); + return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/maps/?error=mapIdNotFound"); } const mapSVGs = await getMapSVGs(); + const lotTypeSummary = getLotTypeSummary({ + mapId: map.mapId + }); + const lotStatusSummary = getLotStatusSummary({ + mapId: map.mapId + }); response.render("map-edit", { headTitle: map.mapName, isCreate: false, map, - mapSVGs + mapSVGs, + lotTypeSummary, + lotStatusSummary }); }; export default handler; diff --git a/handlers/maps-get/edit.ts b/handlers/maps-get/edit.ts index f991016e..ba43b4fc 100644 --- a/handlers/maps-get/edit.ts +++ b/handlers/maps-get/edit.ts @@ -5,24 +5,35 @@ import * as configFunctions from "../../helpers/functions.config.js"; import { getMap } from "../../helpers/lotOccupancyDB/getMap.js"; import { getMapSVGs } from "../../helpers/functions.map.js"; +import { getLotTypeSummary } from "../../helpers/lotOccupancyDB/getLotTypeSummary.js"; +import { getLotStatusSummary } from "../../helpers/lotOccupancyDB/getLotStatusSummary.js"; export const handler: RequestHandler = async (request, response) => { const map = getMap(request.params.mapId); if (!map) { return response.redirect( - configFunctions.getProperty("reverseProxy.urlPrefix") + - "/maps/?error=mapIdNotFound" + configFunctions.getProperty("reverseProxy.urlPrefix") + "/maps/?error=mapIdNotFound" ); } const mapSVGs = await getMapSVGs(); + const lotTypeSummary = getLotTypeSummary({ + mapId: map.mapId + }); + + const lotStatusSummary = getLotStatusSummary({ + mapId: map.mapId + }); + response.render("map-edit", { headTitle: map.mapName, isCreate: false, map, - mapSVGs + mapSVGs, + lotTypeSummary, + lotStatusSummary }); }; diff --git a/handlers/workOrders-post/doSearchWorkOrders.js b/handlers/workOrders-post/doSearchWorkOrders.js index c47d7657..710c3963 100644 --- a/handlers/workOrders-post/doSearchWorkOrders.js +++ b/handlers/workOrders-post/doSearchWorkOrders.js @@ -2,7 +2,8 @@ import { getWorkOrders } from "../../helpers/lotOccupancyDB/getWorkOrders.js"; export const handler = async (request, response) => { const result = getWorkOrders(request.body, { limit: request.body.limit, - offset: request.body.offset + offset: request.body.offset, + includeLotsAndLotOccupancies: true }); response.json({ count: result.count, diff --git a/handlers/workOrders-post/doSearchWorkOrders.ts b/handlers/workOrders-post/doSearchWorkOrders.ts index 3cc96cc0..721b13b2 100644 --- a/handlers/workOrders-post/doSearchWorkOrders.ts +++ b/handlers/workOrders-post/doSearchWorkOrders.ts @@ -5,7 +5,8 @@ import { getWorkOrders } from "../../helpers/lotOccupancyDB/getWorkOrders.js"; export const handler: RequestHandler = async (request, response) => { const result = getWorkOrders(request.body, { limit: request.body.limit, - offset: request.body.offset + offset: request.body.offset, + includeLotsAndLotOccupancies: true }); response.json({ diff --git a/helpers/lotOccupancyDB/getFeeCategories.js b/helpers/lotOccupancyDB/getFeeCategories.js index 20baa740..e9e989d2 100644 --- a/helpers/lotOccupancyDB/getFeeCategories.js +++ b/helpers/lotOccupancyDB/getFeeCategories.js @@ -32,7 +32,7 @@ export const getFeeCategories = (filters, options) => { let expectedFeeCategoryOrderNumber = -1; for (const feeCategory of feeCategories) { expectedFeeCategoryOrderNumber += 1; - if (feeCategory.orderNumber !== expectedFeeCategoryOrderNumber) { + if (updateOrderNumbers && feeCategory.orderNumber !== expectedFeeCategoryOrderNumber) { database .prepare("update FeeCategories" + " set orderNumber = ?" + diff --git a/helpers/lotOccupancyDB/getFeeCategories.ts b/helpers/lotOccupancyDB/getFeeCategories.ts index e9c834d2..74c39792 100644 --- a/helpers/lotOccupancyDB/getFeeCategories.ts +++ b/helpers/lotOccupancyDB/getFeeCategories.ts @@ -61,7 +61,7 @@ export const getFeeCategories = ( for (const feeCategory of feeCategories) { expectedFeeCategoryOrderNumber += 1; - if (feeCategory.orderNumber !== expectedFeeCategoryOrderNumber) { + if (updateOrderNumbers && feeCategory.orderNumber !== expectedFeeCategoryOrderNumber) { database .prepare( "update FeeCategories" + diff --git a/helpers/lotOccupancyDB/getWorkOrders.d.ts b/helpers/lotOccupancyDB/getWorkOrders.d.ts index e8cdf47f..ba07a10b 100644 --- a/helpers/lotOccupancyDB/getWorkOrders.d.ts +++ b/helpers/lotOccupancyDB/getWorkOrders.d.ts @@ -3,10 +3,15 @@ interface GetWorkOrdersFilters { workOrderTypeId?: number | string; workOrderOpenStatus?: "" | "open" | "closed"; workOrderOpenDateString?: string; + occupantName?: string; + lotName?: string; } interface GetWorkOrdersOptions { limit: number; offset: number; + includeLotsAndLotOccupancies?: boolean; + includeComments?: boolean; + includeMilestones?: boolean; } export declare const getWorkOrders: (filters?: GetWorkOrdersFilters, options?: GetWorkOrdersOptions) => { count: number; diff --git a/helpers/lotOccupancyDB/getWorkOrders.js b/helpers/lotOccupancyDB/getWorkOrders.js index e044edf2..d88253b6 100644 --- a/helpers/lotOccupancyDB/getWorkOrders.js +++ b/helpers/lotOccupancyDB/getWorkOrders.js @@ -1,6 +1,10 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { dateIntegerToString, dateStringToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import { getWorkOrderComments } from "./getWorkOrderComments.js"; +import { getLots } from "./getLots.js"; +import { getLotOccupancies } from "./getLotOccupancies.js"; +import { getWorkOrderMilestones } from "./getWorkOrderMilestones.js"; export const getWorkOrders = (filters, options) => { const database = sqlite(databasePath, { readonly: true @@ -24,10 +28,26 @@ export const getWorkOrders = (filters, options) => { sqlWhereClause += " and w.workOrderOpenDate = ?"; sqlParameters.push(dateStringToInteger(filters.workOrderOpenDateString)); } + if (filters.occupantName) { + const occupantNamePieces = filters.occupantName.toLowerCase().split(" "); + for (const occupantNamePiece of occupantNamePieces) { + sqlWhereClause += + " and w.workOrderId in (" + + "select workOrderId from WorkOrderLotOccupancies where recordDelete_timeMillis is null and lotOccupancyId in (select lotOccupancyId from LotOccupancyOccupants where recordDelete_timeMillis is null and instr(lower(occupantName), ?)))"; + sqlParameters.push(occupantNamePiece); + } + } + if (filters.lotName) { + const lotNamePieces = filters.lotName.toLowerCase().split(" "); + for (const lotNamePiece of lotNamePieces) { + sqlWhereClause += + " and w.workOrderId in (" + + "select workOrderId from WorkOrderLots where recordDelete_timeMillis is null and lotId in (select lotId from Lots oo where recordDelete_timeMillis is null and instr(lower(lotName), ?)))"; + sqlParameters.push(lotNamePiece); + } + } const count = database - .prepare("select count(*) as recordCount" + - " from WorkOrders w" + - sqlWhereClause) + .prepare("select count(*) as recordCount" + " from WorkOrders w" + sqlWhereClause) .get(sqlParameters).recordCount; let workOrders = []; if (count > 0) { @@ -49,14 +69,40 @@ export const getWorkOrders = (filters, options) => { " group by workOrderId) m on w.workOrderId = m.workOrderId") + sqlWhereClause + " order by w.workOrderOpenDate desc, w.workOrderNumber" + - (options - ? " limit " + - options.limit + - " offset " + - options.offset - : "")) + (options ? " limit " + options.limit + " offset " + options.offset : "")) .all(sqlParameters); } + if (options.includeComments || + options.includeLotsAndLotOccupancies || + options.includeMilestones) { + for (const workOrder of workOrders) { + if (options.includeComments) { + workOrder.workOrderComments = getWorkOrderComments(workOrder.workOrderId, database); + } + if (options.includeLotsAndLotOccupancies) { + workOrder.workOrderLots = getLots({ + workOrderId: workOrder.workOrderId + }, { + limit: -1, + offset: 0 + }, database).lots; + workOrder.workOrderLotOccupancies = getLotOccupancies({ + workOrderId: workOrder.workOrderId + }, { + limit: -1, + offset: 0, + includeOccupants: true + }, database).lotOccupancies; + } + if (options.includeMilestones) { + workOrder.workOrderMilestones = getWorkOrderMilestones({ + workOrderId: workOrder.workOrderId + }, { + orderBy: "date" + }, database); + } + } + } database.close(); return { count, diff --git a/helpers/lotOccupancyDB/getWorkOrders.ts b/helpers/lotOccupancyDB/getWorkOrders.ts index 25e59a51..82cc6a4b 100644 --- a/helpers/lotOccupancyDB/getWorkOrders.ts +++ b/helpers/lotOccupancyDB/getWorkOrders.ts @@ -2,7 +2,15 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; -import { dateIntegerToString, dateStringToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import { + dateIntegerToString, + dateStringToInteger +} from "@cityssm/expressjs-server-js/dateTimeFns.js"; + +import { getWorkOrderComments } from "./getWorkOrderComments.js"; +import { getLots } from "./getLots.js"; +import { getLotOccupancies } from "./getLotOccupancies.js"; +import { getWorkOrderMilestones } from "./getWorkOrderMilestones.js"; import type * as recordTypes from "../../types/recordTypes"; @@ -10,11 +18,16 @@ interface GetWorkOrdersFilters { workOrderTypeId?: number | string; workOrderOpenStatus?: "" | "open" | "closed"; workOrderOpenDateString?: string; + occupantName?: string; + lotName?: string; } interface GetWorkOrdersOptions { limit: number; offset: number; + includeLotsAndLotOccupancies?: boolean; + includeComments?: boolean; + includeMilestones?: boolean; } export const getWorkOrders = ( @@ -51,12 +64,28 @@ export const getWorkOrders = ( sqlParameters.push(dateStringToInteger(filters.workOrderOpenDateString)); } + if (filters.occupantName) { + const occupantNamePieces = filters.occupantName.toLowerCase().split(" "); + for (const occupantNamePiece of occupantNamePieces) { + sqlWhereClause += + " and w.workOrderId in (" + + "select workOrderId from WorkOrderLotOccupancies where recordDelete_timeMillis is null and lotOccupancyId in (select lotOccupancyId from LotOccupancyOccupants where recordDelete_timeMillis is null and instr(lower(occupantName), ?)))"; + sqlParameters.push(occupantNamePiece); + } + } + + if (filters.lotName) { + const lotNamePieces = filters.lotName.toLowerCase().split(" "); + for (const lotNamePiece of lotNamePieces) { + sqlWhereClause += + " and w.workOrderId in (" + + "select workOrderId from WorkOrderLots where recordDelete_timeMillis is null and lotId in (select lotId from Lots oo where recordDelete_timeMillis is null and instr(lower(lotName), ?)))"; + sqlParameters.push(lotNamePiece); + } + } + const count: number = database - .prepare( - "select count(*) as recordCount" + - " from WorkOrders w" + - sqlWhereClause - ) + .prepare("select count(*) as recordCount" + " from WorkOrders w" + sqlWhereClause) .get(sqlParameters).recordCount; let workOrders: recordTypes.WorkOrder[] = []; @@ -81,16 +110,60 @@ export const getWorkOrders = ( " group by workOrderId) m on w.workOrderId = m.workOrderId") + sqlWhereClause + " order by w.workOrderOpenDate desc, w.workOrderNumber" + - (options - ? " limit " + - options.limit + - " offset " + - options.offset - : "") + (options ? " limit " + options.limit + " offset " + options.offset : "") ) .all(sqlParameters); } + if ( + options.includeComments || + options.includeLotsAndLotOccupancies || + options.includeMilestones + ) { + for (const workOrder of workOrders) { + if (options.includeComments) { + workOrder.workOrderComments = getWorkOrderComments(workOrder.workOrderId, database); + } + + if (options.includeLotsAndLotOccupancies) { + workOrder.workOrderLots = getLots( + { + workOrderId: workOrder.workOrderId + }, + { + limit: -1, + offset: 0 + }, + database + ).lots; + + workOrder.workOrderLotOccupancies = getLotOccupancies( + { + workOrderId: workOrder.workOrderId + }, + { + limit: -1, + offset: 0, + includeOccupants: true + }, + database + ).lotOccupancies; + } + + if (options.includeMilestones) { + workOrder.workOrderMilestones = getWorkOrderMilestones( + { + workOrderId: workOrder.workOrderId + }, + { + orderBy: "date" + }, + database + ); + } + } + } + database.close(); return { diff --git a/package-lock.json b/package-lock.json index cb4e8a50..fd2ac01d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@cityssm/bulma-a11y": "^0.4.0", "@cityssm/bulma-sticky-table": "^2.0.1", "@cityssm/bulma-webapp-css": "^0.12.0", + "@cityssm/fa-glow": "^0.1.0", "@cityssm/mssql-multi-pool": "^2.1.6", "@cityssm/simple-fa5-checkbox": "^0.2.1", "@types/activedirectory2": "^1.2.3", @@ -590,6 +591,12 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/@cityssm/fa-glow": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@cityssm/fa-glow/-/fa-glow-0.1.0.tgz", + "integrity": "sha512-lHddtgbHA26+xgKBBgVQl6japGah2M+L+5z+lRuEV/P76DBSI2fifuCukUhX6k7Rvbiw+qkDSPPxwKJGggr+BA==", + "dev": true + }, "node_modules/@cityssm/mssql-multi-pool": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@cityssm/mssql-multi-pool/-/mssql-multi-pool-2.1.6.tgz", @@ -12609,6 +12616,12 @@ "libphonenumber-js": "^1.10.13" } }, + "@cityssm/fa-glow": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@cityssm/fa-glow/-/fa-glow-0.1.0.tgz", + "integrity": "sha512-lHddtgbHA26+xgKBBgVQl6japGah2M+L+5z+lRuEV/P76DBSI2fifuCukUhX6k7Rvbiw+qkDSPPxwKJGggr+BA==", + "dev": true + }, "@cityssm/mssql-multi-pool": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@cityssm/mssql-multi-pool/-/mssql-multi-pool-2.1.6.tgz", diff --git a/package.json b/package.json index 43136019..f06f8224 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@cityssm/bulma-a11y": "^0.4.0", "@cityssm/bulma-sticky-table": "^2.0.1", "@cityssm/bulma-webapp-css": "^0.12.0", + "@cityssm/fa-glow": "^0.1.0", "@cityssm/mssql-multi-pool": "^2.1.6", "@cityssm/simple-fa5-checkbox": "^0.2.1", "@types/activedirectory2": "^1.2.3", diff --git a/public-scss/style.scss b/public-scss/style.scss index 4b289526..8609403f 100644 --- a/public-scss/style.scss +++ b/public-scss/style.scss @@ -1,6 +1,7 @@ @import "@cityssm/bulma-webapp-css/cityssm"; @import "bulma/sass/utilities/derived-variables"; @import "bulma-calendar/src/sass/index"; +@import "@cityssm/fa-glow/fa-glow"; $white: #fff; $black: #000; diff --git a/public-typescript/mapSearch.js b/public-typescript/mapSearch.js index b667dd1f..17af3f8b 100644 --- a/public-typescript/mapSearch.js +++ b/public-typescript/mapSearch.js @@ -9,16 +9,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); searchResultsContainerElement.innerHTML = '
| Work Order Number | " + - "Work Order Description | " + + "Description | " + + "Related | " + "Date | " + 'Progress | ' + (workOrderPrints.length > 0 ? '' : "") + diff --git a/public-typescript/workOrderSearch.ts b/public-typescript/workOrderSearch.ts index 33be9827..6455f2d4 100644 --- a/public-typescript/workOrderSearch.ts +++ b/public-typescript/workOrderSearch.ts @@ -55,6 +55,34 @@ declare const cityssm: cityssmGlobal; const resultsTbodyElement = document.createElement("tbody"); for (const workOrder of responseJSON.workOrders) { + let relatedHTML = ""; + + for (const lot of workOrder.workOrderLots) { + relatedHTML += + '' + + ' ' + + cityssm.escapeHTML(lot.lotName || "(No Lot Name)") + + " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ' + + relatedHTML + + " | ") + ('' +
- ('' +
+ ('' +
' ' +
workOrder.workOrderOpenDateString +
" ") + - ('' + + ('' + ' ' + (workOrder.workOrderCloseDate ? workOrder.workOrderCloseDateString @@ -116,7 +147,8 @@ declare const cityssm: cityssmGlobal; '
+
+
<%= configFunctions.getProperty("aliases.lot") %> Count
|
+ Percentage |
@@ -152,6 +153,9 @@
<%= lotType.lotCount %>
|
+
+ <%= ((lotType.lotCount / map.lotCount) * 100).toFixed(1) %>%
+ |
<% } %>
@@ -165,6 +169,7 @@
<%= configFunctions.getProperty("aliases.lot") %> Count
|
+ Percentage |
@@ -178,6 +183,9 @@
<%= lotStatus.lotCount %>
|
+
+ <%= ((lotStatus.lotCount / map.lotCount) * 100).toFixed(1) %>%
+ |
<% } %>
diff --git a/views/workOrder-edit.ejs b/views/workOrder-edit.ejs
index 7acc9cdf..8975855c 100644
--- a/views/workOrder-edit.ejs
+++ b/views/workOrder-edit.ejs
@@ -214,7 +214,7 @@
" id="relatedTab--lotOccupancies">
-
-
-
" id="relatedTab--lots">
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
|