Skip to content

Commit 5e6cc85

Browse files
committed
chore: backport form/validation changes to housing
1 parent a8d475b commit 5e6cc85

15 files changed

Lines changed: 280 additions & 344 deletions

File tree

app/src/controllers/housingProject.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import config from 'config';
2+
import { Prisma } from '@prisma/client';
23
import { v4 as uuidv4 } from 'uuid';
34

45
import { transactionWrapper } from '../db/utils/transactionWrapper.ts';
@@ -487,18 +488,25 @@ export const upsertHousingProjectDraftController = async (req: Request<never, ne
487488
res.status(update ? 200 : 201).json(response);
488489
};
489490

490-
export const updateHousingProjectController = async (req: Request<never, never, HousingProject>, res: Response) => {
491+
export const updateHousingProjectController = async (
492+
req: Request<{ housingProjectId: string }, never, Omit<Prisma.housing_projectUpdateInput, 'housingProjectId'>>,
493+
res: Response
494+
) => {
491495
const response = await transactionWrapper<HousingProject>(async (tx: PrismaTransactionClient) => {
492-
return await updateHousingProject(tx, {
493-
...req.body,
494-
financiallySupported: [
495-
req.body.financiallySupportedBc,
496-
req.body.financiallySupportedIndigenous,
497-
req.body.financiallySupportedNonProfit,
498-
req.body.financiallySupportedHousingCoop
499-
].includes(BasicResponse.YES),
500-
...generateUpdateStamps(req.currentContext)
501-
});
496+
return await updateHousingProject(
497+
tx,
498+
{
499+
...req.body,
500+
financiallySupported: [
501+
req.body.financiallySupportedBc,
502+
req.body.financiallySupportedIndigenous,
503+
req.body.financiallySupportedNonProfit,
504+
req.body.financiallySupportedHousingCoop
505+
].includes(BasicResponse.YES),
506+
...generateUpdateStamps(req.currentContext)
507+
},
508+
req.params.housingProjectId
509+
);
502510
});
503511

504512
res.status(200).json(response);

app/src/routes/v1/housingProject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ router.get(
113113
);
114114

115115
/** Updates a housing project*/
116-
router.put(
116+
router.patch(
117117
'/:housingProjectId',
118118
hasAuthorization(Resource.HOUSING_PROJECT, Action.UPDATE),
119119
hasAccess('housingProjectId'),

app/src/services/housingProject.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,18 @@ export const searchHousingProjects = async (
205205
* Updates a specific housing project
206206
* @param tx Prisma transaction client
207207
* @param data Housing project to update
208+
* @param housingProjectId ID of the project to update
208209
* @returns A Promise that resolves to the updated housing project
209210
*/
210211
export const updateHousingProject = async (
211212
tx: PrismaTransactionClient,
212-
data: HousingProjectBase
213+
data: Omit<Prisma.housing_projectUpdateInput, 'housingProjectId'>,
214+
housingProjectId: string
213215
): Promise<HousingProject> => {
214216
const result = await tx.housing_project.update({
215-
data: {
216-
...data,
217-
geoJson: jsonToPrismaInputJson(data.geoJson)
218-
},
217+
data,
219218
where: {
220-
housingProjectId: data.housingProjectId
219+
housingProjectId
221220
},
222221
include: {
223222
activity: {

app/src/validators/housingProject.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,11 @@ const schema = {
9191
},
9292
updateHousingProject: {
9393
body: Joi.object({
94-
housingProjectId: uuidv4.required(),
95-
activityId: activityId.required(),
9694
consentToFeedback: Joi.boolean(),
9795
queuePriority: Joi.number().required().integer().min(0).max(3),
9896
submissionType: Joi.string()
9997
.required()
10098
.valid(...SUBMISSION_TYPE_LIST),
101-
submittedAt: Joi.string().required(),
10299
companyNameRegistered: Joi.string().allow(null),
103100
companyIdRegistered: Joi.string().allow(null),
104101
projectName: Joi.string().required(),

frontend/src/components/form/section/FeedbackConsentSection.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ useFormErrorWatcher(formRef, 'FeedbackConsentSection', tab);
3838
</h4>
3939
<Select
4040
class="col-span-3"
41-
name="consentToFeedback"
41+
name="consent.consentToFeedback"
4242
:label="t('i.housing.project.projectForm.researchOptin')"
4343
:disabled="!getEditable"
4444
:options="YES_NO_LIST"

frontend/src/components/general/project/ProjectFormNavigator.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,7 @@ const onSubmit = async (formValues: GenericObject) => {
312312
astNotes: values.astNotes.notes,
313313
314314
// Submission State
315-
assignedUserId: values.submissionState.assignedUser
316-
? (values.submissionState.assignedUser as User).userId
317-
: undefined,
315+
assignedUserId: values.submissionState.assignedUser ? (values.submissionState.assignedUser as User).userId : null,
318316
region: values.submissionState.region as Region,
319317
area: values.submissionState.area as Area,
320318
applicationStatus: values.submissionState.applicationStatus,

frontend/src/components/housing/project/ProjectFormNavigator.vue

Lines changed: 104 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import RelatedEnquiriesSection from '@/components/form/section/RelatedEnquiriesS
2323
import SubmissionStateSection from '@/components/form/section/SubmissionStateSection.vue';
2424
import { Button, Message, useConfirm, useToast } from '@/lib/primevue';
2525
import { atsService, housingProjectService, mapService, userService } from '@/services';
26-
import { useProjectStore } from '@/store';
26+
import { useAppStore, useFormStore, useProjectStore } from '@/store';
2727
import { ATS_ENQUIRY_TYPE_CODE_PROJECT_INTAKE_SUFFIX, ATS_MANAGING_REGION } from '@/utils/constants/projectCommon';
2828
import { ATSCreateTypes, BasicResponse, GroupName, Initiative } from '@/utils/enums/application';
29-
import { ActivityContactRole, ApplicationStatus } from '@/utils/enums/projectCommon';
29+
import { ActivityContactRole, ApplicationStatus, FormState, FormType } from '@/utils/enums/projectCommon';
3030
import { formatDate } from '@/utils/formatters';
31-
import { omit, scrollToFirstError, setEmptyStringsToNull, toTitleCase } from '@/utils/utils';
31+
import { scrollToFirstError, setEmptyStringsToNull, toTitleCase } from '@/utils/utils';
3232
3333
import type { Ref } from 'vue';
3434
import type {
@@ -37,7 +37,8 @@ import type {
3737
ATSEnquiryResource,
3838
Contact,
3939
DeepPartial,
40-
HousingProject
40+
HousingProject,
41+
User
4142
} from '@/types';
4243
import type { FormSchemaType } from '@/validators/housing/projectFormNavigatorSchema';
4344
@@ -56,6 +57,7 @@ const confirm = useConfirm();
5657
const toast = useToast();
5758
5859
// Store
60+
const { getInitiative } = storeToRefs(useAppStore());
5961
const projectStore = useProjectStore();
6062
const { getActivityContacts } = storeToRefs(projectStore);
6163
@@ -156,12 +158,11 @@ async function handleAtsCreate(values: GenericObject) {
156158
}
157159
158160
async function initializeFormValues(project: HousingProject): Promise<DeepPartial<FormSchemaType>> {
159-
let assigneeOptions = [];
161+
let assigneeOptions: User[] = [];
160162
if (project.assignedUserId)
161163
assigneeOptions = (await userService.searchUsers({ userId: [project.assignedUserId] })).data;
162164
163165
return {
164-
consentToFeedback: project.consentToFeedback ? BasicResponse.YES : BasicResponse.NO,
165166
contact: {
166167
contactId: primaryContact.value?.contactId,
167168
firstName: primaryContact.value?.firstName,
@@ -194,27 +195,27 @@ async function initializeFormValues(project: HousingProject): Promise<DeepPartia
194195
geomarkUrl: project.geomarkUrl,
195196
naturalDisaster: project.naturalDisaster ? BasicResponse.YES : BasicResponse.NO
196197
},
197-
locationPidsAuto: locationPidsAuto.value,
198-
project: {
198+
locationPids: { auto: locationPidsAuto.value },
199+
companyProjectName: {
199200
companyIdRegistered: project.companyIdRegistered,
200201
companyNameRegistered: project.companyNameRegistered,
201202
projectName: project.projectName
202203
},
203204
204205
// Additional Info
205-
projectDescription: project.projectDescription,
206+
projectDescription: { description: project.projectDescription },
206207
207208
// Location
208-
projectLocationDescription: project.projectLocationDescription,
209+
locationDescription: { description: project.projectLocationDescription },
209210
210211
// Automated Status Tool Notes
211-
astNotes: project.astNotes,
212+
astNotes: { notes: project.astNotes },
212213
213214
// Submission state
214215
submissionState: {
215216
queuePriority: project.queuePriority,
216217
submissionType: project.submissionType,
217-
assignedUser: assigneeOptions[0] ?? null,
218+
assignedUser: assigneeOptions[0]?.fullName ?? null,
218219
applicationStatus: project.applicationStatus
219220
},
220221
@@ -228,16 +229,25 @@ async function initializeFormValues(project: HousingProject): Promise<DeepPartia
228229
},
229230
230231
// ATS link
231-
atsClientId: project.atsClientId,
232-
atsEnquiryId: project.atsEnquiryId,
232+
atsInfo: {
233+
atsClientId: project.atsClientId,
234+
atsEnquiryId: project.atsEnquiryId
235+
},
236+
237+
// Related enquiries
238+
relatedEnquiries: { csv: project.relatedEnquiries },
233239
234240
// Updates
235-
aaiUpdated: project.aaiUpdated,
236-
addedToAts: project.addedToAts,
237-
ltsaCompleted: project.ltsaCompleted,
238-
bcOnlineCompleted: project.bcOnlineCompleted,
239-
submittedAt: new Date(project.submittedAt),
240-
relatedEnquiries: project.relatedEnquiries
241+
projectAreasUpdated: {
242+
aaiUpdated: project.aaiUpdated,
243+
addedToAts: project.addedToAts,
244+
ltsaCompleted: project.ltsaCompleted,
245+
bcOnlineCompleted: project.bcOnlineCompleted
246+
},
247+
248+
consent: {
249+
consentToFeedback: project.consentToFeedback ? BasicResponse.YES : BasicResponse.NO
250+
}
241251
};
242252
}
243253
@@ -280,50 +290,88 @@ function onReOpen() {
280290
});
281291
}
282292
283-
const onSubmit = async (values: GenericObject) => {
293+
const onSubmit = async (formValues: GenericObject) => {
284294
try {
295+
// vee-validate doesn't get transformed data from yup so
296+
// manually run the form values through it here
297+
const values: FormSchemaType = projectFormNavigatorSchema.cast(formValues);
298+
285299
await handleAtsCreate(values);
286300
287-
// Generate final submission object
288-
const dataOmitted = omit(
289-
setEmptyStringsToNull({
290-
...values.project,
291-
...values.units,
292-
...values.location,
293-
...values.finance,
294-
...values.submissionState,
295-
activityId: project.activityId,
296-
housingProjectId: project.housingProjectId,
297-
projectDescription: values.projectDescription,
298-
projectLocationDescription: values.projectLocationDescription,
299-
astNotes: values.astNotes,
300-
atsClientId: Number.parseInt(values.atsClientId) || '',
301-
atsEnquiryId: Number.parseInt(values.atsEnquiryId) || '',
302-
aaiUpdated: values.aaiUpdated,
303-
addedToAts: values.addedToAts,
304-
ltsaCompleted: values.ltsaCompleted,
305-
bcOnlineCompleted: values.bcOnlineCompleted,
306-
companyIdRegistered: values.project?.companyIdRegistered ?? null,
307-
submittedAt: values.submittedAt,
308-
consentToFeedback: values.consentToFeedback === BasicResponse.YES,
309-
naturalDisaster: values.location.naturalDisaster === BasicResponse.YES,
310-
assignedUserId: values.submissionState.assignedUser?.userId ?? undefined
311-
}),
312-
['contact', 'assignedUser', 'isDevelopedInBc', 'submissionState', 'locationAddress', 'relatedEnquiries']
313-
);
301+
// Generate final payload
302+
// TODO: Create a type using Pick instead of Partial?
303+
const payload: Partial<HousingProject> = {
304+
// Company and Project Information
305+
projectName: values.companyProjectName.projectName,
306+
companyNameRegistered: values.companyProjectName.companyNameRegistered,
307+
companyIdRegistered: values.companyProjectName.companyIdRegistered,
308+
309+
// Residential units
310+
singleFamilyUnits: values.units.singleFamilyUnits,
311+
multiFamilyUnits: values.units.multiFamilyUnits,
312+
hasRentalUnits: values.units.hasRentalUnits,
313+
rentalUnits: values.units.rentalUnits,
314+
otherUnits: values.units.otherUnits,
315+
otherUnitsDescription: values.units.otherUnitsDescription,
316+
317+
// Financially supported
318+
financiallySupportedBc: values.finance.financiallySupportedBc,
319+
financiallySupportedIndigenous: values.finance.financiallySupportedIndigenous,
320+
indigenousDescription: values.finance.indigenousDescription,
321+
financiallySupportedNonProfit: values.finance.financiallySupportedNonProfit,
322+
nonProfitDescription: values.finance.nonProfitDescription,
323+
financiallySupportedHousingCoop: values.finance.financiallySupportedHousingCoop,
324+
housingCoopDescription: values.finance.housingCoopDescription,
325+
326+
// Location
327+
locality: values.location.locality,
328+
province: values.location.province,
329+
locationPids: values.location.locationPids,
330+
latitude: values.location.latitude,
331+
longitude: values.location.longitude,
332+
streetAddress: values.location.streetAddress,
333+
geomarkUrl: values.location.geomarkUrl,
334+
naturalDisaster: values.location.naturalDisaster === BasicResponse.YES,
335+
336+
// Additional Location Information
337+
projectLocationDescription: values.locationDescription.description,
338+
339+
// Additional Project Information
340+
projectDescription: values.projectDescription.description,
341+
342+
// AST Notes
343+
astNotes: values.astNotes.notes,
344+
345+
// Submission State
346+
assignedUserId: values.submissionState.assignedUser ? (values.submissionState.assignedUser as User).userId : null,
347+
applicationStatus: values.submissionState.applicationStatus,
348+
submissionType: values.submissionState.submissionType,
349+
queuePriority: values.submissionState.queuePriority,
350+
351+
// ATS
352+
atsClientId: values.atsInfo.atsClientId,
353+
atsEnquiryId: values.atsInfo.atsEnquiryId,
354+
355+
// Updates
356+
addedToAts: values.projectAreasUpdated.addedToAts,
357+
ltsaCompleted: values.projectAreasUpdated.ltsaCompleted,
358+
bcOnlineCompleted: values.projectAreasUpdated.bcOnlineCompleted,
359+
aaiUpdated: values.projectAreasUpdated.aaiUpdated,
360+
361+
// Consent
362+
consentToFeedback: values.consent.consentToFeedback === BasicResponse.YES
363+
};
314364
315365
// Update project
316-
const result = await housingProjectService.updateProject(project.housingProjectId, dataOmitted);
366+
const result = await housingProjectService.updateProject(project.housingProjectId, payload);
317367
projectStore.setProject(result.data);
318368
319369
// Wait a tick for store to propagate
320370
await nextTick();
321371
322372
// Reinitialize the form
323373
formRef.value?.resetForm({
324-
values: {
325-
...initializeFormValues(result.data)
326-
}
374+
values: await initializeFormValues(result.data)
327375
});
328376
329377
toast.success(t('i.common.form.savedMessage'));
@@ -332,7 +380,7 @@ const onSubmit = async (values: GenericObject) => {
332380
}
333381
};
334382
335-
const projectFormNavigatorSchema = createProjectFormNavigatorSchema();
383+
const projectFormNavigatorSchema = createProjectFormNavigatorSchema({ initiative: getInitiative.value, t });
336384
337385
// Set basic info, clear it if no contact is provided
338386
function setBasicInfo(contact?: Contact) {
@@ -360,6 +408,9 @@ watch(primaryContact, (newContact, oldContact) => {
360408
});
361409
362410
onBeforeMount(async () => {
411+
useFormStore().setFormType(FormType.NAVIGATOR);
412+
useFormStore().setFormState(FormState.UNLOCKED);
413+
363414
locationPidsAuto.value = (await mapService.getPIDs(project.housingProjectId)).data;
364415
365416
// Default form values

frontend/src/interfaces/IProject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface IProject extends IStamps {
88
projectId: string;
99
activityId: string;
1010
submittedAt: string;
11-
assignedUserId?: string;
11+
assignedUserId?: string | null;
1212
applicationStatus: ApplicationStatus;
1313

1414
companyIdRegistered?: string | null;

frontend/src/locales/en-CA.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@
4545
"activityType": "Activity type",
4646
"companyNameRegistered": "Company name",
4747
"projectName": "Project name",
48+
"projectNumber": "Project number",
4849
"isRegisteredInBc": "Is Registered in BC"
4950
},
51+
"consent": {
52+
"consentToFeedback": "Consent to feedback"
53+
},
5054
"contactCardNavForm": {
5155
"contactApplicantRelationship": "Relationship to project",
5256
"contactEmail": "Contact email",
@@ -498,7 +502,6 @@
498502
"contactPhoneNumber": "Contact phone number",
499503
"contactApplicantRelationship": "Relationship to project",
500504
"contactPreference": "Preferred contact method",
501-
"consentToFeedback": "Consent to feedback",
502505
"financeFinanciallySupportedBc": "BC Housing",
503506
"financeFinanciallySupportedIndigenous": "Indigenous Housing Provider",
504507
"financeIndigenousDescription": "Name of Indigenous Housing Provider",

0 commit comments

Comments
 (0)