Skip to content

Commit

Permalink
WIP: Intake submit
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle1morel committed May 7, 2024
1 parent ea48354 commit 7024312
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 293 deletions.
9 changes: 8 additions & 1 deletion app/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ 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 PERMIT_NEEDED = Object.freeze({
YES: 'Yes',
UNDER_INVESTIGATION: 'Under investigation',
NO: 'No'
});

export const RENTAL_STATUS_LIST = Object.freeze({
Expand Down
10 changes: 10 additions & 0 deletions app/src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,13 @@ export function redactSecrets(data: { [key: string]: unknown }, fields: Array<st
export function toTitleCase(str: string): string {
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();
}
141 changes: 127 additions & 14 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import config from 'config';
import { NIL, v4 as uuidv4 } from 'uuid';

import { APPLICATION_STATUS_LIST, RENTAL_STATUS_LIST } from '../components/constants';
import { APPLICATION_STATUS_LIST, PERMIT_NEEDED, RENTAL_STATUS_LIST } from '../components/constants';
import { camelCaseToTitleCase, deDupeUnsure, getCurrentIdentity, isTruthy, 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 @@ -153,22 +154,134 @@ 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;
createSubmission: async (req: Request, res: Response, next: NextFunction) => {
try {
const newActivityId = await generateUniqueActivityId();

// 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);
const data: any = req.body;

try {
// 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);
let applicant, basic, housing, location, permits, appliedPermits, investigatePermits;

// 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,
isRentalUnit: 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 = {
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,
// archaeology: true,
// forests: true,
// lands: true,
// roadways: true,
// siteRemediation: true,
// subdividingLand: true,
// water: true,
// otherPermits: true,
checkProvincialPermits: data.permits.checkProvincialPermits
// heritageInspectionPermit: true,
// investigationPermit: true,
// siteAlterationPermit: true,
// occupantLicenceToCut: true,
// privateTimberMarks: true,
// crownLandTenure: true,
// highwayUsePermit: true,
// otherPermitsNames: 'other permit names',
// waterLicence: true,
// shortTermUseApproval: true,
// riparianAreasProtection: true,
// changeApprovalInAndAboutStream: true,
// notificationInAndAboutStream: true
};
}

if (data.appliedPermits && data.appliedPermits.length) {
appliedPermits = data.appliedPermits.map((x: Partial<Permit>) => ({
permitId: uuidv4(),
permitTypeId: x.permitTypeId,
activityId: newActivityId,
trackingId: x.trackingId,
status: x.status,
statusLastVerified: '2024-05-03T07:00:00.000Z' // What even is this?
}));
}

if (data.investigatePermits && data.investigatePermits.length) {
investigatePermits = data.investigatePermits.map((x: Partial<Permit>) => ({
permitId: uuidv4(),
permitTypeId: x.permitTypeId,
activityId: newActivityId,
needed: PERMIT_NEEDED.UNDER_INVESTIGATION,
statusLastVerified: '2024-05-03T07:00:00.000Z' // What even is this?
}));
}

// 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
};

// Create new submission
const result = await submissionService.createSubmission(submission);
res.status(201).json({ activityId: result.activity_id });
} catch (e: unknown) {
next(e);
Expand Down
44 changes: 44 additions & 0 deletions app/src/db/migrations/20240506000000_004-shas-intake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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('is_developed_by_company_or_org');
table.text('is_developed_in_bc');
table.text('multi_family_units');
table.text('other_units');
table.text('other_units_description');
table.text('rental_units');
table.text('project_location');
table.text('locality');
table.text('province');
table.text('has_applied_provincial_permits');
table.text('check_provincial_permits');
table.text('indigenous_description');
table.text('non_profit_description');
table.text('housing_coop_description');
})
);
}

export async function down(knex: Knex): Promise<void> {
return Promise.resolve().then(() =>
knex.schema.alterTable('submission', function (table) {
table.dropColumn('housing_coop_description');
table.dropColumn('non_profit_description');
table.dropColumn('indigenous_description');
table.dropColumn('check_provincial_permits');
table.dropColumn('has_applied_provincial_permits');
table.dropColumn('province');
table.dropColumn('locality');
table.dropColumn('project_location');
table.dropColumn('rental_units');
table.dropColumn('other_units_description');
table.dropColumn('other_units');
table.dropColumn('multi_family_units');
table.dropColumn('is_developed_in_bc');
table.dropColumn('is_developed_by_company_or_org');
})
);
}

1 change: 1 addition & 0 deletions app/src/db/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as activity } from './activity';
export { default as document } from './document';
export { default as identity_provider } from './identity_provider';
export { default as note } from './note';
Expand Down
20 changes: 20 additions & 0 deletions app/src/db/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { v4 as uuidv4 } from 'uuid';

