Skip to content

Commit

Permalink
Merge pull request #77 from bcgov/feature/intake-form-draft
Browse files Browse the repository at this point in the history
Allow saving and updating of intake drafts
  • Loading branch information
kyle1morel authored May 21, 2024
2 parents 6310200 + 9a10337 commit 9b94430
Show file tree
Hide file tree
Showing 19 changed files with 691 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# These users will be the default owners for everything in the repo.
# Unless a later match takes precedence, the following users will be
# requested for review when someone opens a pull request.
* @kyle1morel @wilwong89
* @kyle1morel @wilwong89 @sanjaytkbabu
266 changes: 164 additions & 102 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { NIL, v4 as uuidv4 } from 'uuid';
import {
APPLICATION_STATUS_LIST,
INTAKE_STATUS_LIST,
Initiatives,
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 { activityService, submissionService, permitService, userService } from '../services';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';
import type { ChefsFormConfig, ChefsFormConfigData, Submission, ChefsSubmissionExport, Permit } from '../types';
Expand Down Expand Up @@ -167,114 +167,130 @@ const controller = {
notStored.map((x) => x.permits?.map(async (y) => await permitService.createPermit(y)));
},

createSubmission: async (req: Request, res: Response, next: NextFunction) => {
try {
const newActivityId = await generateUniqueActivityId();
generateSubmissionData: async (req: Request, intakeStatus: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = req.body;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = req.body;
const activityId = data.activityId ?? (await activityService.createActivity(Initiatives.HOUSING))?.activityId;

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

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 = {
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 === YesNo.YES,
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) => ({
permitId: x.permitId,
permitTypeId: x.permitTypeId,
activityId: activityId,
trackingId: x.trackingId,
status: PERMIT_STATUS.APPLIED,
statusLastVerified: x.statusLastVerified
}));
}

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

// Put new submission together
return {
submission: {
...applicant,
...basic,
...housing,
...location,
...permits,
submissionId: uuidv4(),
activityId: newActivityId,
submittedAt: new Date().toISOString(),
submissionId: data.submissionId ?? uuidv4(),
activityId: activityId,
submittedAt: data.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
};
intakeStatus: intakeStatus,
applicationStatus: data.applicationStatus ?? APPLICATION_STATUS_LIST.NEW
},
appliedPermits,
investigatePermits
};
},

createDraft: async (req: Request, res: Response, next: NextFunction) => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = req.body;

const { submission, appliedPermits, investigatePermits } = await controller.generateSubmissionData(
req,
data.submit ? INTAKE_STATUS_LIST.SUBMITTED : INTAKE_STATUS_LIST.DRAFT
);

// Create new submission
const result = await submissionService.createSubmission(submission);
Expand All @@ -283,7 +299,27 @@ const controller = {
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.activityId });
res.status(201).json({ activityId: result.activityId, submissionId: result.submissionId });
} catch (e: unknown) {
next(e);
}
},

createSubmission: async (req: Request, res: Response, next: NextFunction) => {
try {
const { submission, appliedPermits, investigatePermits } = await controller.generateSubmissionData(
req,
INTAKE_STATUS_LIST.SUBMITTED
);

// 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.activityId, submissionId: result.submissionId });
} catch (e: unknown) {
next(e);
}
Expand Down Expand Up @@ -324,6 +360,32 @@ const controller = {
}
},

updateDraft: async (req: Request, res: Response, next: NextFunction) => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = req.body;

const { submission, appliedPermits, investigatePermits } = await controller.generateSubmissionData(
req,
data.submit ? INTAKE_STATUS_LIST.SUBMITTED : INTAKE_STATUS_LIST.DRAFT
);

// Update submission
const result = await submissionService.updateSubmission(submission as Submission);

// Remove already existing permits for this activity
await permitService.deletePermitsByActivity(submission.activityId);

// 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(200).json({ activityId: result.activityId, submissionId: result.submissionId });
} catch (e: unknown) {
next(e);
}
},

updateSubmission: async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, NIL), NIL);
Expand Down
4 changes: 1 addition & 3 deletions app/src/db/models/permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ export default {
};
},

fromPrismaModel(input: PrismaGraphPermit | null): Permit | null {
if (!input) return null;

fromPrismaModel(input: PrismaGraphPermit): Permit {
return {
permitId: input.permit_id,
permitTypeId: input.permit_type_id,
Expand Down
10 changes: 10 additions & 0 deletions app/src/routes/v1/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ router.get(
}
);

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

// Submission update draft endpoint
router.put('/draft/:activityId', (req: Request, res: Response, next: NextFunction): void => {
submissionController.updateDraft(req, res, next);
});

// Submission create endpoint
router.put('/', submissionValidator.createSubmission, (req: Request, res: Response, next: NextFunction): void => {
submissionController.createSubmission(req, res, next);
Expand Down
26 changes: 26 additions & 0 deletions app/src/services/activity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import prisma from '../db/dataConnection';
import { activity } from '../db/models';
import { generateUniqueActivityId } from '../db/utils/utils';

const service = {
/**
* @function createActivity
* Create an activity for the given initiative with a unique identifier
* @param {string} initiative The initiative ID
* @returns {Promise<Activity | null>} The result of running the findFirst operation
*/
createActivity: async (initiative: string) => {
const response = await prisma.$transaction(async (trx) => {
const initiativeResult = await trx.initiative.findFirstOrThrow({
where: {
code: initiative
}
});

return await trx.activity.create({
data: {
activity_id: await generateUniqueActivityId(),
initiative_id: initiativeResult.initiative_id
}
});
});

return activity.fromPrismaModel(response);
},

/**
* @function getActivity
* Get an activity
Expand Down
Loading

0 comments on commit 9b94430

Please sign in to comment.