diff --git a/app/config/default.json b/app/config/default.json index 92c8a1342..a6e010c90 100644 --- a/app/config/default.json +++ b/app/config/default.json @@ -6,7 +6,7 @@ "apiPath": "/api/v1", "bodyLimit": "30mb", "db": { - "database": "pns", + "database": "pcns", "host": "localhost", "port": "5432", "poolMin": "2", diff --git a/app/package.json b/app/package.json index e4d5aeb82..df5b3c699 100644 --- a/app/package.json +++ b/app/package.json @@ -37,7 +37,9 @@ "migrate:up": "knex migrate:up", "postmigrate:up": "npm run prisma:sync", "seed": "knex seed:run", - "prisma:sync": "prisma db pull" + "prisma:sync": "prisma db pull", + "postprisma:sync": "npm run prisma:generate", + "prisma:generate": "prisma generate" }, "dependencies": { "@prisma/client": "^5.7.0", diff --git a/app/src/components/utils.ts b/app/src/components/utils.ts index 7e5023a0f..4c12b2ed0 100644 --- a/app/src/components/utils.ts +++ b/app/src/components/utils.ts @@ -102,3 +102,25 @@ export function redactSecrets(data: { [key: string]: unknown }, fields: Array { try { const cfg = config.get('server.chefs.forms') as ChefsFormConfig; - let formData = new Array(); + let formData = new Array(); // Get a JSON export of all form data merged into a single array await Promise.all( Object.values(cfg).map(async (x: ChefsFormConfigData) => { const data = await chefsService.getFormExport(x.id); - data.forEach((d: ChefsSubmissionDataSource) => (d.form.id = x.id)); + data.forEach((d: ChefsSubmissionFormExport) => (d.form.id = x.id)); formData = formData.concat(data); }) ); @@ -29,7 +29,7 @@ const controller = { * IDIR users should be able to see all submissions * BCeID/Business should only see their own submissions */ - const filterData = (data: Array) => { + const filterData = (data: Array) => { const tokenPayload = req.currentUser?.tokenPayload as JwtPayload; const filterToUser = tokenPayload && tokenPayload.identity_provider !== IdentityProvider.IDIR; @@ -52,16 +52,7 @@ const controller = { getSubmission: async (req: Request, res: Response, next: NextFunction) => { try { - const response = await chefsService.getSubmission(req.query.formId as string, req.params.formSubmissionId); - res.status(200).send(response); - } catch (e: unknown) { - next(e); - } - }, - - getSubmissionStatus: async (req: Request, res: Response, next: NextFunction) => { - try { - const response = await chefsService.getSubmissionStatus(req.query.formId as string, req.params.formSubmissionId); + const response = await chefsService.getSubmission(req.query.formId as string, req.params.submissionId); res.status(200).send(response); } catch (e: unknown) { next(e); @@ -70,11 +61,7 @@ const controller = { updateSubmission: async (req: Request, res: Response, next: NextFunction) => { try { - const response = await chefsService.updateSubmission( - req.query.formId as string, - req.params.formSubmissionId, - req.body - ); + const response = await chefsService.updateSubmission(req.body); res.status(200).send(response); } catch (e: unknown) { next(e); diff --git a/app/src/db/migrations/20231212000000_init.ts b/app/src/db/migrations/20231212000000_init.ts index 81d9ee1f6..a0660b631 100644 --- a/app/src/db/migrations/20231212000000_init.ts +++ b/app/src/db/migrations/20231212000000_init.ts @@ -40,13 +40,7 @@ export async function up(knex: Knex): Promise { .then(() => knex.schema.createTable('submission', (table) => { table.uuid('submissionId').primary(); - table - .uuid('assigneeUserId') - .references('userId') - .inTable('user') - .notNullable() - .onUpdate('CASCADE') - .onDelete('CASCADE'); + table.uuid('assigneeToUserId').references('userId').inTable('user').onUpdate('CASCADE').onDelete('CASCADE'); table.string('confirmationId', 255); table.string('contactEmail', 255); table.string('contactPhoneNumber', 255); @@ -64,8 +58,8 @@ export async function up(knex: Knex): Promise { table.string('relatedPermits', 255); table.boolean('updatedAai'); table.string('waitingOn', 255); - table.timestamp('shasCreatedAt', { useTz: true }); - table.string('shasCreatedBy', 255); + table.timestamp('submittedAt', { useTz: true }); + table.string('submittedBy', 255); table.timestamp('bringForwardDate', { useTz: true }); table.string('notes', 2047); stamps(knex, table); diff --git a/app/src/db/prisma/schema.prisma b/app/src/db/prisma/schema.prisma index 93f259a56..7d875e8d5 100644 --- a/app/src/db/prisma/schema.prisma +++ b/app/src/db/prisma/schema.prisma @@ -31,7 +31,7 @@ model knex_migrations_lock { model submission { submissionId String @id @db.Uuid - assigneeUserId String @db.Uuid + assigneeToUserId String? @db.Uuid confirmationId String? @db.VarChar(255) contactEmail String? @db.VarChar(255) contactPhoneNumber String? @db.VarChar(255) @@ -49,15 +49,15 @@ model submission { relatedPermits String? @db.VarChar(255) updatedAai Boolean? waitingOn String? @db.VarChar(255) - shasCreatedAt DateTime? @db.Timestamptz(6) - shasCreatedBy String? @db.VarChar(255) + submittedAt DateTime? @db.Timestamptz(6) + submittedBy String? @db.VarChar(255) bringForwardDate DateTime? @db.Timestamptz(6) notes String? @db.VarChar(2047) createdBy String? @default("00000000-0000-0000-0000-000000000000") @db.VarChar(255) createdAt DateTime? @default(now()) @db.Timestamptz(6) updatedBy String? @db.VarChar(255) updatedAt DateTime? @db.Timestamptz(6) - user user @relation(fields: [assigneeUserId], references: [userId], onDelete: Cascade, map: "submission_assigneeuserid_foreign") + user user? @relation(fields: [assigneeToUserId], references: [userId], onDelete: Cascade, map: "submission_assigneetouserid_foreign") } model user { diff --git a/app/src/interfaces/IStamps.ts b/app/src/interfaces/IStamps.ts new file mode 100644 index 000000000..56e5b8f3b --- /dev/null +++ b/app/src/interfaces/IStamps.ts @@ -0,0 +1,6 @@ +export interface IStamps { + createdBy?: string; + createdAt?: string; + updatedBy?: string; + updatedAt?: string; +} diff --git a/app/src/routes/v1/chefs.ts b/app/src/routes/v1/chefs.ts index a90331a93..adbcc5a7f 100644 --- a/app/src/routes/v1/chefs.ts +++ b/app/src/routes/v1/chefs.ts @@ -15,7 +15,7 @@ router.get('/export', (req: Request, res: Response, next: NextFunction): void => // Submission endpoint router.get( - '/submission/:formSubmissionId', + '/submission/:submissionId', requireChefsFormConfigData, (req: Request, res: Response, next: NextFunction): void => { chefsController.getSubmission(req, res, next); @@ -23,21 +23,8 @@ router.get( ); // Submission endpoint -router.put( - '/submission/:formSubmissionId', - requireChefsFormConfigData, - (req: Request, res: Response, next: NextFunction): void => { - chefsController.updateSubmission(req, res, next); - } -); - -// Submission status endpoint -router.get( - '/submission/:formSubmissionId/status', - requireChefsFormConfigData, - (req: Request, res: Response, next: NextFunction): void => { - chefsController.getSubmissionStatus(req, res, next); - } -); +router.put('/submission/:submissionId', (req: Request, res: Response, next: NextFunction): void => { + chefsController.updateSubmission(req, res, next); +}); export default router; diff --git a/app/src/services/chefs.ts b/app/src/services/chefs.ts index bd153c76c..a243cc76b 100644 --- a/app/src/services/chefs.ts +++ b/app/src/services/chefs.ts @@ -2,10 +2,12 @@ import axios from 'axios'; import config from 'config'; import { PrismaClient } from '@prisma/client'; +import { NIL } from 'uuid'; -import { getChefsApiKey } from '../components/utils'; +import { fromYrn, getChefsApiKey, toYrn } from '../components/utils'; import type { AxiosInstance, AxiosRequestConfig } from 'axios'; +import type { ChefsSubmissionForm } from '../types/ChefsSubmissionForm'; const prisma = new PrismaClient(); @@ -36,28 +38,74 @@ const service = { } }, - getSubmission: async (formId: string, formSubmissionId: string) => { + getSubmission: async (formId: string, submissionId: string) => { try { - const response = await chefsAxios(formId).get(`submissions/${formSubmissionId}`); - return response.data; - } catch (e: unknown) { - throw e; - } - }, + // Try to pull data from our DB + let result = await prisma.submission.findUnique({ + where: { + submissionId: submissionId + } + }); - getSubmissionStatus: async (formId: string, formSubmissionId: string) => { - try { - const response = await chefsAxios(formId).get(`submissions/${formSubmissionId}/status`); - return response.data; + // Pull submission data from CHEFS and store to our DB if it doesn't exist + if (!result) { + const response = (await chefsAxios(formId).get(`submissions/${submissionId}`)).data; + const status = (await chefsAxios(formId).get(`submissions/${submissionId}/status`)).data; + + // TODO: Assigned to correct user + result = await prisma.submission.create({ + data: { + submissionId: response.submission.id, + assigneeToUserId: NIL, //status[0].assignedToUserId, + confirmationId: response.submission.confirmationId, + contactEmail: response.submission.submission.data.contactEmail, + contactPhoneNumber: response.submission.submission.data.contactPhoneNumber, + contactFirstName: response.submission.submission.data.contactFirstName, + contactLastName: response.submission.submission.data.contactLastName, + intakeStatus: status[0].code, + projectName: response.submission.submission.data.projectName, + queuePriority: response.submission.submission.data.queuePriority, + singleFamilyUnits: response.submission.submission.data.singleFamilyUnits, + streetAddress: response.submission.submission.data.streetAddress, + atsClientNumber: null, + addedToATS: null, + financiallySupported: null, + applicationStatus: null, + relatedPermits: null, + updatedAai: null, + waitingOn: null, + submittedAt: response.submission.createdAt, + submittedBy: response.submission.createdBy, + bringForwardDate: null, + notes: null + } + }); + } + + return { + ...result, + addedToATS: toYrn(result.addedToATS), + financiallySupported: toYrn(result.financiallySupported), + updatedAai: toYrn(result.updatedAai) + }; } catch (e: unknown) { throw e; } }, - updateSubmission: async (formId: string, formSubmissionId: string, data: any) => { + updateSubmission: async (data: ChefsSubmissionForm) => { try { - console.log('updateSubmission'); - console.log(data); + await prisma.submission.update({ + data: { + ...data, + addedToATS: fromYrn(data.addedToATS), + financiallySupported: fromYrn(data.financiallySupported), + updatedAai: fromYrn(data.updatedAai) + }, + where: { + submissionId: data.submissionId + } + }); } catch (e: unknown) { throw e; } diff --git a/app/src/types/ChefsSubmissionForm.ts b/app/src/types/ChefsSubmissionForm.ts new file mode 100644 index 000000000..7011d8125 --- /dev/null +++ b/app/src/types/ChefsSubmissionForm.ts @@ -0,0 +1,27 @@ +import { IStamps } from '../interfaces/IStamps'; + +export type ChefsSubmissionForm = { + submissionId: string; + assigneeToUserId?: string; + confirmationId: string; + contactEmail?: string; + contactPhoneNumber?: string; + contactFirstName?: string; + contactLastName?: string; + intakeStatus?: string; + projectName?: string; + queuePriority?: string; + singleFamilyUnits?: string; + streetAddress?: string; + atsClientNumber?: string; + addedToATS?: string; + financiallySupported?: string; + applicationStatus?: string; + relatedPermits?: string; + updatedAai?: string; + waitingOn?: string; + submittedAt: string; + submittedBy: string; + bringForwardDate?: string; + notes?: string; +} & IStamps; diff --git a/app/src/types/ChefsSubmissionDataSource.ts b/app/src/types/ChefsSubmissionFormExport.ts similarity index 88% rename from app/src/types/ChefsSubmissionDataSource.ts rename to app/src/types/ChefsSubmissionFormExport.ts index 26cc65d86..8e2368e2f 100644 --- a/app/src/types/ChefsSubmissionDataSource.ts +++ b/app/src/types/ChefsSubmissionFormExport.ts @@ -1,4 +1,4 @@ -export type ChefsSubmissionDataSource = { +export type ChefsSubmissionFormExport = { form: { id: string; submissionId: string; diff --git a/frontend/src/components/form/Calendar.vue b/frontend/src/components/form/Calendar.vue index 0c1d142e0..e71b02514 100644 --- a/frontend/src/components/form/Calendar.vue +++ b/frontend/src/components/form/Calendar.vue @@ -10,13 +10,15 @@ type Props = { label?: string; name: string; disabled?: boolean; + showTime?: boolean; }; const props = withDefaults(defineProps(), { helpText: '', type: 'text', label: '', - disabled: false + disabled: false, + showTime: false }); const { errorMessage, value } = useField(toRef(props, 'name')); @@ -31,7 +33,7 @@ const { errorMessage, value } = useField(toRef(props, 'name')); :name="name" :class="'w-full ' + { 'p-invalid': errorMessage }" :disabled="disabled" - show-time + :show-time="props.showTime" hour-format="24" show-icon icon-display="input" diff --git a/frontend/src/components/submission/SubmissionForm.vue b/frontend/src/components/submission/SubmissionForm.vue index 465e89300..fa8ecdda7 100644 --- a/frontend/src/components/submission/SubmissionForm.vue +++ b/frontend/src/components/submission/SubmissionForm.vue @@ -11,7 +11,6 @@ import { formatJwtUsername } from '@/utils/formatters'; type Props = { editable: boolean; submission: any; - submissionStatus: any; }; const props = withDefaults(defineProps(), {}); @@ -21,19 +20,10 @@ const emit = defineEmits(['submit', 'cancel']); // Default form values const initialFormValues: any = { - assignee: props.submissionStatus.user?.username, - confirmationId: props.submission.confirmationId, - contactEmail: props.submission.submission.data.contactEmail, - contactPhoneNumber: props.submission.submission.data.contactPhoneNumber, - contactFirstName: props.submission.submission.data.contactFirstName, - contactLastName: props.submission.submission.data.contactLastName, - shasCreatedAt: new Date(props.submission.shasCreatedAt), - shasCreatedBy: formatJwtUsername(props.submission.shasCreatedBy), - intakeStatus: props.submissionStatus.code, - projectName: props.submission.submission.data.projectName, - queuePriority: props.submission.submission.data.queuePriority, - singleFamilyUnits: props.submission.submission.data.singleFamilyUnits, - streetAddress: props.submission.submission.data.streetAddress + ...props.submission, + bringForwardDate: props.submission.bringForwardDate ? new Date(props.submission.bringForwardDate) : undefined, + submittedAt: new Date(props.submission.submittedAt), + submittedBy: formatJwtUsername(props.submission.submittedBy) }; // Form validation schema @@ -92,13 +82,13 @@ const onSubmit = (values: any) => { /> diff --git a/frontend/src/services/chefsService.ts b/frontend/src/services/chefsService.ts index 238fd0674..3d8fee647 100644 --- a/frontend/src/services/chefsService.ts +++ b/frontend/src/services/chefsService.ts @@ -13,23 +13,15 @@ export default { * @function getSubmission * @returns {Promise} An axios response */ - getSubmission(formId: string, formSubmissionId: string) { - return appAxios().get(`chefs/submission/${formSubmissionId}`, { params: { formId } }); - }, - - /** - * @function getSubmissionStatus - * @returns {Promise} An axios response - */ - getSubmissionStatus(formId: string, formSubmissionId: string) { - return appAxios().get(`chefs/submission/${formSubmissionId}/status`, { params: { formId } }); + getSubmission(formId: string, submissionId: string) { + return appAxios().get(`chefs/submission/${submissionId}`, { params: { formId } }); }, /** * @function updateSubmission * @returns {Promise} An axios response */ - updateSubmission(formId: string, formSubmissionId: string, data: any) { - return appAxios().put(`chefs/submission/${formSubmissionId}`, data, { params: { formId } }); + updateSubmission(submissionId: string, data: any) { + return appAxios().put(`chefs/submission/${submissionId}`, data); } }; diff --git a/frontend/src/views/SubmissionView.vue b/frontend/src/views/SubmissionView.vue index 42606269e..e25b198e5 100644 --- a/frontend/src/views/SubmissionView.vue +++ b/frontend/src/views/SubmissionView.vue @@ -18,7 +18,6 @@ const props = withDefaults(defineProps(), {}); // State const editable: Ref = ref(false); const submission: Ref = ref(undefined); -const submissionStatus: Ref = ref(undefined); // Actions const toast = useToast(); @@ -28,15 +27,17 @@ function onCancel() { } async function onSubmit(data: any) { - console.log(data); editable.value = false; - await chefsService.updateSubmission(props.formId, props.submissionId, data); + delete data.assignee; // TODO: REMOVE THIS WHEN USERS WORKING + await chefsService.updateSubmission(props.submissionId, { + ...data, + submissionId: props.submissionId + }); toast.success('Form saved'); } onMounted(async () => { - submission.value = (await chefsService.getSubmission(props.formId, props.submissionId)).data.submission; - submissionStatus.value = (await chefsService.getSubmissionStatus(props.formId, props.submissionId)).data[0]; + submission.value = (await chefsService.getSubmission(props.formId, props.submissionId)).data; }); @@ -51,10 +52,9 @@ onMounted(async () => {