diff --git a/data/config.cemetery.js b/data/config.cemetery.js index 20a7e56c..146abe0f 100644 --- a/data/config.cemetery.js +++ b/data/config.cemetery.js @@ -9,6 +9,11 @@ export const config = { lots: "Burial Sites", map: "Cemetery", maps: "Cemeteries" + }, + settings: { + lotOccupancy: { + occupancyEndDateIsRequired: false + } } }; export default config; diff --git a/data/config.cemetery.ts b/data/config.cemetery.ts index 88ccd242..d9bb5e99 100644 --- a/data/config.cemetery.ts +++ b/data/config.cemetery.ts @@ -11,6 +11,11 @@ export const config: Config = { lots: "Burial Sites", map: "Cemetery", maps: "Cemeteries" + }, + settings: { + lotOccupancy: { + occupancyEndDateIsRequired: false + } } }; diff --git a/handlers/lotOccupancies-get/edit.js b/handlers/lotOccupancies-get/edit.js index 5958f45e..a0eb1097 100644 --- a/handlers/lotOccupancies-get/edit.js +++ b/handlers/lotOccupancies-get/edit.js @@ -1,3 +1,4 @@ +import { getOccupancyTypes } from "../../helpers/functions.cache.js"; import * as configFunctions from "../../helpers/functions.config.js"; import { getLotOccupancy } from "../../helpers/lotOccupancyDB/getLotOccupancy.js"; export const handler = (request, response) => { @@ -5,9 +6,12 @@ export const handler = (request, response) => { if (!lotOccupancy) { return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/lotOccupancies/?error=lotOccupancyIdNotFound"); } + const occupancyTypes = getOccupancyTypes(); return response.render("lotOccupancy-edit", { - headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " View", - lotOccupancy + headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " Update", + lotOccupancy, + occupancyTypes, + isCreate: false }); }; export default handler; diff --git a/handlers/lotOccupancies-get/edit.ts b/handlers/lotOccupancies-get/edit.ts index a23dbd25..1fa03184 100644 --- a/handlers/lotOccupancies-get/edit.ts +++ b/handlers/lotOccupancies-get/edit.ts @@ -1,23 +1,36 @@ -import type { RequestHandler } from "express"; +import type { + RequestHandler +} from "express"; + +import { + getOccupancyTypes +} from "../../helpers/functions.cache.js"; import * as configFunctions from "../../helpers/functions.config.js"; -import { getLotOccupancy } from "../../helpers/lotOccupancyDB/getLotOccupancy.js"; +import { + getLotOccupancy +} from "../../helpers/lotOccupancyDB/getLotOccupancy.js"; export const handler: RequestHandler = (request, response) => { - const lotOccupancy = getLotOccupancy(request.params.lotOccupancyId); + const lotOccupancy = getLotOccupancy(request.params.lotOccupancyId); - if (!lotOccupancy) { - return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/lotOccupancies/?error=lotOccupancyIdNotFound"); - } + if (!lotOccupancy) { + return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/lotOccupancies/?error=lotOccupancyIdNotFound"); + } - return response.render("lotOccupancy-edit", { - headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " View", - lotOccupancy - }); + const occupancyTypes = getOccupancyTypes(); + + return response.render("lotOccupancy-edit", { + headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " Update", + lotOccupancy, + + occupancyTypes, + isCreate: false + }); }; -export default handler; +export default handler; \ No newline at end of file diff --git a/handlers/lotOccupancies-get/view.ts b/handlers/lotOccupancies-get/view.ts index 4e6caaea..cc241949 100644 --- a/handlers/lotOccupancies-get/view.ts +++ b/handlers/lotOccupancies-get/view.ts @@ -1,23 +1,27 @@ -import type { RequestHandler } from "express"; +import type { + RequestHandler +} from "express"; import * as configFunctions from "../../helpers/functions.config.js"; -import { getLotOccupancy } from "../../helpers/lotOccupancyDB/getLotOccupancy.js"; +import { + getLotOccupancy +} from "../../helpers/lotOccupancyDB/getLotOccupancy.js"; export const handler: RequestHandler = (request, response) => { - const lotOccupancy = getLotOccupancy(request.params.lotOccupancyId); + const lotOccupancy = getLotOccupancy(request.params.lotOccupancyId); - if (!lotOccupancy) { - return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/lotOccupancies/?error=lotOccupancyIdNotFound"); - } + if (!lotOccupancy) { + return response.redirect(configFunctions.getProperty("reverseProxy.urlPrefix") + "/lotOccupancies/?error=lotOccupancyIdNotFound"); + } - return response.render("lotOccupancy-view", { - headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " View", - lotOccupancy - }); + return response.render("lotOccupancy-view", { + headTitle: configFunctions.getProperty("aliases.lot") + " " + configFunctions.getProperty("aliases.occupancy") + " View", + lotOccupancy + }); }; -export default handler; +export default handler; \ No newline at end of file diff --git a/handlers/lotOccupancies-post/doUpdateLotOccupancy.d.ts b/handlers/lotOccupancies-post/doUpdateLotOccupancy.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/lotOccupancies-post/doUpdateLotOccupancy.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/lotOccupancies-post/doUpdateLotOccupancy.js b/handlers/lotOccupancies-post/doUpdateLotOccupancy.js new file mode 100644 index 00000000..a3fb0f10 --- /dev/null +++ b/handlers/lotOccupancies-post/doUpdateLotOccupancy.js @@ -0,0 +1,9 @@ +import { updateLotOccupancy } from "../../helpers/lotOccupancyDB/updateLotOccupancy.js"; +export const handler = async (request, response) => { + const success = updateLotOccupancy(request.body, request.session); + response.json({ + success, + lotOccupancyId: request.body.lotOccupancyId + }); +}; +export default handler; diff --git a/handlers/lotOccupancies-post/doUpdateLotOccupancy.ts b/handlers/lotOccupancies-post/doUpdateLotOccupancy.ts new file mode 100644 index 00000000..4070100b --- /dev/null +++ b/handlers/lotOccupancies-post/doUpdateLotOccupancy.ts @@ -0,0 +1,17 @@ +import type { RequestHandler } from "express"; + +import { updateLotOccupancy } from "../../helpers/lotOccupancyDB/updateLotOccupancy.js"; + + +export const handler: RequestHandler = async (request, response) => { + + const success = updateLotOccupancy(request.body, request.session); + + response.json({ + success, + lotOccupancyId: request.body.lotOccupancyId + }); +}; + + +export default handler; \ No newline at end of file diff --git a/helpers/functions.config.d.ts b/helpers/functions.config.d.ts index eeb5fb70..3889836a 100644 --- a/helpers/functions.config.d.ts +++ b/helpers/functions.config.d.ts @@ -23,4 +23,5 @@ export declare function getProperty(propertyName: "aliases.occupancy"): string; export declare function getProperty(propertyName: "aliases.occupancies"): string; export declare function getProperty(propertyName: "aliases.occupant"): string; export declare function getProperty(propertyName: "aliases.occupants"): string; +export declare function getProperty(propertyName: "settings.lotOccupancy.occupancyEndDateIsRequired"): boolean; export declare const keepAliveMillis: number; diff --git a/helpers/functions.config.js b/helpers/functions.config.js index 198f3022..77ea36fc 100644 --- a/helpers/functions.config.js +++ b/helpers/functions.config.js @@ -23,19 +23,19 @@ configFallbackValues.set("aliases.occupancy", "Occupancy"); configFallbackValues.set("aliases.occupancies", "Occupancies"); configFallbackValues.set("aliases.occupant", "Occupant"); configFallbackValues.set("aliases.occupants", "Occupants"); +configFallbackValues.set("settings.lotOccupancy.occupancyEndDateIsRequired", true); export function getProperty(propertyName) { const propertyNameSplit = propertyName.split("."); let currentObject = config; for (const propertyNamePiece of propertyNameSplit) { - if (currentObject[propertyNamePiece]) { + if (Object.prototype.hasOwnProperty.call(currentObject, propertyNamePiece)) { currentObject = currentObject[propertyNamePiece]; + continue; } - else { - return configFallbackValues.get(propertyName); - } + return configFallbackValues.get(propertyName); } return currentObject; } -export const keepAliveMillis = getProperty("session.doKeepAlive") - ? Math.max(getProperty("session.maxAgeMillis") / 2, getProperty("session.maxAgeMillis") - (10 * 60 * 1000)) - : 0; +export const keepAliveMillis = getProperty("session.doKeepAlive") ? + Math.max(getProperty("session.maxAgeMillis") / 2, getProperty("session.maxAgeMillis") - (10 * 60 * 1000)) : + 0; diff --git a/helpers/functions.config.ts b/helpers/functions.config.ts index 7792b30a..07ce9b90 100644 --- a/helpers/functions.config.ts +++ b/helpers/functions.config.ts @@ -1,5 +1,7 @@ // eslint-disable-next-line node/no-unpublished-import -import { config } from "../data/config.js"; +import { + config +} from "../data/config.js"; import type * as configTypes from "../types/configTypes"; @@ -8,7 +10,8 @@ import type * as configTypes from "../types/configTypes"; * SET UP FALLBACK VALUES */ -const configFallbackValues = new Map(); +const configFallbackValues = new Map < string, + unknown > (); configFallbackValues.set("application.applicationName", "Lot Occupancy System"); configFallbackValues.set("application.backgroundURL", "/images/cemetery-background.jpg"); @@ -38,6 +41,8 @@ configFallbackValues.set("aliases.occupancies", "Occupancies"); configFallbackValues.set("aliases.occupant", "Occupant"); configFallbackValues.set("aliases.occupants", "Occupants"); +configFallbackValues.set("settings.lotOccupancy.occupancyEndDateIsRequired", true); + /* * Set up function overloads @@ -73,29 +78,31 @@ export function getProperty(propertyName: "aliases.occupancies"): string; export function getProperty(propertyName: "aliases.occupant"): string; export function getProperty(propertyName: "aliases.occupants"): string; +export function getProperty(propertyName: "settings.lotOccupancy.occupancyEndDateIsRequired"): boolean; + export function getProperty(propertyName: string): unknown { - const propertyNameSplit = propertyName.split("."); + const propertyNameSplit = propertyName.split("."); - let currentObject = config; + let currentObject = config; - for (const propertyNamePiece of propertyNameSplit) { + for (const propertyNamePiece of propertyNameSplit) { - if (currentObject[propertyNamePiece]) { - currentObject = currentObject[propertyNamePiece]; - } else { - return configFallbackValues.get(propertyName); + if (Object.prototype.hasOwnProperty.call(currentObject, propertyNamePiece)) { + currentObject = currentObject[propertyNamePiece]; + continue; + } + + return configFallbackValues.get(propertyName); } - } - - return currentObject; + return currentObject; } export const keepAliveMillis = - getProperty("session.doKeepAlive") - ? Math.max( - getProperty("session.maxAgeMillis") / 2, - getProperty("session.maxAgeMillis") - (10 * 60 * 1000) - ) - : 0; + getProperty("session.doKeepAlive") ? + Math.max( + getProperty("session.maxAgeMillis") / 2, + getProperty("session.maxAgeMillis") - (10 * 60 * 1000) + ) : + 0; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/getLotOccupancy.js b/helpers/lotOccupancyDB/getLotOccupancy.js index 76b64a5c..7437aec3 100644 --- a/helpers/lotOccupancyDB/getLotOccupancy.js +++ b/helpers/lotOccupancyDB/getLotOccupancy.js @@ -2,6 +2,10 @@ import { dateIntegerToString } from "@cityssm/expressjs-server-js/dateTimeFns.js import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { getLotOccupancyOccupants } from "./getLotOccupancyOccupants.js"; +import { getLotOccupancyComments } from "./getLotOccupancyComments.js"; +import { getLotOccupancyFields } from "./getLotOccupancyFields.js"; +import { getLotOccupancyFees } from "./getLotOccupancyFees.js"; +import { getLotOccupancyTransactions } from "./getLotOccupancyTransactions.js"; export const getLotOccupancy = (lotOccupancyId) => { const database = sqlite(databasePath, { readonly: true @@ -22,7 +26,11 @@ export const getLotOccupancy = (lotOccupancyId) => { " and o.lotOccupancyId = ?") .get(lotOccupancyId); if (lotOccupancy) { + lotOccupancy.lotOccupancyFields = getLotOccupancyFields(lotOccupancyId, database); lotOccupancy.lotOccupancyOccupants = getLotOccupancyOccupants(lotOccupancyId, database); + lotOccupancy.lotOccupancyComments = getLotOccupancyComments(lotOccupancyId, database); + lotOccupancy.lotOccupancyFees = getLotOccupancyFees(lotOccupancyId, database); + lotOccupancy.lotOccupancyTransactions = getLotOccupancyTransactions(lotOccupancyId, database); } database.close(); return lotOccupancy; diff --git a/helpers/lotOccupancyDB/getLotOccupancy.ts b/helpers/lotOccupancyDB/getLotOccupancy.ts index 22af3a79..b7a116e6 100644 --- a/helpers/lotOccupancyDB/getLotOccupancy.ts +++ b/helpers/lotOccupancyDB/getLotOccupancy.ts @@ -12,6 +12,22 @@ import { getLotOccupancyOccupants } from "./getLotOccupancyOccupants.js"; +import { + getLotOccupancyComments +} from "./getLotOccupancyComments.js"; + +import { + getLotOccupancyFields +} from "./getLotOccupancyFields.js"; + +import { + getLotOccupancyFees +} from "./getLotOccupancyFees.js"; + +import { + getLotOccupancyTransactions +} from "./getLotOccupancyTransactions.js"; + import type * as recordTypes from "../../types/recordTypes"; @@ -40,7 +56,11 @@ export const getLotOccupancy = (lotOccupancyId: number | string): recordTypes.Lo .get(lotOccupancyId); if (lotOccupancy) { + lotOccupancy.lotOccupancyFields = getLotOccupancyFields(lotOccupancyId, database); lotOccupancy.lotOccupancyOccupants = getLotOccupancyOccupants(lotOccupancyId, database); + lotOccupancy.lotOccupancyComments = getLotOccupancyComments(lotOccupancyId, database); + lotOccupancy.lotOccupancyFees = getLotOccupancyFees(lotOccupancyId, database); + lotOccupancy.lotOccupancyTransactions = getLotOccupancyTransactions(lotOccupancyId, database); } database.close(); diff --git a/helpers/lotOccupancyDB/getLotOccupancyFees.d.ts b/helpers/lotOccupancyDB/getLotOccupancyFees.d.ts new file mode 100644 index 00000000..045c70cd --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFees.d.ts @@ -0,0 +1,4 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +export declare const getLotOccupancyFees: (lotOccupancyId: number | string, connectedDatabase?: sqlite.Database) => recordTypes.LotOccupancyFee[]; +export default getLotOccupancyFees; diff --git a/helpers/lotOccupancyDB/getLotOccupancyFees.js b/helpers/lotOccupancyDB/getLotOccupancyFees.js new file mode 100644 index 00000000..be0b7d1f --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFees.js @@ -0,0 +1,21 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const getLotOccupancyFees = (lotOccupancyId, connectedDatabase) => { + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + const lotOccupancyFees = database + .prepare("select o.lotOccupancyId, o.feeId, o.feeAmount," + + " f.feeName" + + " from LotOccupancyFees o" + + " left join Fees f on o.feeId = f.feeId" + + " where o.recordDelete_timeMillis is null" + + " and o.lotOccupancyId = ?" + + " order by o.recordCreate_timeMillis") + .all(lotOccupancyId); + if (!connectedDatabase) { + database.close(); + } + return lotOccupancyFees; +}; +export default getLotOccupancyFees; diff --git a/helpers/lotOccupancyDB/getLotOccupancyFees.ts b/helpers/lotOccupancyDB/getLotOccupancyFees.ts new file mode 100644 index 00000000..8f707790 --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFees.ts @@ -0,0 +1,35 @@ +import sqlite from "better-sqlite3"; + +import { + lotOccupancyDB as databasePath +} from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +export const getLotOccupancyFees = (lotOccupancyId: number | string, + connectedDatabase ? : sqlite.Database): recordTypes.LotOccupancyFee[] => { + + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + + const lotOccupancyFees: recordTypes.LotOccupancyFee[] = database + .prepare("select o.lotOccupancyId, o.feeId, o.feeAmount," + + " f.feeName" + + " from LotOccupancyFees o" + + " left join Fees f on o.feeId = f.feeId" + + " where o.recordDelete_timeMillis is null" + + " and o.lotOccupancyId = ?" + + " order by o.recordCreate_timeMillis") + .all(lotOccupancyId); + + if (!connectedDatabase) { + database.close(); + } + + return lotOccupancyFees; +}; + + +export default getLotOccupancyFees; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/getLotOccupancyFields.d.ts b/helpers/lotOccupancyDB/getLotOccupancyFields.d.ts new file mode 100644 index 00000000..a3619a4d --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFields.d.ts @@ -0,0 +1,4 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +export declare const getLotOccupancyFields: (lotOccupancyId: number | string, connectedDatabase?: sqlite.Database) => recordTypes.LotOccupancyField[]; +export default getLotOccupancyFields; diff --git a/helpers/lotOccupancyDB/getLotOccupancyFields.js b/helpers/lotOccupancyDB/getLotOccupancyFields.js new file mode 100644 index 00000000..ebfd927c --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFields.js @@ -0,0 +1,30 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const getLotOccupancyFields = (lotOccupancyId, connectedDatabase) => { + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + const lotOccupancyFields = database + .prepare("select o.lotOccupancyId," + + " o.occupancyTypeFieldId, o.lotOccupancyFieldValue," + + " f.occupancyTypeField, f.orderNumber" + + " from LotOccupancyFields o" + + " left join OccupancyTypeFields f on o.occupancyTypeFieldId = f.occupancyTypeFieldId" + + " where o.recordDelete_timeMillis is null" + + " and o.lotOccupancyId = ?" + + " union" + + " select ? as lotOccupancyId," + + " f.occupancyTypeFieldId, '' as lotOccupancyFieldValue," + + " f.occupancyTypeField, f.orderNumber" + + " from OccupancyTypeFields f" + + " where f.recordDelete_timeMillis is null" + + " and f.occupancyTypeId in (select occupancyTypeId from LotOccupancies where lotOccupancyId = ?)" + + " and f.occupancyTypeFieldId not in (select occupancyTypeFieldId from LotOccupancyFields where lotOccupancyId = ?)" + + " order by orderNumber, occupancyTypeField") + .all(lotOccupancyId, lotOccupancyId, lotOccupancyId, lotOccupancyId); + if (!connectedDatabase) { + database.close(); + } + return lotOccupancyFields; +}; +export default getLotOccupancyFields; diff --git a/helpers/lotOccupancyDB/getLotOccupancyFields.ts b/helpers/lotOccupancyDB/getLotOccupancyFields.ts new file mode 100644 index 00000000..076c851a --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyFields.ts @@ -0,0 +1,44 @@ +import sqlite from "better-sqlite3"; + +import { + lotOccupancyDB as databasePath +} from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +export const getLotOccupancyFields = (lotOccupancyId: number | string, + connectedDatabase ? : sqlite.Database): recordTypes.LotOccupancyField[] => { + + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + + const lotOccupancyFields: recordTypes.LotOccupancyField[] = database + .prepare("select o.lotOccupancyId," + + " o.occupancyTypeFieldId, o.lotOccupancyFieldValue," + + " f.occupancyTypeField, f.orderNumber" + + " from LotOccupancyFields o" + + " left join OccupancyTypeFields f on o.occupancyTypeFieldId = f.occupancyTypeFieldId" + + " where o.recordDelete_timeMillis is null" + + " and o.lotOccupancyId = ?" + + " union" + + " select ? as lotOccupancyId," + + " f.occupancyTypeFieldId, '' as lotOccupancyFieldValue," + + " f.occupancyTypeField, f.orderNumber" + + " from OccupancyTypeFields f" + + " where f.recordDelete_timeMillis is null" + + " and f.occupancyTypeId in (select occupancyTypeId from LotOccupancies where lotOccupancyId = ?)" + + " and f.occupancyTypeFieldId not in (select occupancyTypeFieldId from LotOccupancyFields where lotOccupancyId = ?)" + + " order by orderNumber, occupancyTypeField") + .all(lotOccupancyId, lotOccupancyId, lotOccupancyId, lotOccupancyId); + + if (!connectedDatabase) { + database.close(); + } + + return lotOccupancyFields; +}; + + +export default getLotOccupancyFields; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/getLotOccupancyOccupants.ts b/helpers/lotOccupancyDB/getLotOccupancyOccupants.ts index 87518d6c..df893786 100644 --- a/helpers/lotOccupancyDB/getLotOccupancyOccupants.ts +++ b/helpers/lotOccupancyDB/getLotOccupancyOccupants.ts @@ -1,4 +1,5 @@ import sqlite from "better-sqlite3"; + import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; diff --git a/helpers/lotOccupancyDB/getLotOccupancyTransactions.d.ts b/helpers/lotOccupancyDB/getLotOccupancyTransactions.d.ts new file mode 100644 index 00000000..9f64f68a --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyTransactions.d.ts @@ -0,0 +1,4 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +export declare const getLotOccupancyTransactions: (lotOccupancyId: number | string, connectedDatabase?: sqlite.Database) => recordTypes.LotOccupancyTransaction[]; +export default getLotOccupancyTransactions; diff --git a/helpers/lotOccupancyDB/getLotOccupancyTransactions.js b/helpers/lotOccupancyDB/getLotOccupancyTransactions.js new file mode 100644 index 00000000..6911d691 --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyTransactions.js @@ -0,0 +1,25 @@ +import { dateIntegerToString, timeIntegerToString } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const getLotOccupancyTransactions = (lotOccupancyId, connectedDatabase) => { + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + database.function("userFn_dateIntegerToString", dateIntegerToString); + database.function("userFn_timeIntegerToString", timeIntegerToString); + const lotOccupancyTransactions = database + .prepare("select lotOccupancyId, transactionIndex," + + " transactionDate, userFn_dateIntegerToString(transactionDate) as transactionDateString," + + " transactionTime, userFn_timeIntegerToString(transactionTime) as transactionTimeString," + + " transactionAmount, externalReceiptNumber, transactionNote" + + " from LotOccupancyTransactions" + + " where recordDelete_timeMillis is null" + + " and lotOccupancyId = ?" + + " order by transactionDate, transactionTime, transactionIndex") + .all(lotOccupancyId); + if (!connectedDatabase) { + database.close(); + } + return lotOccupancyTransactions; +}; +export default getLotOccupancyTransactions; diff --git a/helpers/lotOccupancyDB/getLotOccupancyTransactions.ts b/helpers/lotOccupancyDB/getLotOccupancyTransactions.ts new file mode 100644 index 00000000..190ee436 --- /dev/null +++ b/helpers/lotOccupancyDB/getLotOccupancyTransactions.ts @@ -0,0 +1,40 @@ +import { dateIntegerToString, timeIntegerToString } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import sqlite from "better-sqlite3"; + +import { + lotOccupancyDB as databasePath +} from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +export const getLotOccupancyTransactions = (lotOccupancyId: number | string, + connectedDatabase ? : sqlite.Database): recordTypes.LotOccupancyTransaction[] => { + + const database = connectedDatabase || sqlite(databasePath, { + readonly: true + }); + + database.function("userFn_dateIntegerToString", dateIntegerToString); + database.function("userFn_timeIntegerToString", timeIntegerToString); + + const lotOccupancyTransactions: recordTypes.LotOccupancyTransaction[] = database + .prepare("select lotOccupancyId, transactionIndex," + + " transactionDate, userFn_dateIntegerToString(transactionDate) as transactionDateString," + + " transactionTime, userFn_timeIntegerToString(transactionTime) as transactionTimeString," + + " transactionAmount, externalReceiptNumber, transactionNote" + + " from LotOccupancyTransactions" + + " where recordDelete_timeMillis is null" + + " and lotOccupancyId = ?" + + " order by transactionDate, transactionTime, transactionIndex") + .all(lotOccupancyId); + + if (!connectedDatabase) { + database.close(); + } + + return lotOccupancyTransactions; +}; + + +export default getLotOccupancyTransactions; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/getLots.d.ts b/helpers/lotOccupancyDB/getLots.d.ts index 7476b727..ad97987b 100644 --- a/helpers/lotOccupancyDB/getLots.d.ts +++ b/helpers/lotOccupancyDB/getLots.d.ts @@ -4,6 +4,7 @@ interface GetLotsFilters { mapId?: number | string; lotTypeId?: number | string; lotStatusId?: number | string; + occupancyStatus?: "" | "occupied" | "unoccupied"; } interface GetLotsOptions { limit: number; diff --git a/helpers/lotOccupancyDB/getLots.js b/helpers/lotOccupancyDB/getLots.js index e29b56c6..ebc62138 100644 --- a/helpers/lotOccupancyDB/getLots.js +++ b/helpers/lotOccupancyDB/getLots.js @@ -26,14 +26,30 @@ export const getLots = (filters, options) => { sqlWhereClause += " and l.lotStatusId = ?"; sqlParameters.push(filters.lotStatusId); } + if (filters.occupancyStatus) { + if (filters.occupancyStatus === "occupied") { + sqlWhereClause += " and lotOccupancyCount > 0"; + } + else if (filters.occupancyStatus === "unoccupied") { + sqlWhereClause += " and (lotOccupancyCount is null or lotOccupancyCount = 0)"; + } + } + const currentDate = dateToInteger(new Date()); const count = database.prepare("select count(*) as recordCount" + " from Lots l" + + (" left join (" + + "select lotId, count(lotOccupancyId) as lotOccupancyCount" + + " from LotOccupancies" + + " where recordDelete_timeMillis is null" + + " and occupancyStartDate <= " + currentDate + + " and (occupancyEndDate is null or occupancyEndDate >= " + currentDate + ")" + + " group by lotId" + + ") o on l.lotId = o.lotId") + sqlWhereClause) .get(sqlParameters) .recordCount; let lots = []; if (count > 0) { - const currentDate = dateToInteger(new Date()); lots = database .prepare("select l.lotId, l.lotName," + " t.lotType," + diff --git a/helpers/lotOccupancyDB/getLots.ts b/helpers/lotOccupancyDB/getLots.ts index 43cea6dd..bcd92985 100644 --- a/helpers/lotOccupancyDB/getLots.ts +++ b/helpers/lotOccupancyDB/getLots.ts @@ -14,6 +14,7 @@ interface GetLotsFilters { mapId ? : number | string; lotTypeId ? : number | string; lotStatusId ? : number | string; + occupancyStatus ? : "" | "occupied" | "unoccupied"; } interface GetLotsOptions { @@ -57,8 +58,26 @@ export const getLots = (filters ? : GetLotsFilters, options ? : GetLotsOptions): sqlParameters.push(filters.lotStatusId); } + if (filters.occupancyStatus) { + if (filters.occupancyStatus === "occupied") { + sqlWhereClause += " and lotOccupancyCount > 0"; + } else if (filters.occupancyStatus === "unoccupied") { + sqlWhereClause += " and (lotOccupancyCount is null or lotOccupancyCount = 0)"; + } + } + + const currentDate = dateToInteger(new Date()); + const count: number = database.prepare("select count(*) as recordCount" + " from Lots l" + + (" left join (" + + "select lotId, count(lotOccupancyId) as lotOccupancyCount" + + " from LotOccupancies" + + " where recordDelete_timeMillis is null" + + " and occupancyStartDate <= " + currentDate + + " and (occupancyEndDate is null or occupancyEndDate >= " + currentDate + ")" + + " group by lotId" + + ") o on l.lotId = o.lotId") + sqlWhereClause) .get(sqlParameters) .recordCount; @@ -67,8 +86,6 @@ export const getLots = (filters ? : GetLotsFilters, options ? : GetLotsOptions): if (count > 0) { - const currentDate = dateToInteger(new Date()); - lots = database .prepare("select l.lotId, l.lotName," + " t.lotType," + diff --git a/helpers/lotOccupancyDB/updateLotOccupancy.d.ts b/helpers/lotOccupancyDB/updateLotOccupancy.d.ts new file mode 100644 index 00000000..529963b1 --- /dev/null +++ b/helpers/lotOccupancyDB/updateLotOccupancy.d.ts @@ -0,0 +1,10 @@ +import type * as recordTypes from "../../types/recordTypes"; +interface UpdateLotOccupancyForm { + lotOccupancyId: string | number; + occupancyTypeId: string | number; + lotId: string | number; + occupancyStartDateString: string; + occupancyEndDateString: string; +} +export declare function updateLotOccupancy(lotOccupancyForm: UpdateLotOccupancyForm, requestSession: recordTypes.PartialSession): boolean; +export default updateLotOccupancy; diff --git a/helpers/lotOccupancyDB/updateLotOccupancy.js b/helpers/lotOccupancyDB/updateLotOccupancy.js new file mode 100644 index 00000000..a30ee8e7 --- /dev/null +++ b/helpers/lotOccupancyDB/updateLotOccupancy.js @@ -0,0 +1,21 @@ +import { dateStringToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export function updateLotOccupancy(lotOccupancyForm, requestSession) { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update LotOccupancies" + + " set occupancyTypeId = ?," + + " lotId = ?," + + " occupancyStartDate = ?," + + " occupancyEndDate = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where lotOccupancyId = ?" + + " and recordDelete_timeMillis is null") + .run(lotOccupancyForm.occupancyTypeId, (lotOccupancyForm.lotId === "" ? undefined : lotOccupancyForm.lotId), dateStringToInteger(lotOccupancyForm.occupancyStartDateString), (lotOccupancyForm.occupancyEndDateString === "" ? undefined : dateStringToInteger(lotOccupancyForm.occupancyEndDateString)), requestSession.user.userName, rightNowMillis, lotOccupancyForm.lotOccupancyId); + database.close(); + return result.changes > 0; +} +export default updateLotOccupancy; diff --git a/helpers/lotOccupancyDB/updateLotOccupancy.ts b/helpers/lotOccupancyDB/updateLotOccupancy.ts new file mode 100644 index 00000000..1d34cd2b --- /dev/null +++ b/helpers/lotOccupancyDB/updateLotOccupancy.ts @@ -0,0 +1,48 @@ +import { dateStringToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js"; +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +interface UpdateLotOccupancyForm { + lotOccupancyId: string | number; + occupancyTypeId: string | number; + lotId: string | number; + + occupancyStartDateString: string; + occupancyEndDateString: string; +} + + +export function updateLotOccupancy(lotOccupancyForm: UpdateLotOccupancyForm, requestSession: recordTypes.PartialSession): boolean { + + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare("update LotOccupancies" + + " set occupancyTypeId = ?," + + " lotId = ?," + + " occupancyStartDate = ?," + + " occupancyEndDate = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where lotOccupancyId = ?" + + " and recordDelete_timeMillis is null") + .run(lotOccupancyForm.occupancyTypeId, + (lotOccupancyForm.lotId === "" ? undefined : lotOccupancyForm.lotId), + dateStringToInteger(lotOccupancyForm.occupancyStartDateString), + (lotOccupancyForm.occupancyEndDateString === "" ? undefined : dateStringToInteger(lotOccupancyForm.occupancyEndDateString)), + requestSession.user.userName, + rightNowMillis, + lotOccupancyForm.lotOccupancyId); + + database.close(); + + return result.changes > 0; +} + + +export default updateLotOccupancy; \ No newline at end of file diff --git a/public-typescript/lotEdit.ts b/public-typescript/lotEdit.ts index 55485357..898e9d7d 100644 --- a/public-typescript/lotEdit.ts +++ b/public-typescript/lotEdit.ts @@ -6,6 +6,7 @@ import type * as recordTypes from "../types/recordTypes"; import type { cityssmGlobal } from "@cityssm/bulma-webapp-js/src/types"; + import type { BulmaJS } from "@cityssm/bulma-js/types"; diff --git a/public-typescript/lotOccupancyEdit.d.ts b/public-typescript/lotOccupancyEdit.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public-typescript/lotOccupancyEdit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public-typescript/lotOccupancyEdit.js b/public-typescript/lotOccupancyEdit.js new file mode 100644 index 00000000..668ad2a8 --- /dev/null +++ b/public-typescript/lotOccupancyEdit.js @@ -0,0 +1,121 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const urlPrefix = document.querySelector("main").dataset.urlPrefix; + const lotOccupancyId = document.querySelector("#lotOccupancy--lotOccupancyId").value; + const isCreate = (lotOccupancyId === ""); + let hasUnsavedChanges = false; + const setUnsavedChanges = () => { + if (!hasUnsavedChanges) { + hasUnsavedChanges = true; + cityssm.enableNavBlocker(); + } + }; + const clearUnsavedChanges = () => { + hasUnsavedChanges = false; + cityssm.disableNavBlocker(); + }; + const formElement = document.querySelector("#form--lotOccupancy"); + formElement.addEventListener("submit", (formEvent) => { + formEvent.preventDefault(); + cityssm.postJSON(urlPrefix + "/lotOccupancies/" + (isCreate ? "doCreateLotOccupancy" : "doUpdateLotOccupancy"), formElement, (responseJSON) => { + if (responseJSON.success) { + clearUnsavedChanges(); + if (isCreate) { + window.location.href = urlPrefix + "/lotOccupancies/" + responseJSON.lotOccupancyId + "/edit"; + } + else { + bulmaJS.alert({ + message: exports.aliases.occupancy + " Updated Successfully", + contextualColorName: "success" + }); + } + } + else { + bulmaJS.alert({ + title: "Error Saving " + exports.aliases.occupancy, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }); + document.querySelector("#lotOccupancy--lotName").addEventListener("click", () => { + let lotSelectCloseModalFunction; + let lotSelectFormElement; + let lotSelectResultsElement; + const selectLot = (clickEvent) => { + clickEvent.preventDefault(); + const selectedLotElement = clickEvent.currentTarget; + document.querySelector("#lotOccupancy--lotId").value = selectedLotElement.dataset.lotId; + document.querySelector("#lotOccupancy--lotName").value = selectedLotElement.dataset.lotName; + setUnsavedChanges(); + lotSelectCloseModalFunction(); + }; + const searchLots = () => { + lotSelectResultsElement.innerHTML = "

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

"; + cityssm.postJSON(urlPrefix + "/lots/doSearchLots", lotSelectFormElement, (responseJSON) => { + if (responseJSON.count === 0) { + lotSelectResultsElement.innerHTML = "
" + + "

" + + "No results." + + "

" + + "
"; + return; + } + const panelElement = document.createElement("div"); + panelElement.className = "panel"; + for (const lot of responseJSON.lots) { + const panelBlockElement = document.createElement("a"); + panelBlockElement.className = "panel-block is-block"; + panelBlockElement.href = "#"; + panelBlockElement.dataset.lotId = lot.lotId.toString(); + panelBlockElement.dataset.lotName = lot.lotName; + panelBlockElement.innerHTML = "
" + + ("
" + + cityssm.escapeHTML(lot.lotName) + "
" + + "" + cityssm.escapeHTML(lot.mapName) + "" + + "
") + + ("
" + + cityssm.escapeHTML(lot.lotStatus) + "
" + + "" + + (lot.lotOccupancyCount > 0 ? + "Currently Occupied" : "") + + "" + + "
") + + "
"; + panelBlockElement.addEventListener("click", selectLot); + panelElement.append(panelBlockElement); + } + lotSelectResultsElement.innerHTML = ""; + lotSelectResultsElement.append(panelElement); + }); + }; + cityssm.openHtmlModal("lotOccupancy-selectLot", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + }, + onshown: (modalElement, closeModalFunction) => { + bulmaJS.toggleHtmlClipped(); + lotSelectCloseModalFunction = closeModalFunction; + const lotNameFilterElement = modalElement.querySelector("#lotSelect--lotName"); + lotNameFilterElement.focus(); + lotNameFilterElement.addEventListener("change", searchLots); + modalElement.querySelector("#lotSelect--occupancyStatus").addEventListener("change", searchLots); + lotSelectFormElement = modalElement.querySelector("#form--lotSelect"); + lotSelectResultsElement = modalElement.querySelector("#resultsContainer--lotSelect"); + lotSelectFormElement.addEventListener("submit", (submitEvent) => { + submitEvent.preventDefault(); + }); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + los.initializeUnlockFieldButtons(formElement); +})(); diff --git a/public-typescript/lotOccupancyEdit.ts b/public-typescript/lotOccupancyEdit.ts new file mode 100644 index 00000000..81bffff3 --- /dev/null +++ b/public-typescript/lotOccupancyEdit.ts @@ -0,0 +1,188 @@ +/* eslint-disable unicorn/prefer-module */ + +import type * as globalTypes from "../types/globalTypes"; +import type * as recordTypes from "../types/recordTypes"; + +import type { + cityssmGlobal +} from "@cityssm/bulma-webapp-js/src/types"; + +import type { + BulmaJS +} from "@cityssm/bulma-js/types"; + +declare const cityssm: cityssmGlobal; +declare const bulmaJS: BulmaJS; + + +(() => { + + const los = (exports.los as globalTypes.LOS); + + const urlPrefix = document.querySelector("main").dataset.urlPrefix; + + const lotOccupancyId = (document.querySelector("#lotOccupancy--lotOccupancyId") as HTMLInputElement).value; + const isCreate = (lotOccupancyId === ""); + + // Main form + + let hasUnsavedChanges = false; + + const setUnsavedChanges = () => { + if (!hasUnsavedChanges) { + hasUnsavedChanges = true; + cityssm.enableNavBlocker(); + } + }; + + const clearUnsavedChanges = () => { + hasUnsavedChanges = false; + cityssm.disableNavBlocker(); + }; + + const formElement = document.querySelector("#form--lotOccupancy") as HTMLFormElement; + + formElement.addEventListener("submit", (formEvent) => { + formEvent.preventDefault(); + + cityssm.postJSON(urlPrefix + "/lotOccupancies/" + (isCreate ? "doCreateLotOccupancy" : "doUpdateLotOccupancy"), + formElement, + (responseJSON: { + success: boolean; + lotOccupancyId ? : number; + errorMessage ? : string; + }) => { + + if (responseJSON.success) { + + clearUnsavedChanges(); + + if (isCreate) { + window.location.href = urlPrefix + "/lotOccupancies/" + responseJSON.lotOccupancyId + "/edit"; + } else { + bulmaJS.alert({ + message: exports.aliases.occupancy + " Updated Successfully", + contextualColorName: "success" + }); + } + } else { + bulmaJS.alert({ + title: "Error Saving " + exports.aliases.occupancy, + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }); + + // Lot Selector + + document.querySelector("#lotOccupancy--lotName").addEventListener("click", () => { + + let lotSelectCloseModalFunction: () => void; + + let lotSelectFormElement: HTMLFormElement; + let lotSelectResultsElement: HTMLElement; + + const selectLot = (clickEvent: Event) => { + clickEvent.preventDefault(); + + const selectedLotElement = clickEvent.currentTarget as HTMLElement; + + (document.querySelector("#lotOccupancy--lotId") as HTMLInputElement).value = selectedLotElement.dataset.lotId; + (document.querySelector("#lotOccupancy--lotName") as HTMLInputElement).value = selectedLotElement.dataset.lotName; + + setUnsavedChanges(); + + lotSelectCloseModalFunction(); + }; + + const searchLots = () => { + + lotSelectResultsElement.innerHTML = "

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

"; + + cityssm.postJSON(urlPrefix + "/lots/doSearchLots", lotSelectFormElement, (responseJSON: { + count: number; + lots: recordTypes.Lot[]; + }) => { + + if (responseJSON.count === 0) { + lotSelectResultsElement.innerHTML = "
" + + "

" + + "No results." + + "

" + + "
"; + + return; + } + + const panelElement = document.createElement("div"); + panelElement.className = "panel"; + + for (const lot of responseJSON.lots) { + + const panelBlockElement = document.createElement("a"); + panelBlockElement.className = "panel-block is-block"; + panelBlockElement.href = "#"; + + panelBlockElement.dataset.lotId = lot.lotId.toString(); + panelBlockElement.dataset.lotName = lot.lotName; + + panelBlockElement.innerHTML = "
" + + ("
" + + cityssm.escapeHTML(lot.lotName) + "
" + + "" + cityssm.escapeHTML(lot.mapName) + "" + + "
") + + ("
" + + cityssm.escapeHTML(lot.lotStatus as string) + "
" + + "" + + (lot.lotOccupancyCount > 0 ? + "Currently Occupied" : "") + + "" + + "
") + + "
"; + + panelBlockElement.addEventListener("click", selectLot); + + panelElement.append(panelBlockElement); + } + + lotSelectResultsElement.innerHTML = ""; + lotSelectResultsElement.append(panelElement); + }); + } + + cityssm.openHtmlModal("lotOccupancy-selectLot", { + onshow: (modalElement) => { + los.populateAliases(modalElement); + }, + onshown: (modalElement, closeModalFunction) => { + + bulmaJS.toggleHtmlClipped(); + + lotSelectCloseModalFunction = closeModalFunction; + + const lotNameFilterElement = modalElement.querySelector("#lotSelect--lotName") as HTMLInputElement; + lotNameFilterElement.focus(); + lotNameFilterElement.addEventListener("change", searchLots); + + modalElement.querySelector("#lotSelect--occupancyStatus").addEventListener("change", searchLots); + + lotSelectFormElement = modalElement.querySelector("#form--lotSelect"); + lotSelectResultsElement = modalElement.querySelector("#resultsContainer--lotSelect"); + + lotSelectFormElement.addEventListener("submit", (submitEvent) => { + submitEvent.preventDefault(); + }); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + + los.initializeUnlockFieldButtons(formElement); +})(); \ No newline at end of file diff --git a/public/html/lotOccupancy-selectLot.html b/public/html/lotOccupancy-selectLot.html new file mode 100644 index 00000000..be4db062 --- /dev/null +++ b/public/html/lotOccupancy-selectLot.html @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/public/javascripts/lotOccupancyEdit.min.js b/public/javascripts/lotOccupancyEdit.min.js new file mode 100644 index 00000000..414e7917 --- /dev/null +++ b/public/javascripts/lotOccupancyEdit.min.js @@ -0,0 +1 @@ +"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=exports.los,t=document.querySelector("main").dataset.urlPrefix,c=""===document.querySelector("#lotOccupancy--lotOccupancyId").value;let s=!1;const a=document.querySelector("#form--lotOccupancy");a.addEventListener("submit",e=>{e.preventDefault(),cityssm.postJSON(t+"/lotOccupancies/"+(c?"doCreateLotOccupancy":"doUpdateLotOccupancy"),a,e=>{e.success?(s=!1,cityssm.disableNavBlocker(),c?window.location.href=t+"/lotOccupancies/"+e.lotOccupancyId+"/edit":bulmaJS.alert({message:exports.aliases.occupancy+" Updated Successfully",contextualColorName:"success"})):bulmaJS.alert({title:"Error Saving "+exports.aliases.occupancy,message:e.errorMessage,contextualColorName:"danger"})})}),document.querySelector("#lotOccupancy--lotName").addEventListener("click",()=>{let c,a,o;const l=e=>{e.preventDefault();const t=e.currentTarget;document.querySelector("#lotOccupancy--lotId").value=t.dataset.lotId,document.querySelector("#lotOccupancy--lotName").value=t.dataset.lotName,s||(s=!0,cityssm.enableNavBlocker()),c()},n=()=>{o.innerHTML='


Searching...

',cityssm.postJSON(t+"/lots/doSearchLots",a,e=>{if(0===e.count)return void(o.innerHTML='

No results.

');const t=document.createElement("div");t.className="panel";for(const c of e.lots){const e=document.createElement("a");e.className="panel-block is-block",e.href="#",e.dataset.lotId=c.lotId.toString(),e.dataset.lotName=c.lotName,e.innerHTML='
'+cityssm.escapeHTML(c.lotName)+'
'+cityssm.escapeHTML(c.mapName)+'
'+cityssm.escapeHTML(c.lotStatus)+'
'+(c.lotOccupancyCount>0?"Currently Occupied":"")+"
",e.addEventListener("click",l),t.append(e)}o.innerHTML="",o.append(t)})};cityssm.openHtmlModal("lotOccupancy-selectLot",{onshow:t=>{e.populateAliases(t)},onshown:(e,t)=>{bulmaJS.toggleHtmlClipped(),c=t;const s=e.querySelector("#lotSelect--lotName");s.focus(),s.addEventListener("change",n),e.querySelector("#lotSelect--occupancyStatus").addEventListener("change",n),a=e.querySelector("#form--lotSelect"),o=e.querySelector("#resultsContainer--lotSelect"),a.addEventListener("submit",e=>{e.preventDefault()})},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})}),e.initializeUnlockFieldButtons(a)})(); \ No newline at end of file diff --git a/routes/lotOccupancies.js b/routes/lotOccupancies.js index 877c9306..771ab48f 100644 --- a/routes/lotOccupancies.js +++ b/routes/lotOccupancies.js @@ -3,10 +3,12 @@ import handler_search from "../handlers/lotOccupancies-get/search.js"; import handler_doSearchLotOccupancies from "../handlers/lotOccupancies-post/doSearchLotOccupancies.js"; import handler_view from "../handlers/lotOccupancies-get/view.js"; import handler_edit from "../handlers/lotOccupancies-get/edit.js"; +import handler_doUpdateLotOccupancy from "../handlers/lotOccupancies-post/doUpdateLotOccupancy.js"; import * as permissionHandlers from "../handlers/permissions.js"; export const router = Router(); router.get("/", handler_search); router.post("/doSearchLotOccupancies", handler_doSearchLotOccupancies); router.get("/:lotOccupancyId", handler_view); router.get("/:lotOccupancyId/edit", permissionHandlers.updateGetHandler, handler_edit); +router.post("/doUpdateLotOccupancy", permissionHandlers.updatePostHandler, handler_doUpdateLotOccupancy); export default router; diff --git a/routes/lotOccupancies.ts b/routes/lotOccupancies.ts index 519af2cb..88848e29 100644 --- a/routes/lotOccupancies.ts +++ b/routes/lotOccupancies.ts @@ -6,7 +6,9 @@ import handler_search from "../handlers/lotOccupancies-get/search.js"; import handler_doSearchLotOccupancies from "../handlers/lotOccupancies-post/doSearchLotOccupancies.js"; import handler_view from "../handlers/lotOccupancies-get/view.js"; + import handler_edit from "../handlers/lotOccupancies-get/edit.js"; +import handler_doUpdateLotOccupancy from "../handlers/lotOccupancies-post/doUpdateLotOccupancy.js"; import * as permissionHandlers from "../handlers/permissions.js"; @@ -22,7 +24,7 @@ router.post("/doSearchLotOccupancies", handler_doSearchLotOccupancies); - router.get("/:lotOccupancyId", +router.get("/:lotOccupancyId", handler_view); @@ -30,5 +32,9 @@ router.get("/:lotOccupancyId/edit", permissionHandlers.updateGetHandler, handler_edit); +router.post("/doUpdateLotOccupancy", + permissionHandlers.updatePostHandler, + handler_doUpdateLotOccupancy); + export default router; \ No newline at end of file diff --git a/types/configTypes.d.ts b/types/configTypes.d.ts index 3d5f5e3d..5b4341ff 100644 --- a/types/configTypes.d.ts +++ b/types/configTypes.d.ts @@ -23,6 +23,12 @@ export interface Config { occupant?: string; occupants?: string; }; + settings?: { + lotOccupancy?: { + lotIdIsRequired?: boolean; + occupancyEndDateIsRequired?: boolean; + }; + }; } interface ConfigApplication { applicationName?: string; diff --git a/types/configTypes.ts b/types/configTypes.ts index da96328f..b0339fc8 100644 --- a/types/configTypes.ts +++ b/types/configTypes.ts @@ -12,7 +12,7 @@ export interface Config { canLogin?: string[]; canUpdate?: string[]; isAdmin?: string[]; - }, + }; aliases?: { lot?: string; lots?: string; @@ -22,7 +22,13 @@ export interface Config { occupancies?: string; occupant?: string; occupants?: string; - } + }; + settings?: { + lotOccupancy?: { + lotIdIsRequired?: boolean; + occupancyEndDateIsRequired?: boolean; + } + }; } interface ConfigApplication { diff --git a/types/recordTypes.d.ts b/types/recordTypes.d.ts index e2ca8ba7..c6d477d3 100644 --- a/types/recordTypes.d.ts +++ b/types/recordTypes.d.ts @@ -107,6 +107,30 @@ export interface Occupant extends Record { occupantPostalCode?: string; occupantPhoneNumber?: string; } +export interface Fee extends Record { + feeId?: number; + feeName?: string; + occupancyTypeId?: number; + lotTypeId?: number; + feeAmount?: number; + feeFunction?: string; + isRequired?: boolean; +} +export interface LotOccupancyFee extends Fee, Record { + lotOccupancyId?: number; + feeAmount?: number; +} +export interface LotOccupancyTransaction extends Record { + lotOccupancyId?: number; + transactionIndex?: number; + transactionDate?: number; + transactionDateString?: string; + transactionTime?: number; + transactionTimeString?: string; + tranactionAmount?: number; + externalReceiptNumber?: string; + transactionNote?: string; +} export interface LotOccupancyOccupant extends Occupant, Record { lotOccupancyId?: number; lotOccupantIndex?: number; @@ -122,6 +146,11 @@ export interface LotOccupancyComment extends Record { lotOccupancyCommentTimeString?: string; lotOccupancyComment?: string; } +export interface LotOccupancyField extends OccupancyTypeField, Record { + lotOccupancyId?: number; + occupancyTypeFieldId?: number; + lotOccupancyFieldValue?: string; +} export interface LotOccupancy extends Record { lotOccupancyId?: number; occupancyTypeId?: number; @@ -134,8 +163,11 @@ export interface LotOccupancy extends Record { occupancyStartDateString?: string; occupancyEndDate?: number; occupancyEndDateString?: string; + lotOccupancyFields?: LotOccupancyField[]; lotOccupancyComments?: LotOccupancyComment[]; lotOccupancyOccupants?: LotOccupancyOccupant[]; + lotOccupancyFees?: LotOccupancyFee[]; + lotOccupancyTransactions?: LotOccupancyTransaction[]; } export interface User { userName: string; diff --git a/types/recordTypes.ts b/types/recordTypes.ts index 21f73f95..d7d7bd44 100644 --- a/types/recordTypes.ts +++ b/types/recordTypes.ts @@ -151,6 +151,39 @@ export interface Occupant extends Record { } +export interface Fee extends Record { + feeId?: number; + feeName?: string; + + occupancyTypeId?: number; + lotTypeId?: number; + + feeAmount?: number; + feeFunction?: string; + + isRequired?: boolean; +} + + +export interface LotOccupancyFee extends Fee, Record { + lotOccupancyId?: number; + feeAmount?: number; +} + + +export interface LotOccupancyTransaction extends Record { + lotOccupancyId?: number; + transactionIndex?: number; + transactionDate?: number; + transactionDateString?: string; + transactionTime?: number; + transactionTimeString?: string; + tranactionAmount?: number; + externalReceiptNumber?: string; + transactionNote?: string; +} + + export interface LotOccupancyOccupant extends Occupant, Record { lotOccupancyId ? : number; lotOccupantIndex ? : number; @@ -174,6 +207,13 @@ export interface LotOccupancyComment extends Record { } +export interface LotOccupancyField extends OccupancyTypeField, Record { + lotOccupancyId?: number; + occupancyTypeFieldId?: number; + lotOccupancyFieldValue?: string; +} + + export interface LotOccupancy extends Record { lotOccupancyId ? : number; @@ -192,8 +232,11 @@ export interface LotOccupancy extends Record { occupancyEndDate ? : number; occupancyEndDateString ? : string; + lotOccupancyFields? : LotOccupancyField[]; lotOccupancyComments ? : LotOccupancyComment[]; lotOccupancyOccupants ? : LotOccupancyOccupant[]; + lotOccupancyFees?: LotOccupancyFee[]; + lotOccupancyTransactions?: LotOccupancyTransaction[]; } diff --git a/views/lotOccupancy-edit.ejs b/views/lotOccupancy-edit.ejs index 8e68bc4d..37e7aa71 100644 --- a/views/lotOccupancy-edit.ejs +++ b/views/lotOccupancy-edit.ejs @@ -16,28 +16,142 @@
  • - <%= configFunctions.getProperty("aliases.occupancy") %> View + <%= configFunctions.getProperty("aliases.occupancy") %>: <%= lotOccupancy.lotName %>
  • - <%= configFunctions.getProperty("aliases.occupancy") %> Edit + Update <%= configFunctions.getProperty("aliases.occupancy") %>
  • - <%= configFunctions.getProperty("aliases.occupancy") %> Edit + <%= configFunctions.getProperty("aliases.occupancy") %> Update

    -
    - -
    +
    +
    + +
    + + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + + disabled /> +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + " + <%= (configFunctions.getProperty("settings.lotOccupancy.occupancyEndDateIsRequired") ? " required" : "") %> /> +
    +
    +
    +
    +
    + +<% if (isCreate) { %> + +<% } else { %> + +

    <%= configFunctions.getProperty("aliases.occupants") %>

    + +

    Comments

    + +
    +
    +

    Fees

    +
    +
    +

    Transactions

    +
    +
    +<% } %> + <%- include('_footerA'); -%> + + + + <%- include('_footerB'); -%> diff --git a/views/lotOccupancy-view.ejs b/views/lotOccupancy-view.ejs index e045dd2d..d978ad43 100644 --- a/views/lotOccupancy-view.ejs +++ b/views/lotOccupancy-view.ejs @@ -1,39 +1,144 @@ <%- include('_header'); -%>

    - <%= configFunctions.getProperty("aliases.occupancy") %> View + <%= configFunctions.getProperty("aliases.occupancy") %>: <%= lotOccupancy.lotName %>

    <% if (user.userProperties.canUpdate) { %> -
    - " href="<%= urlPrefix %>/lotOccupancies/<%= lotOccupancy.lotOccupancyId %>/edit"> - - Update <%= configFunctions.getProperty("aliases.occupancy") %> + +
    <% } %> +
    +
    +

    + <%= configFunctions.getProperty("aliases.occupancy") %> Type
    + <%= lotOccupancy.occupancyType %> +

    +
    +
    +

    + <%= configFunctions.getProperty("aliases.lot") %>
    + <%= lotOccupancy.lotName %> +

    +

    + <%= configFunctions.getProperty("aliases.map") %>
    + <%= lotOccupancy.mapName %> +

    +
    +
    +

    + Start Date
    + <%= lotOccupancy.occupancyStartDateString %> +

    +

    + End Date
    + <%= (lotOccupancy.occupancyEndDateString === "" ? "(No End Date)" : lotOccupancy.occupancyEndDateString) %> +

    +
    +
    + +

    <%= configFunctions.getProperty("aliases.occupants") %>

    + +<% if (lotOccupancy.lotOccupancyOccupants.length === 0) { %> +
    +

    There are no <%= configFunctions.getProperty("aliases.occupants").toLowerCase() %> + associated with this record.

    +
    +<% } else { %> + + + + + + + + + + + <% for (const lotOccupancyOccupant of lotOccupancy.lotOccupancyOccupants) { %> + + + + + + + <% } %> + +
    <%= configFunctions.getProperty("aliases.occupant") %> Type<%= configFunctions.getProperty("aliases.occupant") %>AddressPhone Number
    <%= lotOccupancyOccupant.lotOccupantType %><%= lotOccupancyOccupant.occupantName %> + <%= lotOccupancyOccupant.occupantAddress1 %>
    + <% if (lotOccupancyOccupant.occupantAddress2 && lotOccupancyOccupant.occupantAddress2 !== "") { %> + <%= lotOccupancyOccupant.occupantAddress2 %>
    + <% } %> + <%= lotOccupancyOccupant.occupantCity %>, <%= lotOccupancyOccupant.occupantProvince %>
    + <%= lotOccupancyOccupant.occupantPostalCode %> +
    + <%= lotOccupancyOccupant.occupantPhoneNumber %> +
    +<% } %> + +<% if (lotOccupancy.lotOccupancyComments.length > 0) { %> +

    Comments

    +<% } %> + +
    +
    +

    Fees

    + + <% if (lotOccupancy.lotOccupancyFees.length === 0) { %> +
    +

    + There are no fees applied to this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record. +

    +
    + <% } else { %> + + <% } %> +
    +
    +

    Transactions

    + + <% if (lotOccupancy.lotOccupancyTransactions.length === 0) { %> +
    +

    + There are no transactions associated with this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record. +

    +
    + <% } else { %> + + <% } %> +
    +
    <%- include('_footerA'); -%> -<%- include('_footerB'); -%> +<%- include('_footerB'); -%> \ No newline at end of file