From 400383c96fa4b1a3922c384f6f63febc25a25469 Mon Sep 17 00:00:00 2001 From: Paul Walsh Date: Tue, 22 Feb 2022 13:11:28 +0200 Subject: [PATCH] Add public ids for tickets. --- package.json | 6 ++-- src/cli/ensure-public-ids.ts | 12 +++++++ src/core/service-requests/helpers.ts | 41 +++++++++++++++++++++- src/core/service-requests/models.ts | 20 ++++++++++- src/core/service-requests/repositories.ts | 4 --- src/migrations/11_public_id_for_request.ts | 22 ++++++++++++ src/types/data.ts | 2 ++ src/types/db.ts | 4 ++- test/test-models.ts | 12 ++++++- 9 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 src/cli/ensure-public-ids.ts create mode 100644 src/migrations/11_public_id_for_request.ts diff --git a/package.json b/package.json index dd1027b..0f09d66 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "generate-fake-data": "ts-node ./src/cli/generate-fake-data", "send-test-email": "ts-node ./src/cli/send-test-email", "send-test-sms": "ts-node ./src/cli/send-test-sms", - "send-test-dispatch": "ts-node ./src/cli/send-test-dispatch" + "send-test-dispatch": "ts-node ./src/cli/send-test-dispatch", + "ensure-public-ids": "ts-node ./src/cli/ensure-public-ids" }, "bin": { "govflow-start": "./cli/default-server.js", @@ -21,7 +22,8 @@ "govflow-generate-fake-data": "./cli/generate-fake-data.js", "govflow-send-test-email": "./cli/send-test-email.js", "govflow-send-test-sms": "./cli/send-test-sms.js", - "govflow-send-test-dispatch": "./cli/send-test-dispatch.js" + "govflow-send-test-dispatch": "./cli/send-test-dispatch.js", + "govflow-ensure-public-ids": "./cli/ensure-public-ids.js" }, "license": "MIT", "dependencies": { diff --git a/src/cli/ensure-public-ids.ts b/src/cli/ensure-public-ids.ts new file mode 100644 index 0000000..7b0f624 --- /dev/null +++ b/src/cli/ensure-public-ids.ts @@ -0,0 +1,12 @@ +#! /usr/bin/env node +import { createApp } from '..'; + +(async () => { + const app = await createApp(); + const { database } = app; + const { ServiceRequest } = database.models; + const records = await ServiceRequest.findAll(); + for (const record of records) { + await record.save() + } +})(); diff --git a/src/core/service-requests/helpers.ts b/src/core/service-requests/helpers.ts index c56bbbf..3b43cdb 100644 --- a/src/core/service-requests/helpers.ts +++ b/src/core/service-requests/helpers.ts @@ -1,4 +1,5 @@ -import { StaffUserAttributes } from "../../types"; +import { Op } from "sequelize"; +import { ServiceRequestInstance, ServiceRequestModel, StaffUserAttributes } from "../../types"; export function makeAuditMessage(user: StaffUserAttributes | undefined, fieldName: string, oldValue:string, newValue:string): string { let displayName = 'System'; @@ -7,3 +8,41 @@ export function makeAuditMessage(user: StaffUserAttributes | undefined, fieldNam } return `${displayName} changed this request ${fieldName} from ${oldValue} to ${newValue}`; } + +export function makePublicIdString(year: number, month: number, counter: number): string { + const yearStr = year.toString(10).slice(-2); + const monthStr = String((month + 1).toString(10)).padStart(2, "0"); + return `${yearStr}${monthStr}${counter}`; +} +export async function makePublicIdForRequest(instance: ServiceRequestInstance): Promise { + if (instance.publicId) { + // do nothing if the instance already has a publicId + } else { + const year = instance.createdAt.getFullYear(); + const month = instance.createdAt.getMonth(); + const windowLower = new Date(year, month, 0); + const windowHigher = (month != 11) ? new Date(year, month + 1, 0) : new Date(year + 1, 0, 0); + const ServiceRequest = instance.constructor as ServiceRequestModel; + const lookup = await ServiceRequest.findAll( + { + where: { + jurisdictionId: instance.jurisdictionId, + createdAt: { [Op.gte]: windowLower, [Op.lt]: windowHigher } + } + } + ) as ServiceRequestInstance[]; + let lastIncrement = 0; + if (lookup.length > 0) { + const lastRecord = lookup.reduce( + (last: ServiceRequestInstance, curr: ServiceRequestInstance) => + (last.idCounter > curr.idCounter) ? last : curr + + ); + lastIncrement = lastRecord.idCounter; + } + const idCounter = lastIncrement + 1; + const publicId = makePublicIdString(year, month, idCounter); + instance.idCounter = idCounter; + instance.publicId = publicId; + } +} diff --git a/src/core/service-requests/models.ts b/src/core/service-requests/models.ts index c2c62b6..39c30c8 100644 --- a/src/core/service-requests/models.ts +++ b/src/core/service-requests/models.ts @@ -1,5 +1,6 @@ import { DataTypes } from 'sequelize'; -import { ModelDefinition } from '../../types'; +import { ModelDefinition, ServiceRequestInstance } from '../../types'; +import { makePublicIdForRequest } from './helpers'; export const REQUEST_STATUSES = { 'inbox': 'Inbox', @@ -39,6 +40,14 @@ export const ServiceRequestModel: ModelDefinition = { allowNull: false, primaryKey: true, }, + publicId: { + type: DataTypes.STRING, + allowNull: false, + }, + idCounter: { + type: DataTypes.INTEGER, + allowNull: false, + }, inputChannel: { allowNull: false, type: DataTypes.ENUM(...INPUT_CHANNEL_KEYS), @@ -172,6 +181,10 @@ export const ServiceRequestModel: ModelDefinition = { unique: true, fields: ['id', 'jurisdictionId'] }, + { + unique: true, + fields: ['publicId', 'jurisdictionId'] + }, { unique: false, fields: ['status'] @@ -195,6 +208,11 @@ export const ServiceRequestModel: ModelDefinition = { throw new Error('A Service Request requires one of a lat/lon pair, address, or address_id.'); } } + }, + hooks: { + beforeValidate(instance: ServiceRequestInstance) { + return makePublicIdForRequest(instance); + } } } } diff --git a/src/core/service-requests/repositories.ts b/src/core/service-requests/repositories.ts index 03de83d..3d42c9f 100644 --- a/src/core/service-requests/repositories.ts +++ b/src/core/service-requests/repositories.ts @@ -138,8 +138,6 @@ export class ServiceRequestRepository implements IServiceRequestRepository { ): Promise { const { ServiceRequest } = this.models; let record = await ServiceRequest.findByPk(id) as ServiceRequestInstance; - console.error("ASSIGNED TO") - console.error(user); const auditMessage = makeAuditMessage(user, '"assigned to"', record.assignedTo, assignedTo); record.assignedTo = assignedTo; record = await record.save(); @@ -152,8 +150,6 @@ export class ServiceRequestRepository implements IServiceRequestRepository { ): Promise { const { ServiceRequest } = this.models; let record = await ServiceRequest.findByPk(id) as ServiceRequestInstance; - console.error("DEPARTMENT") - console.error(user); const auditMessage = makeAuditMessage(user, '"department"', record.departmentId, department); record.departmentId = department; record = await record.save(); diff --git a/src/migrations/11_public_id_for_request.ts b/src/migrations/11_public_id_for_request.ts new file mode 100644 index 0000000..e5750de --- /dev/null +++ b/src/migrations/11_public_id_for_request.ts @@ -0,0 +1,22 @@ +import type { QueryInterface } from 'sequelize'; +import { DataTypes } from 'sequelize'; + +export async function up({ context: queryInterface }: Record): Promise { + await queryInterface.addColumn( + 'ServiceRequest', + 'publicId', + { type: DataTypes.STRING, allowNull: false } + ); + await queryInterface.addColumn( + 'ServiceRequest', + 'idCounter', + { type: DataTypes.INTEGER, allowNull: false } + ); + await queryInterface.addIndex('ServiceRequest', ['jurisdictionId', 'publicId']); +} + +export async function down({ context: queryInterface }: Record): Promise { + await queryInterface.removeColumn('ServiceRequest', 'publicId'); + await queryInterface.removeColumn('ServiceRequest', 'idCounter'); + await queryInterface.removeIndex('ServiceRequest', ['jurisdictionId', 'publicId']) +} diff --git a/src/types/data.ts b/src/types/data.ts index aed8fc2..933484c 100644 --- a/src/types/data.ts +++ b/src/types/data.ts @@ -49,6 +49,8 @@ export interface ServiceInstance export interface ServiceRequestAttributes { id: string; + publicId: string, + idCounter: number, serviceId?: string; jurisdictionId: string; description: string; diff --git a/src/types/db.ts b/src/types/db.ts index d784de1..26b1675 100644 --- a/src/types/db.ts +++ b/src/types/db.ts @@ -31,11 +31,13 @@ export interface ServiceRequestFilterParams { assignedTo?: string; } +export type ServiceRequestModel = ModelCtor>; + export interface Models { Jurisdiction: ModelCtor>, StaffUser: ModelCtor>, Service: ModelCtor>, - ServiceRequest: ModelCtor>, + ServiceRequest: ServiceRequestModel, ServiceRequestComment: ModelCtor>, Communication: ModelCtor>, Department: ModelCtor>, diff --git a/test/test-models.ts b/test/test-models.ts index 1e4fb99..ef56e7a 100644 --- a/test/test-models.ts +++ b/test/test-models.ts @@ -2,7 +2,7 @@ import chai from 'chai'; import type { Application } from 'express'; import { createApp } from '../src/index'; import makeTestData from '../src/tools/fake-data-generator'; -import { TestDataPayload } from '../src/types'; +import { ServiceRequestInstance, TestDataPayload } from '../src/types'; describe('Verify Core Models.', function () { @@ -60,6 +60,16 @@ describe('Verify Core Models.', function () { } }); + it('should have generated public ids for service requests', async function () { + const { ServiceRequest } = app.database.models; + const records = await ServiceRequest.findAll() as ServiceRequestInstance[]; + + for (const record of records) { + chai.assert(record.publicId); + chai.assert(record.idCounter); + } + }); + it('should write communications to database', async function () { const { Communication } = app.database.models; for (const communicationData of testData.communications) {