Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple files to be selected/dropped at once #78

Merged
merged 9 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions app/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export const DEFAULTCORS = Object.freeze({
origin: true
});

/**
* Basic
*/
export const YesNo = Object.freeze({ YES: 'Yes', NO: 'No' });
export const YesNoUnsure = Object.freeze({ YES: 'Yes', NO: 'No', UNSURE: 'Unsure' });

/** Current user authentication types */
export const IdentityProvider = Object.freeze({
IDIR: 'idir',
Expand All @@ -34,7 +40,7 @@ export const Initiatives = Object.freeze({
HOUSING: 'HOUSING'
});

/** CHEFS form statuses */
/** SHAS form statuses */
export const APPLICATION_STATUS_LIST = Object.freeze({
NEW: 'New',
IN_PROGRESS: 'In Progress',
Expand All @@ -45,13 +51,20 @@ export const APPLICATION_STATUS_LIST = Object.freeze({
export const INTAKE_STATUS_LIST = Object.freeze({
SUBMITTED: 'Submitted',
ASSIGNED: 'Assigned',
COMPLETED: 'Completed'
COMPLETED: 'Completed',
DRAFT: 'Draft'
});

export const RENTAL_STATUS_LIST = Object.freeze({
export const PERMIT_NEEDED = Object.freeze({
YES: 'Yes',
NO: 'No',
UNSURE: 'Unsure'
UNDER_INVESTIGATION: 'Under investigation',
NO: 'No'
});

export const PERMIT_STATUS = Object.freeze({
NEW: 'New',
APPLIED: 'Applied',
COMPLETED: 'Completed'
});

/** Types of notes */
Expand Down
12 changes: 12 additions & 0 deletions app/src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,5 +227,17 @@ export function redactSecrets(data: { [key: string]: unknown }, fields: Array<st
* @returns {object} An arbitrary object with specified secret fields marked as redacted
*/
export function toTitleCase(str: string): string {
if (!str) return str;

return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
}

/**
* @function uuidToActivityId
* Converts a UUDI to an activity ID
* @param {string} id The ID to convert
* @returns {string} A truncated version of the given ID
*/
export function uuidToActivityId(id: string): string {
return id.substring(0, 8).toUpperCase();
}
160 changes: 137 additions & 23 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import config from 'config';
import { NIL, v4 as uuidv4 } from 'uuid';

import { APPLICATION_STATUS_LIST, RENTAL_STATUS_LIST } from '../components/constants';
import { camelCaseToTitleCase, deDupeUnsure, getCurrentIdentity, isTruthy, toTitleCase } from '../components/utils';
import {
APPLICATION_STATUS_LIST,
INTAKE_STATUS_LIST,
PERMIT_NEEDED,
PERMIT_STATUS,
YesNo,
YesNoUnsure
} from '../components/constants';
import { camelCaseToTitleCase, deDupeUnsure, getCurrentIdentity, toTitleCase } from '../components/utils';
import { generateUniqueActivityId } from '../db/utils/utils';
import { submissionService, permitService, userService } from '../services';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';
Expand Down Expand Up @@ -38,10 +46,16 @@ const controller = {
Object.values<ChefsFormConfigData>(cfg).map(async (x: ChefsFormConfigData) => {
return (await submissionService.getFormExport(x.id)).map((data: ChefsSubmissionExport) => {
const financiallySupportedValues = {
financiallySupportedBC: isTruthy(data.isBCHousingSupported),
financiallySupportedIndigenous: isTruthy(data.isIndigenousHousingProviderSupported),
financiallySupportedNonProfit: isTruthy(data.isNonProfitSupported),
financiallySupportedHousingCoop: isTruthy(data.isHousingCooperativeSupported)
financiallySupportedBC: data.isBCHousingSupported ? toTitleCase(data.isBCHousingSupported) : YesNo.NO,
financiallySupportedIndigenous: data.isIndigenousHousingProviderSupported
? toTitleCase(data.isIndigenousHousingProviderSupported)
: YesNo.NO,
financiallySupportedNonProfit: data.isNonProfitSupported
? toTitleCase(data.isNonProfitSupported)
: YesNo.NO,
financiallySupportedHousingCoop: data.isHousingCooperativeSupported
? toTitleCase(data.isHousingCooperativeSupported)
: YesNo.NO
};

// Get greatest of multiple Units data
Expand Down Expand Up @@ -117,7 +131,7 @@ const controller = {
contactPhoneNumber: data.contactPhoneNumber,
contactName: `${data.contactFirstName} ${data.contactLastName}`,
contactApplicantRelationship: camelCaseToTitleCase(data.contactApplicantRelationship),
financiallySupported: Object.values(financiallySupportedValues).includes(true),
financiallySupported: Object.values(financiallySupportedValues).includes(YesNo.YES),
...financiallySupportedValues,
intakeStatus: toTitleCase(data.form.status),
locationPIDs: data.parcelID,
Expand All @@ -126,9 +140,9 @@ const controller = {
naturalDisaster: data.naturalDisasterInd,
queuePriority: parseInt(data.queuePriority),
singleFamilyUnits: maxUnits,
isRentalUnit: data.isRentalUnit
hasRentalUnits: data.isRentalUnit
? camelCaseToTitleCase(deDupeUnsure(data.isRentalUnit))
: RENTAL_STATUS_LIST.UNSURE,
: YesNoUnsure.UNSURE,
streetAddress: data.streetAddress,
submittedAt: data.form.createdAt,
submittedBy: data.form.username,
Expand All @@ -153,23 +167,123 @@ const controller = {
notStored.map((x) => x.permits?.map(async (y) => await permitService.createPermit(y)));
},

createEmptySubmission: async (req: Request, res: Response, next: NextFunction) => {
let testSubmissionId;
let submissionQuery;

// Testing for activityId collisions, which are truncated UUIDs
// If a collision is detected, generate new UUID and test again
do {
testSubmissionId = uuidv4();
submissionQuery = await submissionService.getSubmission(testSubmissionId.substring(0, 8).toUpperCase());
} while (submissionQuery);

createSubmission: async (req: Request, res: Response, next: NextFunction) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function createSubmission has 96 lines of code (exceeds 25 allowed). Consider refactoring.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function createSubmission has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.

try {
const newActivityId = await generateUniqueActivityId();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const submitter = (req.currentUser?.tokenPayload as any)?.idir_username;
const result = await submissionService.createEmptySubmission(testSubmissionId, submitter);
const data: any = req.body;

let applicant, basic, housing, location, permits;
let appliedPermits: Array<Permit> = [],
investigatePermits: Array<Permit> = [];

// Create applicant information
if (data.applicant) {
applicant = {
contactName: `${data.applicant.firstName} ${data.applicant.lastName}`,
contactPhoneNumber: data.applicant.phoneNumber,
contactEmail: data.applicant.email,
contactApplicantRelationship: data.applicant.relationshipToProject,
contactPreference: data.applicant.contactPreference
};
}

if (data.basic) {
basic = {
isDevelopedByCompanyOrOrg: data.basic.isDevelopedByCompanyOrOrg,
isDevelopedInBC: data.basic.isDevelopedInBC,
companyNameRegistered: data.basic.registeredName
};
}

if (data.housing) {
housing = {
projectName: data.housing.projectName,
projectDescription: data.housing.projectDescription,
//singleFamilySelected: true, // not necessary to save - check if singleFamilyUnits not null
//multiFamilySelected: true, // not necessary to save - check if multiFamilyUnits not null
singleFamilyUnits: data.housing.singleFamilyUnits,
multiFamilyUnits: data.housing.multiFamilyUnits,
//otherSelected: true, // not necessary to save - check if otherUnits not null
otherUnitsDescription: data.housing.otherUnitsDescription,
otherUnits: data.housing.otherUnits,
hasRentalUnits: data.housing.hasRentalUnits,
financiallySupportedBC: data.housing.financiallySupportedBC,
financiallySupportedIndigenous: data.housing.financiallySupportedIndigenous,
financiallySupportedNonProfit: data.housing.financiallySupportedNonProfit,
financiallySupportedHousingCoop: data.housing.financiallySupportedHousingCoop,
rentalUnits: data.housing.rentalUnits,
indigenousDescription: data.housing.indigenousDescription,
nonProfitDescription: data.housing.nonProfitDescription,
housingCoopDescription: data.housing.housingCoopDescription
};
}

if (data.location) {
location = {
naturalDisaster: data.location.naturalDisaster,
projectLocation: data.location.projectLocation,
locationPIDs: data.location.ltsaPIDLookup,
latitude: data.location.latitude,
longitude: data.location.longitude,
//addressSearch: 'Search address', // not necessary to save - client side search field
streetAddress: data.location.streetAddress,
locality: data.location.locality,
province: data.location.province
};
}

if (data.permits) {
permits = {
hasAppliedProvincialPermits: data.permits.hasAppliedProvincialPermits,
checkProvincialPermits: data.permits.checkProvincialPermits
};
}

if (data.appliedPermits && data.appliedPermits.length) {
appliedPermits = data.appliedPermits.map((x: Permit) => ({
permitTypeId: x.permitTypeId,
activityId: newActivityId,
trackingId: x.trackingId,
status: PERMIT_STATUS.APPLIED,
statusLastVerified: x.statusLastVerified
}));
}

if (data.investigatePermits && data.investigatePermits.length) {
investigatePermits = data.investigatePermits.flatMap((x: Permit) => ({
permitTypeId: x.permitTypeId,
activityId: newActivityId,
needed: PERMIT_NEEDED.UNDER_INVESTIGATION,
statusLastVerified: x.statusLastVerified
}));
}

// Put new submission together
const submission = {
...applicant,
...basic,
...housing,
...location,
...permits,
submissionId: uuidv4(),
activityId: newActivityId,
submittedAt: new Date().toISOString(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
submittedBy: (req.currentUser?.tokenPayload as any)?.idir_username,
intakeStatus: INTAKE_STATUS_LIST.SUBMITTED,
applicationStatus: APPLICATION_STATUS_LIST.NEW
};

// Create new submission
const result = await submissionService.createSubmission(submission);

// Create each permit
await Promise.all(appliedPermits.map(async (x: Permit) => await permitService.createPermit(x)));
await Promise.all(investigatePermits.map(async (x: Permit) => await permitService.createPermit(x)));

res.status(201).json({ activityId: result.activity_id });
res.status(201).json({ activityId: result.activityId });
} catch (e: unknown) {
next(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RENTAL_STATUS_LIST } from '../../components/constants';
import { YesNoUnsure } from '../../components/constants';
import type { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
return Promise.resolve().then(() =>
knex.schema.alterTable('submission', function (table) {
table.text('contact_preference');
table.text('contact_applicant_relationship');
table.text('is_rental_unit').notNullable().defaultTo(RENTAL_STATUS_LIST.UNSURE);
table.text('is_rental_unit').notNullable().defaultTo(YesNoUnsure.UNSURE);
table.text('project_description');
})
);
Expand Down
Loading
Loading