import { activityService } from '../../services';
import { uuidToActivityId } from '../../components/utils';
/**
* @function generateUniqueActivityId
* Generate a new activityId, which are truncated UUIDs
* If a collision is detected, generate new UUID and test again
* @returns {Promise<string>} A string in title case
*/
export async function generateUniqueActivityId() {
let id, queryResult;

do {
id = uuidToActivityId(uuidv4());
queryResult = await activityService.getActivity(id);
} while (queryResult);

return id;
}
6 changes: 3 additions & 3 deletions app/src/routes/v1/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ router.get(
}
);

// Submission endpoint
router.put('/create', (req: Request, res: Response, next: NextFunction): void => {
submissionController.createEmptySubmission(req, res, next);
// Submission create endpoint
router.put('/', (req: Request, res: Response, next: NextFunction): void => {
submissionController.createSubmission(req, res, next);
});

router.get(
Expand Down
22 changes: 22 additions & 0 deletions app/src/services/activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import prisma from '../db/dataConnection';
import { activity } from '../db/models';

const service = {
/**
* @function getActivity
* Get an activity
* @param {string} activityId Unique activity ID
* @returns {Promise<Activity | null>} The result of running the findFirst operation
*/
getActivity: async (activityId: string) => {
const response = await prisma.activity.findFirst({
where: {
activity_id: activityId
}
});

return activity.fromPrismaModel(response);
}
};

export default service;
1 change: 1 addition & 0 deletions app/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as activityService } from './activity';
export { default as comsService } from './coms';
export { default as documentService } from './document';
export { default as emailService } from './email';
Expand Down
18 changes: 5 additions & 13 deletions app/src/services/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,27 @@ function chefsAxios(formId: string, options: AxiosRequestConfig = {}): AxiosInst

const service = {
/**
* @function createEmptySubmission
* Creates a new minimal submission
* @function createSubmission
* Creates a new submission
* @returns {Promise<Partial<{activity_id: string}>>} The result of running the transaction
*/
createEmptySubmission: async (submissionId: string, createdBy: string): Promise<Partial<{ activity_id: string }>> => {
createSubmission: async (data: Partial<Submission>): Promise<Partial<{ activity_id: string }>> => {
return await prisma.$transaction(async (trx) => {
const initiative = await trx.initiative.findFirstOrThrow({
where: {
code: Initiatives.HOUSING
}
});

const activityId = submissionId.substring(0, 8).toUpperCase() as string;
await trx.activity.create({
data: {
activity_id: activityId,
activity_id: data.activityId as string,
initiative_id: initiative.initiative_id
}
});

return await trx.submission.create({
data: {
submission_id: submissionId,
activity_id: activityId,
application_status: APPLICATION_STATUS_LIST.NEW,
intake_status: INTAKE_STATUS_LIST.SUBMITTED,
submitted_at: new Date(Date.now()),
submitted_by: createdBy
}
data: submission.toPrismaModel(data as Submission)
});
});
},
Expand Down
1 change: 1 addition & 0 deletions app/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { Activity } from './Activity';
export type { BringForward } from './BringForward';
export type { ChefsFormConfig, ChefsFormConfigData } from './ChefsFormConfig';
export type { ChefsSubmissionExport } from './ChefsSubmissionExport';
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/components/form/StepperNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ const props = withDefaults(defineProps<Props>(), {
:disabled="props.prevDisabled"
@click="props.prevCallback()"
/>
<Button
class="p-button-sm"
outlined
label="Save draft"
type="submit"
:disabled="!editable"
/>
<slot name="content" />
<Button
class="px-4 py-1"
outlined
Expand Down
Loading

0 comments on commit 7024312

Please sign in to comment.