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 saving and updating of intake drafts #77

Merged
merged 3 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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

consider getting rid of comments if not required.

//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('/', (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
Loading