Skip to content

Commit

Permalink
Allow navigators to create empty submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
wilwong89 authored and kyle1morel committed Apr 19, 2024
1 parent f6b0eb9 commit 828fc28
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 5 deletions.
6 changes: 6 additions & 0 deletions app/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export const APPLICATION_STATUS_LIST = Object.freeze({
COMPLETED: 'Completed'
});

export const INTAKE_STATUS_LIST = Object.freeze({
SUBMITTED: 'Submitted',
ASSIGNED: 'Assigned',
COMPLETED: 'Completed'
});

export const RENTAL_STATUS_LIST = Object.freeze({
YES: 'Yes',
NO: 'No',
Expand Down
22 changes: 22 additions & 0 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ import type { NextFunction, Request, Response } from '../interfaces/IExpress';
import type { ChefsFormConfig, ChefsFormConfigData, Submission, ChefsSubmissionExport, Permit } from '../types';

const controller = {
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);

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

res.status(201).json({ activityId: result.activity_id });
} catch (e: unknown) {
next(e);
}
},

checkAndStoreNewSubmissions: async () => {
const cfg = config.get('server.chefs.forms') as ChefsFormConfig;

Expand Down
4 changes: 4 additions & 0 deletions app/src/routes/v1/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ router.get(
);

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

router.get(
'/:activityId',
submissionValidator.getSubmission,
Expand Down
36 changes: 35 additions & 1 deletion app/src/services/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import axios from 'axios';
import config from 'config';

import { APPLICATION_STATUS_LIST, Initiatives } from '../components/constants';
import { APPLICATION_STATUS_LIST, INTAKE_STATUS_LIST, Initiatives } from '../components/constants';
import { getChefsApiKey } from '../components/utils';
import prisma from '../db/dataConnection';
import { submission } from '../db/models';
Expand All @@ -26,6 +26,40 @@ function chefsAxios(formId: string, options: AxiosRequestConfig = {}): AxiosInst
}

const service = {
/**
* @function createEmptySubmission
* Creates a new minimal submission
* @returns {Promise<Partial<{activity_id: string}>>} The result of running the transaction
*/
createEmptySubmission: async (submissionId: string, createdBy: string): 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,
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
}
});
});
},

/**
* @function createSubmissionsFromExport
* Creates the given activities and submissions from exported CHEFS data
Expand Down
38 changes: 38 additions & 0 deletions app/tests/unit/controllers/submission.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ describe('checkAndStoreNewSubmissions', () => {
const formExportSpy = jest.spyOn(submissionService, 'getFormExport');
const searchSubmissionsSpy = jest.spyOn(submissionService, 'searchSubmissions');
const createSubmissionsFromExportSpy = jest.spyOn(submissionService, 'createSubmissionsFromExport');
const createEmptySubmissionSpy = jest.spyOn(submissionService, 'createEmptySubmission');
const getSubmissionSpy = jest.spyOn(submissionService, 'getSubmission');

it('creates submissions', async () => {
(config.get as jest.Mock).mockReturnValueOnce({
Expand Down Expand Up @@ -342,6 +344,42 @@ describe('checkAndStoreNewSubmissions', () => {
expect(createPermitSpy).toHaveBeenCalledTimes(1);
});

it('creates empty submission with no UUID collision', async () => {
const req = {
body: SUBMISSION_1,
currentUser: CURRENT_USER
};
const next = jest.fn();

createEmptySubmissionSpy.mockResolvedValue({ activity_id: '00000000' });
getSubmissionSpy.mockResolvedValue(null);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
await submissionController.createEmptySubmission(req as any, res as any, next);

expect(createEmptySubmissionSpy).toHaveBeenCalledTimes(1);
expect(getSubmissionSpy).toHaveBeenCalledTimes(1);
});

it('creates empty submission with a UUID collision', async () => {
const req = {
body: SUBMISSION_1,
currentUser: CURRENT_USER
};
const next = jest.fn();

createEmptySubmissionSpy.mockResolvedValue({ activity_id: '11112222' });
// Mocking two UUID collisions
getSubmissionSpy.mockResolvedValueOnce(SUBMISSION_1);
getSubmissionSpy.mockResolvedValueOnce(SUBMISSION_1);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
await submissionController.createEmptySubmission(req as any, res as any, next);

expect(createEmptySubmissionSpy).toHaveBeenCalledTimes(1);
expect(getSubmissionSpy).toHaveBeenCalledTimes(3);
});

it('creates permits', async () => {
(config.get as jest.Mock).mockReturnValueOnce({
form1: {
Expand Down
39 changes: 35 additions & 4 deletions frontend/src/components/submission/SubmissionList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Spinner } from '@/components/layout';
import { Checkbox, Column, DataTable, FilterMatchMode, InputText } from '@/lib/primevue';
import { Button, Checkbox, Column, DataTable, FilterMatchMode, InputText, useConfirm, useToast } from '@/lib/primevue';
import { submissionService } from '@/services';
import { RouteNames } from '@/utils/constants';
import { formatDate } from '@/utils/formatters';
Expand All @@ -17,15 +19,38 @@ type Props = {
const props = withDefaults(defineProps<Props>(), {});
// State
const selection: Ref<Submission | undefined> = ref(undefined);
// Actions
const confirmDialog = useConfirm();
const router = useRouter();
const toast = useToast();
const latLongFormat = (lat: number | null, long: number | null): string => {
if (!lat || !long) return '';
return `${lat}, ${long}`;
};
// State
const selection: Ref<Submission | undefined> = ref(undefined);
const handleCreateNewActivity = () => {
confirmDialog.require({
header: 'Confirm create submission',
message: 'Please confirm that you want to create a new submission',
accept: async () => {
try {
const res = await submissionService.createSubmission();
if (res?.data?.activityId) {
router.push({ name: RouteNames.SUBMISSION, query: { activityId: res.data.activityId } });
}
} catch (e) {
toast.error('Unable to create new submission');
}
},
acceptLabel: 'Confirm',
rejectLabel: 'Cancel'
});
};
// Datatable filter(s)
const filters = ref({
Expand Down Expand Up @@ -61,7 +86,13 @@ const filters = ref({
<Spinner />
</template>
<template #header>
<div class="flex justify-content-end">
<div class="flex justify-content-between">
<Button
label="Create new activity"
type="submit"
icon="pi pi-plus"
@click="handleCreateNewActivity"
/>
<span class="p-input-icon-left ml-4">
<i class="pi pi-search" />
<InputText
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/services/submissionService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { appAxios } from './interceptors';

export default {
/**
* @function createSubmission
* @returns {Promise} An axios response
*/
createSubmission() {
return appAxios().put('submission/create');
},

/**
* @function getFormExport
* @returns {Promise} An axios response
Expand Down

0 comments on commit 828fc28

Please sign in to comment.