include comments

deepsource-autofix-76c6eb20
Dan Gowans 2024-06-27 15:09:31 -04:00
parent ed2a14ee5f
commit 82906b0133
38 changed files with 383 additions and 9 deletions

24
app.js
View File

@ -27,13 +27,20 @@ import routerReports from './routes/reports.js';
import routerWorkOrders from './routes/workOrders.js';
import { version } from './version.js';
const debug = Debug(`lot-occupancy-system:app:${process.pid}`);
/*
* INITIALIZE THE DATABASE
*/
initializeDatabase();
/*
* INITIALIZE APP
*/
const _dirname = '.';
export const app = express();
app.disable('X-Powered-By');
if (!configFunctions.getConfigProperty('reverseProxy.disableEtag')) {
app.set('etag', false);
}
// View engine setup
app.set('views', path.join(_dirname, 'views'));
app.set('view engine', 'ejs');
if (!configFunctions.getConfigProperty('reverseProxy.disableCompression')) {
@ -51,10 +58,16 @@ app.use(cookieParser());
app.use(csurf({
cookie: true
}));
/*
* Rate Limiter
*/
app.use(rateLimit({
windowMs: 10_000,
max: useTestDatabases ? 1_000_000 : 200
}));
/*
* STATIC ROUTES
*/
const urlPrefix = configFunctions.getConfigProperty('reverseProxy.urlPrefix');
if (urlPrefix !== '') {
debug(`urlPrefix = ${urlPrefix}`);
@ -66,8 +79,12 @@ app.use(`${urlPrefix}/lib/cityssm-bulma-webapp-js`, express.static(path.join('no
app.use(`${urlPrefix}/lib/fa`, express.static(path.join('node_modules', '@fortawesome', 'fontawesome-free')));
app.use(`${urlPrefix}/lib/leaflet`, express.static(path.join('node_modules', 'leaflet', 'dist')));
app.use(`${urlPrefix}/lib/randomcolor/randomColor.js`, express.static(path.join('node_modules', 'randomcolor', 'randomColor.js')));
/*
* SESSION MANAGEMENT
*/
const sessionCookieName = configFunctions.getConfigProperty('session.cookieName');
const FileStoreSession = FileStore(session);
// Initialize session
app.use(session({
store: new FileStoreSession({
path: './data/sessions',
@ -84,6 +101,7 @@ app.use(session({
sameSite: 'strict'
}
}));
// Clear cookie if no corresponding session
app.use((request, response, next) => {
if (Object.hasOwn(request.cookies, sessionCookieName) &&
!Object.hasOwn(request.session, 'user')) {
@ -91,6 +109,7 @@ app.use((request, response, next) => {
}
next();
});
// Redirect logged in users
const sessionChecker = (request, response, next) => {
if (Object.hasOwn(request.session, 'user') &&
Object.hasOwn(request.cookies, sessionCookieName)) {
@ -100,6 +119,10 @@ const sessionChecker = (request, response, next) => {
const redirectUrl = getSafeRedirectURL(request.originalUrl);
response.redirect(`${urlPrefix}/login?redirect=${encodeURIComponent(redirectUrl)}`);
};
/*
* ROUTES
*/
// Make the user and config objects available to the templates
app.use((request, response, next) => {
response.locals.buildNumber = version;
response.locals.user = request.session.user;
@ -140,6 +163,7 @@ app.get(`${urlPrefix}/logout`, (request, response) => {
response.redirect(`${urlPrefix}/login`);
}
});
// Catch 404 and forward to error handler
app.use((request, _response, next) => {
debug(request.url);
next(createError(404, `File not found: ${request.url}`));

View File

@ -68,6 +68,7 @@ if (process.env.STARTUP_TEST === 'true') {
debug(`Killing processes in ${killSeconds} seconds...`);
setTimeout(() => {
debug('Killing processes');
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
process.exit(0);
}, 10_000);
}

View File

@ -1,3 +1,5 @@
// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair
/* eslint-disable n/no-process-exit, unicorn/no-process-exit */
import http from 'node:http';
import Debug from 'debug';
import exitHook from 'exit-hook';
@ -8,15 +10,21 @@ function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
// handle specific listen errors with friendly messages
switch (error.code) {
// eslint-disable-next-line no-fallthrough
case 'EACCES': {
debug('Requires elevated privileges');
process.exit(1);
// break;
}
// eslint-disable-next-line no-fallthrough
case 'EADDRINUSE': {
debug('Port is already in use.');
process.exit(1);
// break;
}
// eslint-disable-next-line no-fallthrough
default: {
throw error;
}
@ -29,6 +37,9 @@ function onListening(server) {
debug(`HTTP Listening on ${bind}`);
}
}
/*
* Initialize HTTP
*/
process.title = `${getConfigProperty('application.applicationName')} (Worker)`;
const httpPort = getConfigProperty('application.httpPort');
const httpServer = http.createServer(app);

View File

@ -18,6 +18,7 @@ describe('Update - Maps', () => {
cy.injectAxe();
cy.checkA11y();
cy.log('Populate the fields');
// eslint-disable-next-line promise/catch-or-return, promise/always-return
cy.fixture('map.json').then((mapJSON) => {
cy.get("input[name='mapName']")
.clear()
@ -53,6 +54,7 @@ describe('Update - Maps', () => {
cy.location('pathname')
.should('not.contain', '/new')
.should('contain', '/edit');
// eslint-disable-next-line promise/catch-or-return, promise/always-return
cy.fixture('map.json').then((mapJSON) => {
cy.get("input[name='mapName']").should('have.value', mapJSON.mapName);
cy.get("textarea[name='mapDescription']").should('have.value', mapJSON.mapDescription);

View File

@ -1,3 +1,4 @@
/* eslint-disable node/no-unpublished-import */
import 'cypress-axe';
export const logout = () => {
cy.visit('/logout');
@ -11,6 +12,7 @@ export const login = (userName) => {
cy.get("form [name='password']").type(userName);
cy.get('form').submit();
cy.location('pathname').should('not.contain', '/login');
// Logged in pages have a navbar
cy.get('.navbar').should('have.length', 1);
};
export const ajaxDelayMillis = 800;

View File

@ -36,6 +36,7 @@ export const configDefaultValues = {
'settings.lot.lotNamePattern': undefined,
'settings.lot.lotNameHelpText': '',
'settings.lot.lotNameSortNameFunction': (lotName) => lotName,
// eslint-disable-next-line no-secrets/no-secrets
'settings.lotOccupancy.occupancyEndDateIsRequired': true,
'settings.lotOccupancy.occupantCityDefault': '',
'settings.lotOccupancy.occupantProvinceDefault': '',
@ -50,6 +51,7 @@ export const configDefaultValues = {
'settings.printPdf.contentDisposition': 'attachment',
'settings.dynamicsGP.integrationIsEnabled': false,
'settings.dynamicsGP.mssqlConfig': undefined,
// eslint-disable-next-line no-secrets/no-secrets
'settings.dynamicsGP.lookupOrder': ['invoice'],
'settings.dynamicsGP.accountCodes': [],
'settings.dynamicsGP.itemNumbers': [],

View File

@ -1,6 +1,7 @@
import Debug from 'debug';
import { getConfigProperty } from '../helpers/functions.config.js';
const debug = Debug('lot-occupancy-system:databasePaths');
// Determine if test databases should be used
export const useTestDatabases = getConfigProperty('application.useTestDatabases') ||
process.env.TEST_DATABASES === 'true';
if (useTestDatabases) {

View File

@ -5,6 +5,7 @@ import { acquireConnection } from './pool.js';
export default async function addLotOccupancyFee(lotOccupancyFeeForm, user) {
const database = await acquireConnection();
const rightNowMillis = Date.now();
// Calculate fee and tax (if not set)
let feeAmount;
let taxAmount;
if ((lotOccupancyFeeForm.feeAmount ?? '') === '') {
@ -23,6 +24,7 @@ export default async function addLotOccupancyFee(lotOccupancyFeeForm, user) {
? Number.parseFloat(lotOccupancyFeeForm.taxAmount)
: 0;
}
// Check if record already exists
const record = database
.prepare(`select feeAmount, taxAmount, recordDelete_timeMillis
from LotOccupancyFees
@ -69,6 +71,7 @@ export default async function addLotOccupancyFee(lotOccupancyFeeForm, user) {
return true;
}
}
// Create new record
const result = database
.prepare(`insert into LotOccupancyFees (
lotOccupancyId, feeId,

View File

@ -9,6 +9,9 @@ export default async function cleanupDatabase(user) {
1000;
let inactivatedRecordCount = 0;
let purgedRecordCount = 0;
/*
* Work Order Comments
*/
inactivatedRecordCount += database
.prepare(`update WorkOrderComments
set recordDelete_userName = ?,
@ -20,6 +23,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from WorkOrderComments where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Order Lot Occupancies
*/
inactivatedRecordCount += database
.prepare(`update WorkOrderLotOccupancies
set recordDelete_userName = ?,
@ -31,6 +37,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from WorkOrderLotOccupancies where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Order Lots
*/
inactivatedRecordCount += database
.prepare(`update WorkOrderLots
set recordDelete_userName = ?,
@ -42,6 +51,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from WorkOrderLots where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Order Milestones
*/
inactivatedRecordCount += database
.prepare(`update WorkOrderMilestones
set recordDelete_userName = ?,
@ -53,6 +65,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from WorkOrderMilestones where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Orders
*/
purgedRecordCount += database
.prepare(`delete from WorkOrders
where recordDelete_timeMillis <= ?
@ -61,17 +76,26 @@ export default async function cleanupDatabase(user) {
and workOrderId not in (select workOrderId from WorkOrderLots)
and workOrderId not in (select workOrderId from WorkOrderMilestones)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Order Milestone Types
*/
purgedRecordCount += database
.prepare(`delete from WorkOrderMilestoneTypes
where recordDelete_timeMillis <= ?
and workOrderMilestoneTypeId not in (
select workOrderMilestoneTypeId from WorkOrderMilestones)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Work Order Types
*/
purgedRecordCount += database
.prepare(`delete from WorkOrderTypes
where recordDelete_timeMillis <= ?
and workOrderTypeId not in (select workOrderTypeId from WorkOrders)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupancy Comments
*/
inactivatedRecordCount += database
.prepare(`update LotOccupancyComments
set recordDelete_userName = ?,
@ -83,6 +107,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from LotOccupancyComments where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupancy Fields
*/
inactivatedRecordCount += database
.prepare(`update LotOccupancyFields
set recordDelete_userName = ?,
@ -93,6 +120,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from LotOccupancyFields where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupancy Occupants
*/
inactivatedRecordCount += database
.prepare(`update LotOccupancyOccupants
set recordDelete_userName = ?,
@ -103,12 +133,19 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from LotOccupancyOccupants where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupancy Fees/Transactions
* - Maintain financials, do not delete related.
*/
purgedRecordCount += database
.prepare('delete from LotOccupancyFees where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
purgedRecordCount += database
.prepare('delete from LotOccupancyTransactions where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupancies
*/
purgedRecordCount += database
.prepare(`delete from LotOccupancies
where recordDelete_timeMillis <= ?
@ -119,6 +156,9 @@ export default async function cleanupDatabase(user) {
and lotOccupancyId not in (select lotOccupancyId from LotOccupancyTransactions)
and lotOccupancyId not in (select lotOccupancyId from WorkOrderLotOccupancies)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Fees
*/
inactivatedRecordCount += database
.prepare(`update Fees
set recordDelete_userName = ?,
@ -131,11 +171,17 @@ export default async function cleanupDatabase(user) {
where recordDelete_timeMillis <= ?
and feeId not in (select feeId from LotOccupancyFees)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Fee Categories
*/
purgedRecordCount += database
.prepare(`delete from FeeCategories
where recordDelete_timeMillis <= ?
and feeCategoryId not in (select feeCategoryId from Fees)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Occupancy Type Fields
*/
inactivatedRecordCount += database
.prepare(`update OccupancyTypeFields
set recordDelete_userName = ?,
@ -148,6 +194,9 @@ export default async function cleanupDatabase(user) {
where recordDelete_timeMillis <= ?
and occupancyTypeFieldId not in (select occupancyTypeFieldId from LotOccupancyFields)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Occupancy Type Prints
*/
inactivatedRecordCount += database
.prepare(`update OccupancyTypePrints
set recordDelete_userName = ?,
@ -158,6 +207,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from OccupancyTypePrints where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Occupancy Types
*/
purgedRecordCount += database
.prepare(`delete from OccupancyTypes
where recordDelete_timeMillis <= ?
@ -166,11 +218,17 @@ export default async function cleanupDatabase(user) {
and occupancyTypeId not in (select occupancyTypeId from LotOccupancies)
and occupancyTypeId not in (select occupancyTypeId from Fees)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Occupant Types
*/
purgedRecordCount += database
.prepare(`delete from LotOccupantTypes
where recordDelete_timeMillis <= ?
and lotOccupantTypeId not in (select lotOccupantTypeId from LotOccupancyOccupants)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Comments
*/
inactivatedRecordCount += database
.prepare(`update LotComments
set recordDelete_userName = ?,
@ -181,6 +239,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from LotComments where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Fields
*/
inactivatedRecordCount += database
.prepare(`update LotFields
set recordDelete_userName = ?,
@ -191,6 +252,9 @@ export default async function cleanupDatabase(user) {
purgedRecordCount += database
.prepare('delete from LotFields where recordDelete_timeMillis <= ?')
.run(recordDeleteTimeMillisMin).changes;
/*
* Lots
*/
inactivatedRecordCount += database
.prepare(`update Lots
set recordDelete_userName = ?,
@ -206,11 +270,17 @@ export default async function cleanupDatabase(user) {
and lotId not in (select lotId from LotOccupancies)
and lotId not in (select lotId from WorkOrderLots)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Statuses
*/
purgedRecordCount += database
.prepare(`delete from LotStatuses
where recordDelete_timeMillis <= ?
and lotStatusId not in (select lotStatusId from Lots)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Type Fields
*/
inactivatedRecordCount += database
.prepare(`update LotTypeFields
set recordDelete_userName = ?,
@ -223,6 +293,9 @@ export default async function cleanupDatabase(user) {
where recordDelete_timeMillis <= ?
and lotTypeFieldId not in (select lotTypeFieldId from LotFields)`)
.run(recordDeleteTimeMillisMin).changes;
/*
* Lot Types
*/
purgedRecordCount += database
.prepare(`delete from LotTypes
where recordDelete_timeMillis <= ?

View File

@ -12,6 +12,9 @@ export default async function copyLotOccupancy(oldLotOccupancyId, user) {
occupancyStartDateString: dateToString(new Date()),
occupancyEndDateString: ''
}, user, database);
/*
* Copy Fields
*/
const rightNowMillis = Date.now();
for (const occupancyField of oldLotOccupancy.lotOccupancyFields ?? []) {
database
@ -22,6 +25,9 @@ export default async function copyLotOccupancy(oldLotOccupancyId, user) {
values (?, ?, ?, ?, ?, ?, ?)`)
.run(newLotOccupancyId, occupancyField.occupancyTypeFieldId, occupancyField.lotOccupancyFieldValue, user.userName, rightNowMillis, user.userName, rightNowMillis);
}
/*
* Copy Occupants
*/
for (const occupant of oldLotOccupancy.lotOccupancyOccupants ?? []) {
await addLotOccupancyOccupant({
lotOccupancyId: newLotOccupancyId,

View File

@ -6,7 +6,9 @@ export default async function getLotOccupancyComments(lotOccupancyId, connectedD
database.function('userFn_timeIntegerToString', timeIntegerToString);
database.function('userFn_timeIntegerToPeriodString', timeIntegerToPeriodString);
const lotComments = database
.prepare(`select lotOccupancyCommentId,
.prepare(
// eslint-disable-next-line no-secrets/no-secrets
`select lotOccupancyCommentId,
lotOccupancyCommentDate, userFn_dateIntegerToString(lotOccupancyCommentDate) as lotOccupancyCommentDateString,
lotOccupancyCommentTime,
userFn_timeIntegerToString(lotOccupancyCommentTime) as lotOccupancyCommentTimeString,

View File

@ -5,11 +5,15 @@ export default async function getNextWorkOrderNumber(connectedDatabase) {
const paddingLength = getConfigProperty('settings.workOrders.workOrderNumberLength');
const currentYearString = new Date().getFullYear().toString();
const regex = new RegExp(`^${currentYearString}-\\d+$`);
database.function('userFn_matchesWorkOrderNumberSyntax', (workOrderNumber) => {
database.function(
// eslint-disable-next-line no-secrets/no-secrets
'userFn_matchesWorkOrderNumberSyntax', (workOrderNumber) => {
return regex.test(workOrderNumber) ? 1 : 0;
});
const workOrderNumberRecord = database
.prepare(`select workOrderNumber from WorkOrders
.prepare(
// eslint-disable-next-line no-secrets/no-secrets
`select workOrderNumber from WorkOrders
where userFn_matchesWorkOrderNumberSyntax(workOrderNumber) = 1
order by cast(substr(workOrderNumber, instr(workOrderNumber, '-') + 1) as integer) desc`)
.get();

View File

@ -1,6 +1,7 @@
import { getConfigProperty } from '../helpers/functions.config.js';
import { acquireConnection } from './pool.js';
const availablePrints = getConfigProperty('settings.lotOccupancy.prints');
// eslint-disable-next-line @typescript-eslint/naming-convention
const userFunction_configContainsPrintEJS = (printEJS) => {
if (printEJS === '*' || availablePrints.includes(printEJS)) {
return 1;
@ -9,7 +10,9 @@ const userFunction_configContainsPrintEJS = (printEJS) => {
};
export default async function getOccupancyTypePrints(occupancyTypeId, connectedDatabase) {
const database = connectedDatabase ?? (await acquireConnection());
database.function('userFn_configContainsPrintEJS', userFunction_configContainsPrintEJS);
database.function(
// eslint-disable-next-line no-secrets/no-secrets
'userFn_configContainsPrintEJS', userFunction_configContainsPrintEJS);
const results = database
.prepare(`select printEJS, orderNumber
from OccupancyTypePrints

View File

@ -1,3 +1,5 @@
// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair
/* eslint-disable no-case-declarations */
import { dateIntegerToString, dateStringToInteger, dateToInteger, timeIntegerToString } from '@cityssm/utils-datetime';
import camelCase from 'camelcase';
import { getConfigProperty } from '../helpers/functions.config.js';
@ -38,6 +40,7 @@ const occupantCommentAlias = `${occupantCamelCase}Comment`;
export default async function getReportData(reportName, reportParameters = {}) {
let sql;
const sqlParameters = [];
// eslint-disable-next-line sonarjs/max-switch-cases
switch (reportName) {
case 'maps-all': {
sql = 'select * from Maps';

View File

@ -3,6 +3,7 @@ import { getConfigProperty } from '../helpers/functions.config.js';
import getLotOccupancies from './getLotOccupancies.js';
import getLots from './getLots.js';
import { acquireConnection } from './pool.js';
// eslint-disable-next-line security/detect-unsafe-regex
const commaSeparatedNumbersRegex = /^\d+(?:,\d+)*$/;
function buildWhereClause(filters) {
let sqlWhereClause = ' where m.recordDelete_timeMillis is null and w.recordDelete_timeMillis is null';
@ -66,7 +67,9 @@ export default async function getWorkOrderMilestones(filters, options, connected
database.function('userFn_dateIntegerToString', dateIntegerToString);
database.function('userFn_timeIntegerToString', timeIntegerToString);
database.function('userFn_timeIntegerToPeriodString', timeIntegerToPeriodString);
// Filters
const { sqlWhereClause, sqlParameters } = buildWhereClause(filters);
// Order By
let orderByClause = '';
switch (options.orderBy) {
case 'completion': {
@ -84,6 +87,8 @@ export default async function getWorkOrderMilestones(filters, options, connected
break;
}
}
// Query
// eslint-disable-next-line no-secrets/no-secrets
const sql = `select m.workOrderMilestoneId,
m.workOrderMilestoneTypeId, t.workOrderMilestoneType,
m.workOrderMilestoneDate,

View File

@ -1,3 +1,5 @@
// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, n/no-unpublished-import */
import gulp from 'gulp';
import changed from 'gulp-changed';
import include from 'gulp-include';
@ -5,14 +7,22 @@ import minify from 'gulp-minify';
import gulpSass from 'gulp-sass';
import dartSass from 'sass';
const sass = gulpSass(dartSass);
/*
* Compile SASS
*/
const publicSCSSDestination = 'public/stylesheets';
function publicSCSSFunction() {
return gulp
.src('public-scss/*.scss')
.pipe(sass({ outputStyle: 'compressed', includePaths: ['node_modules'] }).on('error', sass.logError))
.pipe(sass({ outputStyle: 'compressed', includePaths: ['node_modules'] }).on('error',
// eslint-disable-next-line @typescript-eslint/unbound-method
sass.logError))
.pipe(gulp.dest(publicSCSSDestination));
}
gulp.task('public-scss', publicSCSSFunction);
/*
* Minify public/javascripts
*/
const publicJavascriptsDestination = 'public/javascripts';
function publicJavascriptsMinFunction() {
return gulp
@ -45,6 +55,9 @@ gulp.task('public-javascript-adminTables', publicJavascriptsAdminTablesFunction)
gulp.task('public-javascript-lotOccupancyEdit', publicJavascriptsLotOccupancyEditFunction);
gulp.task('public-javascript-workOrderEdit', publicJavascriptsWorkOrderEditFunction);
gulp.task('public-javascript-min', publicJavascriptsMinFunction);
/*
* Watch
*/
function watchFunction() {
gulp.watch('public-scss/*.scss', publicSCSSFunction);
gulp.watch('public-typescript/adminTables/*.js', publicJavascriptsAdminTablesFunction);
@ -53,6 +66,9 @@ function watchFunction() {
gulp.watch('public-typescript/*.js', publicJavascriptsMinFunction);
}
gulp.task('watch', watchFunction);
/*
* Initialize default
*/
gulp.task('default', () => {
publicJavascriptsAdminTablesFunction();
publicJavascriptsLotOccupancyEditFunction();

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import ical, { ICalEventStatus } from 'ical-generator';
import getWorkOrderMilestones from '../../database/getWorkOrderMilestones.js';
import { getConfigProperty } from '../../helpers/functions.config.js';
@ -38,6 +39,7 @@ function buildEventSummary(milestone) {
}
return summary;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function buildEventDescriptionHTML_occupancies(request, milestone) {
let descriptionHTML = '';
if (milestone.workOrderLotOccupancies.length > 0) {
@ -82,6 +84,7 @@ function buildEventDescriptionHTML_occupancies(request, milestone) {
}
return descriptionHTML;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function buildEventDescriptionHTML_lots(request, milestone) {
let descriptionHTML = '';
if (milestone.workOrderLots.length > 0) {
@ -118,6 +121,7 @@ function buildEventDescriptionHTML_lots(request, milestone) {
}
return descriptionHTML;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function buildEventDescriptionHTML_prints(request, milestone) {
let descriptionHTML = '';
const prints = getConfigProperty('settings.workOrders.prints');
@ -169,6 +173,9 @@ function buildEventLocation(milestone) {
}
export default async function handler(request, response) {
const urlRoot = getUrlRoot(request);
/*
* Get work order milestones
*/
const workOrderMilestoneFilters = {
workOrderTypeIds: request.query.workOrderTypeIds,
workOrderMilestoneTypeIds: request.query.workOrderMilestoneTypeIds
@ -184,6 +191,9 @@ export default async function handler(request, response) {
includeWorkOrders: true,
orderBy: 'date'
});
/*
* Create calendar object
*/
const calendar = ical({
name: 'Work Order Milestone Calendar',
url: `${urlRoot}/workOrders`
@ -196,13 +206,19 @@ export default async function handler(request, response) {
company: calendarCompany,
product: calendarProduct
});
/*
* Loop through milestones
*/
for (const milestone of workOrderMilestones) {
const milestoneTimePieces = `${milestone.workOrderMilestoneDateString} ${milestone.workOrderMilestoneTimeString}`.split(timeStringSplitRegex);
const milestoneDate = new Date(Number.parseInt(milestoneTimePieces[0], 10), Number.parseInt(milestoneTimePieces[1], 10) - 1, Number.parseInt(milestoneTimePieces[2], 10), Number.parseInt(milestoneTimePieces[3], 10), Number.parseInt(milestoneTimePieces[4], 10));
const milestoneEndDate = new Date(milestoneDate.getTime());
milestoneEndDate.setHours(milestoneEndDate.getHours() + 1);
// Build summary (title in Outlook)
const summary = buildEventSummary(milestone);
// Build URL
const workOrderUrl = getWorkOrderUrl(request, milestone);
// Create event
const eventData = {
start: milestoneDate,
created: new Date(milestone.recordCreate_timeMillis),
@ -216,22 +232,27 @@ export default async function handler(request, response) {
eventData.end = milestoneEndDate;
}
const calendarEvent = calendar.createEvent(eventData);
// Build description
const descriptionHTML = buildEventDescriptionHTML(request, milestone);
calendarEvent.description({
plain: workOrderUrl,
html: descriptionHTML
});
// Set status
if (milestone.workOrderMilestoneCompletionDate) {
calendarEvent.status(ICalEventStatus.CONFIRMED);
}
// Add categories
const categories = buildEventCategoryList(milestone);
for (const category of categories) {
calendarEvent.createCategory({
name: category
});
}
// Set location
const location = buildEventLocation(milestone);
calendarEvent.location(location);
// Set organizer / attendees
if (milestone.workOrderLotOccupancies.length > 0) {
let organizerSet = false;
for (const lotOccupancy of milestone.workOrderLotOccupancies ?? []) {

View File

@ -14,13 +14,13 @@ export default async function handler(_request, response) {
const workOrderResults = await getWorkOrders({
workOrderOpenDateString: currentDateString
}, {
limit: 1,
limit: 1, // only using the count
offset: 0
});
const lotOccupancyResults = await getLotOccupancies({
occupancyStartDateString: currentDateString
}, {
limit: 1,
limit: 1, // only using the count
offset: 0,
includeFees: false,
includeOccupants: false,

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/filename-case */
import { getDynamicsGPDocument } from '../../helpers/functions.dynamicsGP.js';
export default async function handler(request, response) {
const externalReceiptNumber = request.body.externalReceiptNumber;

View File

@ -9,6 +9,9 @@ import getWorkOrderMilestoneTypesFromDatabase from '../database/getWorkOrderMile
import getWorkOrderTypesFromDatabase from '../database/getWorkOrderTypes.js';
import { getConfigProperty } from './functions.config.js';
const debug = Debug(`lot-occupancy-system:functions.cache:${process.pid}`);
/*
* Lot Occupant Types
*/
let lotOccupantTypes;
export async function getLotOccupantTypes() {
if (lotOccupantTypes === undefined) {
@ -33,6 +36,9 @@ export async function getLotOccupantTypeByLotOccupantType(lotOccupantType) {
function clearLotOccupantTypesCache() {
lotOccupantTypes = undefined;
}
/*
* Lot Statuses
*/
let lotStatuses;
export async function getLotStatuses() {
if (lotStatuses === undefined) {
@ -56,6 +62,9 @@ export async function getLotStatusByLotStatus(lotStatus) {
function clearLotStatusesCache() {
lotStatuses = undefined;
}
/*
* Lot Types
*/
let lotTypes;
export async function getLotTypes() {
if (lotTypes === undefined) {
@ -79,6 +88,9 @@ export async function getLotTypesByLotType(lotType) {
function clearLotTypesCache() {
lotTypes = undefined;
}
/*
* Occupancy Types
*/
let occupancyTypes;
let allOccupancyTypeFields;
export async function getOccupancyTypes() {
@ -122,6 +134,9 @@ function clearOccupancyTypesCache() {
occupancyTypes = undefined;
allOccupancyTypeFields = undefined;
}
/*
* Work Order Types
*/
let workOrderTypes;
export async function getWorkOrderTypes() {
if (workOrderTypes === undefined) {
@ -138,6 +153,9 @@ export async function getWorkOrderTypeById(workOrderTypeId) {
function clearWorkOrderTypesCache() {
workOrderTypes = undefined;
}
/*
* Work Order Milestone Types
*/
let workOrderMilestoneTypes;
export async function getWorkOrderMilestoneTypes() {
if (workOrderMilestoneTypes === undefined) {

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import { DynamicsGP } from '@cityssm/dynamics-gp';
import { getConfigProperty } from './functions.config.js';
let gp;

View File

@ -5,7 +5,7 @@ import getNextLotIdFromDatabase from '../database/getNextLotId.js';
import getPreviousLotIdFromDatabase from '../database/getPreviousLotId.js';
const debug = Debug(`lot-occupancy-system:functions.lots:${process.pid}`);
const cacheOptions = {
stdTTL: 2 * 60,
stdTTL: 2 * 60, // two minutes
useClones: false
};
const previousLotIdCache = new NodeCache(cacheOptions);

View File

@ -1,8 +1,11 @@
// skipcq: JS-C1003 - Added to ReportData
import * as dateTimeFunctions from '@cityssm/utils-datetime';
import getLot from '../database/getLot.js';
import getLotOccupancy from '../database/getLotOccupancy.js';
import getWorkOrder from '../database/getWorkOrder.js';
// skipcq: JS-C1003 - Added to ReportData
import * as configFunctions from './functions.config.js';
// skipcq: JS-C1003 - Added to ReportData
import * as lotOccupancyFunctions from './functions.lotOccupancy.js';
const screenPrintConfigs = {
lotOccupancy: {
@ -22,6 +25,7 @@ const pdfPrintConfigs = {
title: 'Work Order Field Sheet - Comment Log',
params: ['workOrderId']
},
// Occupancy
'ssm.cemetery.burialPermit': {
title: 'Burial Permit',
params: ['lotOccupancyId']

View File

@ -14,6 +14,9 @@ const user = {
}
};
export async function initializeCemeteryDatabase() {
/*
* Ensure database does not already exist
*/
debug(`Checking for ${databasePath}...`);
const databaseInitialized = initializeDatabase();
if (!databaseInitialized) {
@ -22,15 +25,24 @@ export async function initializeCemeteryDatabase() {
return false;
}
debug('New database file created. Proceeding with initialization.');
/*
* Lot Types
*/
await addRecord('LotTypes', 'Casket Grave', 1, user);
await addRecord('LotTypes', 'Columbarium', 2, user);
await addRecord('LotTypes', 'Mausoleum', 2, user);
await addRecord('LotTypes', 'Niche Wall', 2, user);
await addRecord('LotTypes', 'Urn Garden', 2, user);
await addRecord('LotTypes', 'Crematorium', 2, user);
/*
* Lot Statuses
*/
await addRecord('LotStatuses', 'Available', 1, user);
await addRecord('LotStatuses', 'Reserved', 2, user);
await addRecord('LotStatuses', 'Taken', 3, user);
/*
* Lot Occupant Types
*/
await addLotOccupantType({
lotOccupantType: 'Deceased',
fontAwesomeIconClass: 'cross',
@ -52,9 +64,13 @@ export async function initializeCemeteryDatabase() {
occupantCommentTitle: 'Relationship to Owner/Deceased',
orderNumber: 4
}, user);
/*
* Occupancy Types
*/
await addRecord('OccupancyTypes', 'Preneed', 1, user);
const intermentOccupancyTypeId = await addRecord('OccupancyTypes', 'Interment', 2, user);
const cremationOccupancyTypeId = await addRecord('OccupancyTypes', 'Cremation', 3, user);
// Death Date
const deathDateField = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Death Date',
@ -69,6 +85,7 @@ export async function initializeCemeteryDatabase() {
await addOccupancyTypeField(Object.assign(deathDateField, {
occupancyTypeId: cremationOccupancyTypeId
}), user);
// Death Age
const deathAgeField = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Death Age',
@ -81,6 +98,7 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(deathAgeField, user);
await addOccupancyTypeField(Object.assign(deathAgeField, { occupancyTypeId: cremationOccupancyTypeId }), user);
// Death Age Period
const deathAgePeriod = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Death Age Period',
@ -95,6 +113,7 @@ export async function initializeCemeteryDatabase() {
await addOccupancyTypeField(Object.assign(deathAgePeriod, {
occupancyTypeId: cremationOccupancyTypeId
}), user);
// Death Place
const deathPlace = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Death Place',
@ -107,6 +126,7 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(deathPlace, user);
await addOccupancyTypeField(Object.assign(deathPlace, { occupancyTypeId: cremationOccupancyTypeId }), user);
// Funeral Home
const funeralHome = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Funeral Home',
@ -119,6 +139,7 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(funeralHome, user);
await addOccupancyTypeField(Object.assign(funeralHome, { occupancyTypeId: cremationOccupancyTypeId }), user);
// Funeral Date
const funeralDate = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Funeral Date',
@ -131,6 +152,7 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(funeralDate, user);
await addOccupancyTypeField(Object.assign(funeralDate, { occupancyTypeId: cremationOccupancyTypeId }), user);
// Container Type
const containerType = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Container Type',
@ -143,6 +165,7 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(containerType, user);
await addOccupancyTypeField(Object.assign(containerType, { occupancyTypeId: cremationOccupancyTypeId }), user);
// Committal Type
const committalType = {
occupancyTypeId: intermentOccupancyTypeId,
occupancyTypeField: 'Committal Type',
@ -155,11 +178,17 @@ export async function initializeCemeteryDatabase() {
};
await addOccupancyTypeField(committalType, user);
await addOccupancyTypeField(Object.assign(committalType, { occupancyTypeId: cremationOccupancyTypeId }), user);
/*
* Fee Categories
*/
await addRecord('FeeCategories', 'Interment Rights', 1, user);
await addRecord('FeeCategories', 'Cremation Services', 2, user);
await addRecord('FeeCategories', 'Burial Charges', 3, user);
await addRecord('FeeCategories', 'Disinterment of Human Remains', 4, user);
await addRecord('FeeCategories', 'Additional Services', 5, user);
/*
* Work Orders
*/
await addRecord('WorkOrderTypes', 'Cemetery Work Order', 1, user);
await addRecord('WorkOrderMilestoneTypes', 'Funeral', 1, user);
await addRecord('WorkOrderMilestoneTypes', 'Arrival', 2, user);

View File

@ -9,20 +9,33 @@ const recordColumns = `recordCreate_userName varchar(30) not null,
recordDelete_userName varchar(30),
recordDelete_timeMillis integer`;
const createStatements = [
/*
* Lot Types
*/
`create table if not exists LotTypes (lotTypeId integer not null primary key autoincrement, lotType varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`,
'create index if not exists idx_lottypes_ordernumber on LotTypes (orderNumber, lotType)',
`create table if not exists LotTypeFields (lotTypeFieldId integer not null primary key autoincrement, lotTypeId integer not null, lotTypeField varchar(100) not null, lotTypeFieldValues text, isRequired bit not null default 0, pattern varchar(100), minimumLength smallint not null default 1 check (minimumLength >= 0), maximumLength smallint not null default 100 check (maximumLength >= 0), orderNumber smallint not null default 0, ${recordColumns}, foreign key (lotTypeId) references LotTypes (lotTypeId))`,
'create index if not exists idx_lottypefields_ordernumber on LotTypeFields (lotTypeId, orderNumber, lotTypeField)',
/*
* Lot Statuses
*/
`create table if not exists LotStatuses (lotStatusId integer not null primary key autoincrement, lotStatus varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`,
'create index if not exists idx_lotstatuses_ordernumber on LotStatuses (orderNumber, lotStatus)',
/*
* Maps and Lots
*/
`create table if not exists Maps (mapId integer not null primary key autoincrement, mapName varchar(200) not null, mapDescription text, mapLatitude decimal(10, 8) check (mapLatitude between -90 and 90), mapLongitude decimal(11, 8) check (mapLongitude between -180 and 180), mapSVG varchar(50), mapAddress1 varchar(50), mapAddress2 varchar(50), mapCity varchar(20), mapProvince varchar(2), mapPostalCode varchar(7), mapPhoneNumber varchar(30), ${recordColumns})`,
`create table if not exists Lots (lotId integer not null primary key autoincrement, lotTypeId integer not null, lotName varchar(100), mapId integer, mapKey varchar(100), lotLatitude decimal(10, 8) check (lotLatitude between -90 and 90), lotLongitude decimal(11, 8) check (lotLongitude between -180 and 180), lotStatusId integer, ${recordColumns}, foreign key (lotTypeId) references LotTypes (lotTypeId), foreign key (mapId) references Maps (mapId), foreign key (lotStatusId) references LotStatuses (lotStatusId))`,
`create table if not exists LotFields (lotId integer not null, lotTypeFieldId integer not null, lotFieldValue text not null, ${recordColumns}, primary key (lotId, lotTypeFieldId), foreign key (lotId) references Lots (lotId), foreign key (lotTypeFieldId) references LotTypeFields (lotTypeFieldId)) without rowid`,
`create table if not exists LotComments (lotCommentId integer not null primary key autoincrement, lotId integer not null, lotCommentDate integer not null check (lotCommentDate > 0), lotCommentTime integer not null check (lotCommentTime >= 0), lotComment text not null, ${recordColumns}, foreign key (lotId) references Lots (lotId))`,
'create index if not exists idx_lotcomments_datetime on LotComments (lotId, lotCommentDate, lotCommentTime)',
/*
* Occupancies
*/
`create table if not exists OccupancyTypes (occupancyTypeId integer not null primary key autoincrement, occupancyType varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`,
'create index if not exists idx_occupancytypes_ordernumber on OccupancyTypes (orderNumber, occupancyType)',
`create table if not exists OccupancyTypeFields (occupancyTypeFieldId integer not null primary key autoincrement, occupancyTypeId integer, occupancyTypeField varchar(100) not null, occupancyTypeFieldValues text, isRequired bit not null default 0, pattern varchar(100), minimumLength smallint not null default 1 check (minimumLength >= 0), maximumLength smallint not null default 100 check (maximumLength >= 0), orderNumber smallint not null default 0, ${recordColumns}, foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId))`,
// eslint-disable-next-line no-secrets/no-secrets
'create index if not exists idx_occupancytypefields_ordernumber on OccupancyTypeFields (occupancyTypeId, orderNumber, occupancyTypeField)',
`create table if not exists OccupancyTypePrints (occupancyTypeId integer not null, printEJS varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns}, primary key (occupancyTypeId, printEJS), foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId))`,
'create index if not exists idx_occupancytypeprints_ordernumber on OccupancyTypePrints (occupancyTypeId, orderNumber, printEJS)',
@ -33,12 +46,16 @@ const createStatements = [
occupantCommentTitle varchar(50) not null default '',
orderNumber smallint not null default 0,
${recordColumns})`,
// eslint-disable-next-line no-secrets/no-secrets
'create index if not exists idx_lotoccupanttypes_ordernumber on LotOccupantTypes (orderNumber, lotOccupantType)',
`create table if not exists LotOccupancies (lotOccupancyId integer not null primary key autoincrement, occupancyTypeId integer not null, lotId integer, occupancyStartDate integer not null check (occupancyStartDate > 0), occupancyEndDate integer check (occupancyEndDate > 0), ${recordColumns}, foreign key (lotId) references Lots (lotId), foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId))`,
`create table if not exists LotOccupancyOccupants (lotOccupancyId integer not null, lotOccupantIndex integer not null, occupantName varchar(200) not null, occupantAddress1 varchar(50), occupantAddress2 varchar(50), occupantCity varchar(20), occupantProvince varchar(2), occupantPostalCode varchar(7), occupantPhoneNumber varchar(30), occupantEmailAddress varchar(200), lotOccupantTypeId integer not null, occupantComment text not null default '', ${recordColumns}, primary key (lotOccupancyId, lotOccupantIndex), foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId), foreign key (lotOccupantTypeId) references LotOccupantTypes (lotOccupantTypeId)) without rowid`,
`create table if not exists LotOccupancyFields (lotOccupancyId integer not null, occupancyTypeFieldId integer not null, lotOccupancyFieldValue text not null, ${recordColumns}, primary key (lotOccupancyId, occupancyTypeFieldId), foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId), foreign key (occupancyTypeFieldId) references OccupancyTypeFields (occupancyTypeFieldId)) without rowid`,
`create table if not exists LotOccupancyComments (lotOccupancyCommentId integer not null primary key autoincrement, lotOccupancyId integer not null, lotOccupancyCommentDate integer not null check (lotOccupancyCommentDate > 0), lotOccupancyCommentTime integer not null check (lotOccupancyCommentTime >= 0), lotOccupancyComment text not null, ${recordColumns}, foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId))`,
'create index if not exists idx_lotoccupancycomments_datetime on LotOccupancyComments (lotOccupancyId, lotOccupancyCommentDate, lotOccupancyCommentTime)',
/*
* Fees and Transactions
*/
`create table if not exists FeeCategories (feeCategoryId integer not null primary key autoincrement, feeCategory varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`,
`create table if not exists Fees (
feeId integer not null primary key autoincrement,
@ -64,6 +81,9 @@ const createStatements = [
`create table if not exists LotOccupancyFees (lotOccupancyId integer not null, feeId integer not null, quantity decimal(4, 1) not null default 1, feeAmount decimal(8, 2) not null, taxAmount decmial(8, 2) not null, ${recordColumns}, primary key (lotOccupancyId, feeId), foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId), foreign key (feeId) references Fees (feeId)) without rowid`,
`create table if not exists LotOccupancyTransactions (lotOccupancyId integer not null, transactionIndex integer not null, transactionDate integer not null check (transactionDate > 0), transactionTime integer not null check (transactionTime >= 0), transactionAmount decimal(8, 2) not null, externalReceiptNumber varchar(100), transactionNote text, ${recordColumns}, primary key (lotOccupancyId, transactionIndex), foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)) without rowid`,
'create index if not exists idx_lotoccupancytransactions_ordernumber on LotOccupancyTransactions (lotOccupancyId, transactionDate, transactionTime)',
/*
* Work Orders
*/
`create table if not exists WorkOrderTypes (workOrderTypeId integer not null primary key autoincrement, workOrderType varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`,
'create index if not exists idx_workordertypes_ordernumber on WorkOrderTypes (orderNumber, workOrderType)',
`create table if not exists WorkOrders (workOrderId integer not null primary key autoincrement, workOrderTypeId integer not null, workOrderNumber varchar(50) not null, workOrderDescription text, workOrderOpenDate integer check (workOrderOpenDate > 0), workOrderCloseDate integer check (workOrderCloseDate > 0), ${recordColumns}, foreign key (workOrderTypeId) references WorkOrderTypes (workOrderTypeId))`,

View File

@ -61,7 +61,11 @@ import handler_doUpdateOccupancyType from '../handlers/admin-post/doUpdateOccupa
import handler_doUpdateOccupancyTypeField from '../handlers/admin-post/doUpdateOccupancyTypeField.js';
import handler_doUpdateWorkOrderMilestoneType from '../handlers/admin-post/doUpdateWorkOrderMilestoneType.js';
import handler_doUpdateWorkOrderType from '../handlers/admin-post/doUpdateWorkOrderType.js';
// Ntfy Startup
export const router = Router();
/*
* Fees
*/
router.get('/fees', handler_fees);
router.post('/doAddFeeCategory', handler_doAddFeeCategory);
router.post('/doUpdateFeeCategory', handler_doUpdateFeeCategory);
@ -73,55 +77,73 @@ router.post('/doUpdateFee', handler_doUpdateFee);
router.post('/doMoveFeeUp', handler_doMoveFeeUp);
router.post('/doMoveFeeDown', handler_doMoveFeeDown);
router.post('/doDeleteFee', handler_doDeleteFee);
/*
* Occupancy Type Management
*/
router.get('/occupancyTypes', handler_occupancyTypes);
router.post('/doAddOccupancyType', handler_doAddOccupancyType);
router.post('/doUpdateOccupancyType', handler_doUpdateOccupancyType);
router.post('/doMoveOccupancyTypeUp', handler_doMoveOccupancyTypeUp);
router.post('/doMoveOccupancyTypeDown', handler_doMoveOccupancyTypeDown);
router.post('/doDeleteOccupancyType', handler_doDeleteOccupancyType);
// Occupancy Type Fields
router.post('/doAddOccupancyTypeField', handler_doAddOccupancyTypeField);
router.post('/doUpdateOccupancyTypeField', handler_doUpdateOccupancyTypeField);
router.post('/doMoveOccupancyTypeFieldUp', handler_doMoveOccupancyTypeFieldUp);
router.post('/doMoveOccupancyTypeFieldDown', handler_doMoveOccupancyTypeFieldDown);
router.post('/doDeleteOccupancyTypeField', handler_doDeleteOccupancyTypeField);
// Occupancy Type Prints
router.post('/doAddOccupancyTypePrint', handler_doAddOccupancyTypePrint);
router.post('/doMoveOccupancyTypePrintUp', handler_doMoveOccupancyTypePrintUp);
router.post('/doMoveOccupancyTypePrintDown', handler_doMoveOccupancyTypePrintDown);
router.post('/doDeleteOccupancyTypePrint', handler_doDeleteOccupancyTypePrint);
/*
* Lot Type Management
*/
router.get('/lotTypes', handler_lotTypes);
router.post('/doAddLotType', handler_doAddLotType);
router.post('/doUpdateLotType', handler_doUpdateLotType);
router.post('/doMoveLotTypeUp', handler_doMoveLotTypeUp);
router.post('/doMoveLotTypeDown', handler_doMoveLotTypeDown);
router.post('/doDeleteLotType', handler_doDeleteLotType);
// Lot Type Fields
router.post('/doAddLotTypeField', handler_doAddLotTypeField);
router.post('/doUpdateLotTypeField', handler_doUpdateLotTypeField);
router.post('/doMoveLotTypeFieldUp', handler_doMoveLotTypeFieldUp);
router.post('/doMoveLotTypeFieldDown', handler_doMoveLotTypeFieldDown);
router.post('/doDeleteLotTypeField', handler_doDeleteLotTypeField);
/*
* Config Tables
*/
router.get('/tables', handler_tables);
// Config Tables - Work Order Types
router.post('/doAddWorkOrderType', handler_doAddWorkOrderType);
router.post('/doUpdateWorkOrderType', handler_doUpdateWorkOrderType);
router.post('/doMoveWorkOrderTypeUp', handler_doMoveWorkOrderTypeUp);
router.post('/doMoveWorkOrderTypeDown', handler_doMoveWorkOrderTypeDown);
router.post('/doDeleteWorkOrderType', handler_doDeleteWorkOrderType);
// Config Tables - Work Order Milestone Types
router.post('/doAddWorkOrderMilestoneType', handler_doAddWorkOrderMilestoneType);
router.post('/doUpdateWorkOrderMilestoneType', handler_doUpdateWorkOrderMilestoneType);
router.post('/doMoveWorkOrderMilestoneTypeUp', handler_doMoveWorkOrderMilestoneTypeUp);
router.post('/doMoveWorkOrderMilestoneTypeDown', handler_doMoveWorkOrderMilestoneTypeDown);
router.post('/doDeleteWorkOrderMilestoneType', handler_doDeleteWorkOrderMilestoneType);
// Config Tables - Lot Statuses
router.post('/doAddLotStatus', handler_doAddLotStatus);
router.post('/doUpdateLotStatus', handler_doUpdateLotStatus);
router.post('/doMoveLotStatusUp', handler_doMoveLotStatusUp);
router.post('/doMoveLotStatusDown', handler_doMoveLotStatusDown);
router.post('/doDeleteLotStatus', handler_doDeleteLotStatus);
// Config Tables - Lot Occupant Types
router.post('/doAddLotOccupantType', handler_doAddLotOccupantType);
router.post('/doUpdateLotOccupantType', handler_doUpdateLotOccupantType);
router.post('/doMoveLotOccupantTypeUp', handler_doMoveLotOccupantTypeUp);
router.post('/doMoveLotOccupantTypeDown', handler_doMoveLotOccupantTypeDown);
router.post('/doDeleteLotOccupantType', handler_doDeleteLotOccupantType);
// Database Maintenance
router.get('/database', handler_database);
router.post('/doBackupDatabase', handler_doBackupDatabase);
router.post('/doCleanupDatabase', handler_doCleanupDatabase);
// Ntfy Startup
router.get('/ntfyStartup', handler_ntfyStartup);
export default router;

View File

@ -27,27 +27,35 @@ import handler_doUpdateLotOccupancyTransaction from '../handlers/lotOccupancies-
import { updateGetHandler, updatePostHandler } from '../handlers/permissions.js';
import { getConfigProperty } from '../helpers/functions.config.js';
export const router = Router();
// Search
router.get('/', handler_search);
router.post('/doSearchLotOccupancies', handler_doSearchLotOccupancies);
// Create
router.get('/new', updateGetHandler, handler_new);
router.post('/doGetOccupancyTypeFields', updatePostHandler, handler_doGetOccupancyTypeFields);
router.post('/doCreateLotOccupancy', updatePostHandler, handler_doCreateLotOccupancy);
// View
router.get('/:lotOccupancyId', handler_view);
// Edit
router.get('/:lotOccupancyId/edit', updateGetHandler, handler_edit);
router.post('/doUpdateLotOccupancy', updatePostHandler, handler_doUpdateLotOccupancy);
router.post('/doCopyLotOccupancy', updatePostHandler, handler_doCopyLotOccupancy);
router.post('/doDeleteLotOccupancy', updatePostHandler, handler_doDeleteLotOccupancy);
// Occupants
router.post('/doSearchPastOccupants', updatePostHandler, handler_doSearchPastOccupants);
router.post('/doAddLotOccupancyOccupant', updatePostHandler, handler_doAddLotOccupancyOccupant);
router.post('/doUpdateLotOccupancyOccupant', updatePostHandler, handler_doUpdateLotOccupancyOccupant);
router.post('/doDeleteLotOccupancyOccupant', updatePostHandler, handler_doDeleteLotOccupancyOccupant);
// Comments
router.post('/doAddLotOccupancyComment', updatePostHandler, handler_doAddLotOccupancyComment);
router.post('/doUpdateLotOccupancyComment', updatePostHandler, handler_doUpdateLotOccupancyComment);
router.post('/doDeleteLotOccupancyComment', updatePostHandler, handler_doDeleteLotOccupancyComment);
// Fees
router.post('/doGetFees', updatePostHandler, handler_doGetFees);
router.post('/doAddLotOccupancyFee', updatePostHandler, handler_doAddLotOccupancyFee);
router.post('/doUpdateLotOccupancyFeeQuantity', updatePostHandler, handler_doUpdateLotOccupancyFeeQuantity);
router.post('/doDeleteLotOccupancyFee', updatePostHandler, handler_doDeleteLotOccupancyFee);
// Transactions
if (getConfigProperty('settings.dynamicsGP.integrationIsEnabled')) {
router.post('/doGetDynamicsGPDocument', updatePostHandler, handler_doGetDynamicsGPDocument);
}

View File

@ -15,8 +15,14 @@ import handler_doUpdateLot from '../handlers/lots-post/doUpdateLot.js';
import handler_doUpdateLotComment from '../handlers/lots-post/doUpdateLotComment.js';
import * as permissionHandlers from '../handlers/permissions.js';
export const router = Router();
/*
* Lot Search
*/
router.get('/', handler_search);
router.post('/doSearchLots', handler_doSearchLots);
/*
* Lot View / Edit
*/
router.get('/new', permissionHandlers.updateGetHandler, handler_new);
router.get('/:lotId', handler_view);
router.get('/:lotId/next', handler_next);

View File

@ -27,27 +27,36 @@ import handler_doUpdateWorkOrder from '../handlers/workOrders-post/doUpdateWorkO
import handler_doUpdateWorkOrderComment from '../handlers/workOrders-post/doUpdateWorkOrderComment.js';
import handler_doUpdateWorkOrderMilestone from '../handlers/workOrders-post/doUpdateWorkOrderMilestone.js';
export const router = Router();
// Search
router.get('/', handler_search);
router.post('/doSearchWorkOrders', handler_doSearchWorkOrders);
// Milestone Calendar
router.get('/milestoneCalendar', handler_milestoneCalendar);
router.post('/doGetWorkOrderMilestones', handler_doGetWorkOrderMilestones);
// Outlook Integration
router.get('/outlook', handler_outlook);
// New
router.get('/new', permissionHandlers.updateGetHandler, handler_new);
router.post('/doCreateWorkOrder', permissionHandlers.updatePostHandler, handler_doCreateWorkOrder);
// View
router.get('/:workOrderId', handler_view);
router.post('/doReopenWorkOrder', permissionHandlers.updatePostHandler, handler_doReopenWorkOrder);
// Edit
router.get('/:workOrderId/edit', permissionHandlers.updateGetHandler, handler_edit);
router.post('/doUpdateWorkOrder', permissionHandlers.updatePostHandler, handler_doUpdateWorkOrder);
router.post('/doCloseWorkOrder', permissionHandlers.updatePostHandler, handler_doCloseWorkOrder);
router.post('/doDeleteWorkOrder', permissionHandlers.updatePostHandler, handler_doDeleteWorkOrder);
// Lot Occupancy
router.post('/doAddWorkOrderLotOccupancy', permissionHandlers.updatePostHandler, handler_doAddWorkOrderLotOccupancy);
router.post('/doDeleteWorkOrderLotOccupancy', permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLotOccupancy);
router.post('/doAddWorkOrderLot', permissionHandlers.updatePostHandler, handler_doAddWorkOrderLot);
router.post('/doUpdateLotStatus', permissionHandlers.updatePostHandler, handler_doUpdateLotStatus);
router.post('/doDeleteWorkOrderLot', permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderLot);
// Comments
router.post('/doAddWorkOrderComment', permissionHandlers.updatePostHandler, handler_doAddWorkOrderComment);
router.post('/doUpdateWorkOrderComment', permissionHandlers.updatePostHandler, handler_doUpdateWorkOrderComment);
router.post('/doDeleteWorkOrderComment', permissionHandlers.updatePostHandler, handler_doDeleteWorkOrderComment);
// Milestones
router.post('/doAddWorkOrderMilestone', permissionHandlers.updatePostHandler, handler_doAddWorkOrderMilestone);
router.post('/doUpdateWorkOrderMilestone', permissionHandlers.updatePostHandler, handler_doUpdateWorkOrderMilestone);
router.post('/doCompleteWorkOrderMilestone', permissionHandlers.updatePostHandler, handler_doCompleteWorkOrderMilestone);

View File

@ -105,6 +105,12 @@ const cemeteryToMapName = {
const mapCache = new Map();
async function getMap(dataRow) {
const mapCacheKey = dataRow.cemetery;
/*
if (masterRow.CM_CEMETERY === "HS" &&
(masterRow.CM_BLOCK === "F" || masterRow.CM_BLOCK === "G" || masterRow.CM_BLOCK === "H" || masterRow.CM_BLOCK === "J")) {
mapCacheKey += "-" + masterRow.CM_BLOCK;
}
*/
if (mapCache.has(mapCacheKey)) {
return mapCache.get(mapCacheKey);
}
@ -181,14 +187,17 @@ async function importFromMasterCSV() {
masterRow.CM_INTERMENT_YR !== '0') {
occupancyEndDateString = formatDateString(masterRow.CM_INTERMENT_YR, masterRow.CM_INTERMENT_MON, masterRow.CM_INTERMENT_DAY);
}
// if purchase date unavailable
if (preneedOccupancyStartDateString === '0000-00-00' &&
occupancyEndDateString !== '') {
preneedOccupancyStartDateString = occupancyEndDateString;
}
// if end date unavailable
if (preneedOccupancyStartDateString === '0000-00-00' &&
masterRow.CM_DEATH_YR !== '' &&
masterRow.CM_DEATH_YR !== '0') {
preneedOccupancyStartDateString = formatDateString(masterRow.CM_DEATH_YR, masterRow.CM_DEATH_MON, masterRow.CM_DEATH_DAY);
// if death took place, and there's no preneed end date
if (occupancyEndDateString === '0000-00-00' ||
occupancyEndDateString === '') {
occupancyEndDateString = preneedOccupancyStartDateString;
@ -251,6 +260,7 @@ async function importFromMasterCSV() {
let deceasedLotOccupancyId;
if (masterRow.CM_DECEASED_NAME !== '') {
deceasedOccupancyStartDateString = formatDateString(masterRow.CM_INTERMENT_YR, masterRow.CM_INTERMENT_MON, masterRow.CM_INTERMENT_DAY);
// if interment date unavailable
if (deceasedOccupancyStartDateString === '0000-00-00' &&
masterRow.CM_DEATH_YR !== '' &&
masterRow.CM_DEATH_YR !== '0') {
@ -331,6 +341,20 @@ async function importFromMasterCSV() {
occupantPhoneNumber: funeralHomeOccupant.occupantPhoneNumber,
occupantEmailAddress: funeralHomeOccupant.occupantEmailAddress
}, user);
/*
addOrUpdateLotOccupancyField(
{
lotOccupancyId: deceasedLotOccupancyId,
occupancyTypeFieldId: allOccupancyTypeFields.find(
(occupancyTypeField) => {
return occupancyTypeField.occupancyTypeField === "Funeral Home";
}
).occupancyTypeFieldId,
lotOccupancyFieldValue: masterRow.CM_FUNERAL_HOME
},
user
);
*/
}
if (masterRow.CM_FUNERAL_YR !== '') {
const lotOccupancyFieldValue = formatDateString(masterRow.CM_FUNERAL_YR, masterRow.CM_FUNERAL_MON, masterRow.CM_FUNERAL_DAY);
@ -811,6 +835,18 @@ async function importFromWorkOrderCSV() {
occupantPhoneNumber: funeralHomeOccupant.occupantPhoneNumber,
occupantEmailAddress: funeralHomeOccupant.occupantEmailAddress
}, user);
/*
addOrUpdateLotOccupancyField(
{
lotOccupancyId: lotOccupancyId,
occupancyTypeFieldId: allOccupancyTypeFields.find((occupancyTypeField) => {
return occupancyTypeField.occupancyTypeField === "Funeral Home";
}).occupancyTypeFieldId,
lotOccupancyFieldValue: workOrderRow.WO_FUNERAL_HOME
},
user
);
*/
}
if (workOrderRow.WO_FUNERAL_YR !== '') {
const lotOccupancyFieldValue = formatDateString(workOrderRow.WO_FUNERAL_YR, workOrderRow.WO_FUNERAL_MON, workOrderRow.WO_FUNERAL_DAY);
@ -850,6 +886,7 @@ async function importFromWorkOrderCSV() {
workOrderId: workOrder.workOrderId,
lotOccupancyId
}, user);
// Milestones
let hasIncompleteMilestones = !workOrderRow.WO_CONFIRMATION_IN;
let maxMilestoneCompletionDateString = workOrderOpenDateString;
if (importIds.acknowledgedWorkOrderMilestoneTypeId) {
@ -971,6 +1008,7 @@ async function importFromWorkOrderCSV() {
console.log(`Started ${new Date().toLocaleString()}`);
console.time('importFromCsv');
purgeTables();
// purgeConfigTables();
await importFromMasterCSV();
await importFromPrepaidCSV();
await importFromWorkOrderCSV();

View File

@ -1,6 +1,11 @@
// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair
/* eslint-disable unicorn/no-await-expression-member */
import sqlite from 'better-sqlite3';
import { lotOccupancyDB as databasePath } from '../data/databasePaths.js';
import * as cacheFunctions from '../helpers/functions.cache.js';
/*
* Fee IDs
*/
const feeCache = new Map();
export function getFeeIdByFeeDescription(feeDescription) {
if (feeCache.keys.length === 0) {
@ -17,6 +22,9 @@ export function getFeeIdByFeeDescription(feeDescription) {
}
return feeCache.get(feeDescription);
}
/*
* Lot Occupant Type IDs
*/
export const preneedOwnerLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Preneed Owner'))
.lotOccupantTypeId;
export const funeralDirectorLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Funeral Director')).lotOccupantTypeId;
@ -24,9 +32,15 @@ export const deceasedLotOccupantTypeId = (await cacheFunctions.getLotOccupantTyp
.lotOccupantTypeId;
export const purchaserLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Purchaser'))
.lotOccupantTypeId;
/*
* Lot Status IDs
*/
export const availableLotStatusId = (await cacheFunctions.getLotStatusByLotStatus('Available')).lotStatusId;
export const reservedLotStatusId = (await cacheFunctions.getLotStatusByLotStatus('Reserved')).lotStatusId;
export const takenLotStatusId = (await cacheFunctions.getLotStatusByLotStatus('Taken')).lotStatusId;
/*
* Lot Type IDs
*/
const casketLotTypeId = (await cacheFunctions.getLotTypesByLotType('Casket Grave')).lotTypeId;
const columbariumLotTypeId = (await cacheFunctions.getLotTypesByLotType('Columbarium')).lotTypeId;
const crematoriumLotTypeId = (await cacheFunctions.getLotTypesByLotType('Crematorium')).lotTypeId;
@ -55,12 +69,21 @@ export function getLotTypeId(dataRow) {
}
return casketLotTypeId;
}
/*
* Occupancy Type IDs
*/
export const preneedOccupancyType = (await cacheFunctions.getOccupancyTypeByOccupancyType('Preneed'));
export const deceasedOccupancyType = (await cacheFunctions.getOccupancyTypeByOccupancyType('Interment'));
export const cremationOccupancyType = (await cacheFunctions.getOccupancyTypeByOccupancyType('Cremation'));
/*
* Work Order Milestone Type IDs
*/
export const acknowledgedWorkOrderMilestoneTypeId = (await cacheFunctions.getWorkOrderMilestoneTypeByWorkOrderMilestoneType('Acknowledged'))?.workOrderMilestoneTypeId;
export const deathWorkOrderMilestoneTypeId = (await cacheFunctions.getWorkOrderMilestoneTypeByWorkOrderMilestoneType('Death'))?.workOrderMilestoneTypeId;
export const funeralWorkOrderMilestoneTypeId = (await cacheFunctions.getWorkOrderMilestoneTypeByWorkOrderMilestoneType('Funeral'))?.workOrderMilestoneTypeId;
export const cremationWorkOrderMilestoneTypeId = (await cacheFunctions.getWorkOrderMilestoneTypeByWorkOrderMilestoneType('Cremation'))?.workOrderMilestoneTypeId;
export const intermentWorkOrderMilestoneTypeId = (await cacheFunctions.getWorkOrderMilestoneTypeByWorkOrderMilestoneType('Interment'))?.workOrderMilestoneTypeId;
/*
* Work Order Type IDs
*/
export const workOrderTypeId = 1;

View File

@ -20,6 +20,7 @@ async function importMaps() {
}
}
catch {
// ignore
}
finally {
try {
@ -28,6 +29,7 @@ async function importMaps() {
}
}
catch {
// ignore
}
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import assert from 'node:assert';
import fs from 'node:fs/promises';
import { lotOccupancyDB as databasePath, useTestDatabases } from '../data/databasePaths.js';

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import assert from 'node:assert';
import { exec } from 'node:child_process';
import * as http from 'node:http';
@ -34,6 +35,7 @@ describe('lot-occupancy-system', () => {
httpServer.close();
}
catch {
// ignore
}
});
it(`Ensure server starts on port ${portNumber.toString()}`, () => {

View File

@ -1,8 +1,11 @@
import assert from 'node:assert';
import fs from 'node:fs';
import { lotNameSortNameFunction } from '../data/config.cemetery.ssm.js';
// skipcq: JS-C1003 - Testing functions
import * as cacheFunctions from '../helpers/functions.cache.js';
// skipcq: JS-C1003 - Testing functions
import * as sqlFilterFunctions from '../helpers/functions.sqlFilters.js';
// skipcq: JS-C1003 - Testing functions
import * as userFunctions from '../helpers/functions.user.js';
describe('config.cemetery.ssm', () => {
it('Sorts burial site names', () => {
@ -13,6 +16,7 @@ describe('config.cemetery.ssm', () => {
});
describe('functions.cache', () => {
const badId = -3;
// eslint-disable-next-line no-secrets/no-secrets
const badName = 'qwertyuiopasdfghjklzxcvbnm';
before(() => {
cacheFunctions.clearCaches();

View File

@ -6,7 +6,6 @@
"isolatedModules": false,
"declaration": true,
"noImplicitAny": false,
"removeComments": true,
"allowUnreachableCode": false,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,

View File

@ -1,6 +1,10 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import { Service } from 'node-windows';
import { serviceConfig } from './windowsService.js';
// Create a new service object
const svc = new Service(serviceConfig);
// Listen for the "install" event, which indicates the
// process is available as a service.
svc.on('install', () => {
svc.start();
});

View File

@ -1,8 +1,12 @@
/* eslint-disable unicorn/filename-case, @eslint-community/eslint-comments/disable-enable-pair */
import { Service } from 'node-windows';
import { serviceConfig } from './windowsService.js';
// Create a new service object
const svc = new Service(serviceConfig);
// Listen for the "uninstall" event so we know when it's done.
svc.on('uninstall', () => {
console.log('Uninstall complete.');
console.log('The service exists:', svc.exists);
});
// Uninstall the service.
svc.uninstall();