Skip to content

Commit

Permalink
Merge pull request #591 from bcgov/ccfri-4027-document-upload-bugs
Browse files Browse the repository at this point in the history
ccfri-4027 - fix document upload bugs and code refactor
  • Loading branch information
vietle-cgi authored Dec 5, 2024
2 parents c85f3ce + 5b4770d commit 1560f10
Show file tree
Hide file tree
Showing 18 changed files with 436 additions and 699 deletions.
56 changes: 9 additions & 47 deletions backend/src/components/supportingDocumentUpload.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
const {postApplicationDocument, getApplicationDocument, deleteDocument, patchOperationWithObjectId} = require('./utils');
const { postApplicationDocument, getApplicationDocument, patchOperationWithObjectId } = require('./utils');
const HttpStatus = require('http-status-codes');
const log = require('./logger');
const {getFileExtension, convertHeicDocumentToJpg} = require('../util/uploadFileUtils');
const { getFileExtension, convertHeicDocumentToJpg } = require('../util/uploadFileUtils');

async function saveDocument(req, res) {
try {
Expand All @@ -11,20 +11,21 @@ async function saveDocument(req, res) {
let documentClone = document;
let changeRequestNewFacilityId = documentClone.changeRequestNewFacilityId;
delete documentClone.changeRequestNewFacilityId;
if (getFileExtension(documentClone.filename) === 'heic' ) {
if (getFileExtension(documentClone.filename) === 'heic') {
log.verbose(`saveDocument :: heic detected for file name ${documentClone.filename} starting conversion`);
documentClone = await convertHeicDocumentToJpg(documentClone);
}
let response = await postApplicationDocument(documentClone);
//if this is a new facility change request, link supporting documents to the New Facility Change Action
if (changeRequestNewFacilityId) {
await patchOperationWithObjectId('ccof_change_request_new_facilities', changeRequestNewFacilityId, {
'[email protected]': `/ccof_application_facility_documents(${response?.applicationFacilityDocumentId})`
'[email protected]': `/ccof_application_facility_documents(${response?.applicationFacilityDocumentId})`,
});
}
}
return res.sendStatus(HttpStatus.OK);
} catch (e) {
log.error(e);
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(e.data ? e.data : e?.status);
}
}
Expand All @@ -39,7 +40,6 @@ function mapDocument(fileInfo) {
document.ccof_application_facility_documentId = fileInfo['ApplicationFacilityDocument.ccof_application_facility_documentid'];
document.description = fileInfo.notetext;
return document;

}

async function getUploadedDocuments(req, res) {
Expand All @@ -49,61 +49,23 @@ async function getUploadedDocuments(req, res) {
let documentFiles = [];
if (response?.value?.length > 0) {
for (let fileInfo of response?.value) {
if(getAllFiles){
if (getAllFiles) {
documentFiles.push(mapDocument(fileInfo));
}
else{
if(fileInfo.subject !== 'Facility License') {
} else {
if (fileInfo.subject !== 'Facility License') {
documentFiles.push(mapDocument(fileInfo));
}
}

}
}
return res.status(HttpStatus.OK).json(documentFiles);
} catch (e) {
log.error(e);
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(e.data ? e.data : e?.status);
}
}

async function getAllUploadedDocuments(req, res) {
try {
let response = await getApplicationDocument(req.params.applicationId);
let documentFiles = [];
if (response?.value?.length > 0) {
for (let fileInfo of response?.value) {
const document = {};
document.filename = fileInfo.filename;
document.annotationid = fileInfo.annotationid;
document.documentType = fileInfo.subject;
document.ccof_facility = fileInfo['ApplicationFacilityDocument.ccof_facility'];
document.ccof_facility_name = fileInfo['ApplicationFacilityDocument.ccof_facility@OData.Community.Display.V1.FormattedValue'];
document.ccof_application_facility_documentId = fileInfo['ApplicationFacilityDocument.ccof_application_facility_documentid'];
document.description=fileInfo.notetext;
documentFiles.push(document);
}
}
return res.status(HttpStatus.OK).json(documentFiles);
} catch (e) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(e.data ? e.data : e?.status);
}
}

async function deleteUploadedDocuments(req, res) {
try {
let deletedDocuments = req.body;
for (let annotationid of deletedDocuments) {
await deleteDocument(annotationid);
}
return res.sendStatus(HttpStatus.OK);
} catch (e) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(e.data ? e.data : e?.status);
}

}

module.exports = {
saveDocument,
getUploadedDocuments,
deleteUploadedDocuments
};
9 changes: 3 additions & 6 deletions backend/src/routes/supportingDocuments.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ const passport = require('passport');
const router = express.Router();
const auth = require('../components/auth');
const isValidBackendToken = auth.isValidBackendToken();
const {saveDocument, getUploadedDocuments, deleteUploadedDocuments} = require('../components/supportingDocumentUpload');
const { saveDocument, getUploadedDocuments } = require('../components/supportingDocumentUpload');

module.exports = router;

router.post('', passport.authenticate('jwt', {session: false}), isValidBackendToken, saveDocument);

router.get('/:applicationId', passport.authenticate('jwt', {session: false}), isValidBackendToken, getUploadedDocuments);

router.delete('', passport.authenticate('jwt', {session: false}), isValidBackendToken, deleteUploadedDocuments);
router.post('', passport.authenticate('jwt', { session: false }), isValidBackendToken, saveDocument);

router.get('/:applicationId', passport.authenticate('jwt', { session: false }), isValidBackendToken, getUploadedDocuments);
6 changes: 5 additions & 1 deletion backend/src/util/mapping/Mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,11 @@ const DocumentsMappings = [
{ back: 'subject', front: 'documentType' },
];

const ApplicationDocumentsMappings = [...DocumentsMappings, { back: 'ApplicationFacilityDocument.ccof_facility', front: 'facilityId' }];
const ApplicationDocumentsMappings = [
...DocumentsMappings,
{ back: 'ApplicationFacilityDocument.ccof_facility', front: 'facilityId' },
{ back: 'ApplicationFacilityDocument.ccof_facility@OData.Community.Display.V1.FormattedValue', front: 'facilityName' },
];

module.exports = {
ApplicationDocumentsMappings,
Expand Down
91 changes: 27 additions & 64 deletions frontend/src/components/RFI/RFIDocumentUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
Upload supporting documents (for example, receipts, quotes, invoices, and/or budget/finance documents)
</v-row>
<v-row class="pa-6 pt-2 text-body-2">
The maximum file size is 2MB for each document. Accepted file types are jpg, jpeg, heic, png, pdf, docx, doc,
xls, and xlsx.
{{ FILE_REQUIREMENTS_TEXT }}
</v-row>
</div>
<div class="px-md-12 px-7 pb-10">
Expand All @@ -27,12 +26,16 @@
>
<template #top>
<v-col flex>
<div class="d-flex">
<v-btn class="my-5" dark color="#003366" :disabled="isLocked" @click="addNew">
<v-icon dark> mdi-plus </v-icon>
Add
</v-btn>
</div>
<AppButton
v-if="!isLocked"
id="add-new-file"
:primary="false"
size="large"
class="add-file-button mb-2"
@click="addNew"
>
Add File
</AppButton>
</v-col>
</template>
<template #item.document="{ item }">
Expand All @@ -42,19 +45,18 @@
<v-file-input
v-else
:id="String(item.id)"
@update:model-value="selectFile"
color="#003366"
:rules="fileRules"
:rules="rules.fileRules"
prepend-icon="mdi-file-upload"
:clearable="false"
class="pt-0"
:accept="fileAccept"
class="mt-4"
:accept="FILE_TYPES_ACCEPT"
:disabled="false"
placeholder="Select your file"
:error-messages="fileInputError"
required
@click:clear="deleteItem(item)"
@click="uploadDocumentClicked($event)"
@update:model-value="selectFile"
/>
</template>
<template #item.description="{ item }">
Expand All @@ -65,10 +67,10 @@
v-else
v-model="item.description"
placeholder="Enter a description (Optional)"
density="compact"
clearable
:rules="[rules.maxLength(255)]"
max-length="255"
class="mt-4"
@change="descriptionChanged(item)"
/>
</template>
Expand All @@ -82,18 +84,21 @@
</template>
<script>
import { mapState } from 'pinia';
import AppButton from '@/components/guiComponents/AppButton.vue';
import { useApplicationStore } from '@/store/application.js';
import { useNavBarStore } from '@/store/navBar.js';
import { useReportChangesStore } from '@/store/reportChanges.js';
import { getFileExtension, getFileNameWithMaxNameLength, humanFileSize } from '@/utils/file.js';
import { getFileNameWithMaxNameLength } from '@/utils/file.js';
import alertMixin from '@/mixins/alertMixin.js';
import rules from '@/utils/rules.js';
import { deepCloneObject } from '@/utils/common.js';
import { CHANGE_TYPES } from '@/utils/constants.js';
import { CHANGE_TYPES, FILE_REQUIREMENTS_TEXT, FILE_TYPES_ACCEPT } from '@/utils/constants.js';
export default {
components: {},
components: { AppButton },
mixins: [alertMixin],
props: {
currentFacility: {
Expand Down Expand Up @@ -139,24 +144,6 @@ export default {
class: 'table-header',
},
],
fileAccept: [
'image/png',
'image/jpeg',
'image/jpg',
'.pdf',
'.png',
'.jpg',
'.jpeg',
'.heic',
'.doc',
'.docx',
'.xls',
'.xlsx',
],
fileExtensionAccept: ['pdf', 'png', 'jpg', 'jpeg', 'heic', 'doc', 'docx', 'xls', 'xlsx'],
fileFormats: 'PDF, JPEG, JPG, PNG, HEIC, DOC, DOCX, XLS and XLSX',
fileInputError: [],
fileRules: [],
uploadedRFITypeDocuments: [],
editedIndex: -1,
editedItem: {
Expand All @@ -169,7 +156,6 @@ export default {
description: '',
id: null,
},
selectRules: [(v) => !!v || 'This is required'],
};
},
computed: {
Expand Down Expand Up @@ -198,31 +184,12 @@ export default {
},
},
},
created() {
this.FILE_REQUIREMENTS_TEXT = FILE_REQUIREMENTS_TEXT;
this.FILE_TYPES_ACCEPT = FILE_TYPES_ACCEPT;
this.rules = rules;
},
async mounted() {
const maxSize = 2100000; // 2.18 MB is max size since after base64 encoding it might grow upto 3 MB.
this.fileRules = [
(v) => !!v || 'This is required',
(value) => {
return !value || !value.length || value[0]?.name?.length < 255 || 'File name can be max 255 characters.';
},
(value) => {
return (
!value ||
!value.length ||
value[0].size < maxSize ||
`The maximum file size is ${humanFileSize(maxSize)} for each document.`
);
},
(value) => {
return (
!value ||
!value.length ||
this.fileExtensionAccept.includes(getFileExtension(value[0].name)?.toLowerCase()) ||
`Accepted file types are ${this.fileFormats}.`
);
},
];
await this.createTable();
},
methods: {
Expand Down Expand Up @@ -287,10 +254,6 @@ export default {
uploadDocumentClicked(event) {
this.currentrow = event.target.id;
},
handleFileReadErr() {
this.setErrorAlert('Sorry, an unexpected error seems to have occurred. Try uploading your files later.');
},
async createTable() {
this.isLoading = true;
try {
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/RFI/RFILanding.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1241,25 +1241,30 @@
</v-form>
</template>
<script>
import { isEqual } from 'lodash';
import { mapActions, mapState } from 'pinia';
import { useRfiAppStore } from '@/store/rfiApp.js';
import { useAppStore } from '@/store/app.js';
import { useApplicationStore } from '@/store/application.js';
import { useNavBarStore } from '@/store/navBar.js';
import { useReportChangesStore } from '@/store/reportChanges.js';
import { useSupportingDocumentUploadStore } from '@/store/supportingDocumentUpload.js';
import AppTimeInput from '@/components/guiComponents/AppTimeInput.vue';
import AppDateInput from '@/components/guiComponents/AppDateInput.vue';
import FacilityHeader from '@/components/guiComponents/FacilityHeader.vue';
import RFIDocumentUpload from '@/components/RFI/RFIDocumentUpload.vue';
import NavButton from '@/components/util/NavButton.vue';
import DocumentService from '@/services/documentService';
import alertMixin from '@/mixins/alertMixin.js';
import globalMixin from '@/mixins/globalMixin.js';
import { deepCloneObject } from '@/utils/common.js';
import { isEqual } from 'lodash';
import rules from '@/utils/rules.js';
import RFIDocumentUpload from '@/components/RFI/RFIDocumentUpload.vue';
import NavButton from '@/components/util/NavButton.vue';
import { PROGRAM_YEAR_LANGUAGE_TYPES } from '@/utils/constants.js';
import FacilityHeader from '@/components/guiComponents/FacilityHeader.vue';
let model = {
expansionList: [],
Expand Down Expand Up @@ -1441,7 +1446,7 @@ export default {
methods: {
...mapActions(useRfiAppStore, ['loadRfi', 'saveRfi', 'setRfiModel']),
...mapActions(useNavBarStore, ['setNavBarRFIComplete']),
...mapActions(useSupportingDocumentUploadStore, ['saveUploadedDocuments', 'getDocuments', 'deleteDocuments']),
...mapActions(useSupportingDocumentUploadStore, ['saveUploadedDocuments', 'getDocuments']),
nextBtnClicked() {
this.$router.push(this.nextPath);
},
Expand Down Expand Up @@ -1614,7 +1619,8 @@ export default {
},
async processDocumentFileDelete() {
if (this.uploadedDocuments?.deletedItems?.length > 0) {
await this.deleteDocuments(this.uploadedDocuments.deletedItems);
await DocumentService.deleteDocuments(this.uploadedDocuments.deletedItems);
this.uploadedDocuments.deletedItems = [];
}
},
async processDocumentFilesSave(newFilesAdded) {
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/SummaryDeclaration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
variant="accordion"
>
<UploadedDocumentsSummary
:documents="facility.documents"
:documents="getDocumentsByFacility(facility)"
:program-year-id="summaryModel?.application?.programYearId"
@is-summary-valid="isFormComplete"
/>
Expand Down Expand Up @@ -1040,6 +1040,10 @@ export default {
this.forceNavBarRefresh();
},
getDocumentsByFacility(facility) {
return this.applicationUploadedDocuments?.filter((document) => facility?.facilityId === document.facilityId);
},
// CCFRI-3808 - This function ensures that submitted AFS documents from previous submissions cannot be deleted from the Portal when the Ministry Adjudicators re-enable/re-unlock the AFS section.
// i.e.: Documents with documentType = APPLICATION_AFS_SUBMITTED are not deletable.
async updateAfsSupportingDocuments() {
Expand Down
Loading

0 comments on commit 1560f10

Please sign in to comment.