diff --git a/src/components/ContinuationIn/AuthorizationInformation.vue b/src/components/ContinuationIn/AuthorizationInformation.vue index 345f21ae..4758691a 100644 --- a/src/components/ContinuationIn/AuthorizationInformation.vue +++ b/src/components/ContinuationIn/AuthorizationInformation.vue @@ -212,6 +212,7 @@ import { AuthorizationProofIF, ExistingBusinessInfoIF } from '@/interfaces' import { DateMixin, DocumentMixin } from '@/mixins' import { CanJurisdictions, IntlJurisdictions, UsaJurisdiction } from '@bcrs-shared-components/jurisdiction/list-data' import { JurisdictionLocation } from '@bcrs-shared-components/enums' +import { DocumentClassEnum } from '@/enums' @Component({ components: { @@ -285,13 +286,18 @@ export default class AuthorizationInformation extends Mixins(DateMixin, Document if (!documentKey || !documentName) return // safety check this.isDownloading = true - await this.downloadDocument(documentKey, documentName).catch(error => { + await this.downloadDocumentFromDRS( + documentKey, + documentName, + DocumentClassEnum.CORP + ).catch(error => { // eslint-disable-next-line no-console - console.log('fetchDocument() error =', error) + console.log('downloadDocument() error =', error) this.errorDialogTitle = 'Unable to download document' - this.errorDialogText = 'We were unable to download your document. If this error persists, please contact us.' + this.errorDialogText = 'An error occurred while downloading the document. Please try again.' this.errorDialog = true }) + this.isDownloading = false } diff --git a/src/components/ContinuationIn/AuthorizationProof.vue b/src/components/ContinuationIn/AuthorizationProof.vue index 9a993699..0b57807b 100644 --- a/src/components/ContinuationIn/AuthorizationProof.vue +++ b/src/components/ContinuationIn/AuthorizationProof.vue @@ -163,8 +163,8 @@ import { Action, Getter } from 'pinia-class' import { StatusCodes } from 'http-status-codes' import { useStore } from '@/store/store' import { DocumentMixin } from '@/mixins' -import { AuthorizationProofIF, ExistingBusinessInfoIF, PresignedUrlIF } from '@/interfaces' -import { FilingStatus } from '@/enums' +import { AuthorizationProofIF, ExistingBusinessInfoIF } from '@/interfaces' +import { FilingStatus, DOCUMENT_TYPES as DocumentTypes } from '@/enums' import FileUploadPreview from '@/components/common/FileUploadPreview.vue' import AutoResize from 'vue-auto-resize' import MessageBox from '@/components/common/MessageBox.vue' @@ -183,14 +183,17 @@ export default class AuthorizationProof extends Mixins(DocumentMixin) { $refs!: { fileUploadPreview: FileUploadPreview } - + @Getter(useStore) getBusinessId!: string + @Getter(useStore) getTempId!: string @Getter(useStore) getContinuationInAuthorizationProof!: AuthorizationProofIF @Getter(useStore) getExistingBusinessInfo!: ExistingBusinessInfoIF @Getter(useStore) getFilingStatus!: FilingStatus @Getter(useStore) getKeycloakGuid!: string @Getter(useStore) getShowErrors!: boolean + @Getter(useStore) getContinuationInConsumerDocumentId!: string @Action(useStore) setContinuationAuthorization!: (x: AuthorizationProofIF) => void + @Action(useStore) setContinuationConsumerDocumentId!: (x: string) => void // Local properties authorization = null as AuthorizationProofIF @@ -277,13 +280,33 @@ export default class AuthorizationProof extends Mixins(DocumentMixin) { return // don't add to array } - // try to upload to Minio - let psu: PresignedUrlIF + // try to upload to Document Record Service try { this.isDocumentLoading = true - psu = await this.getPresignedUrl(file.name) - const res = await this.uploadToUrl(psu.preSignedUrl, file, psu.key, this.getKeycloakGuid) - if (!res || res.status !== StatusCodes.OK) throw new Error() + const res = await this.uploadDocumentToDRS( + file, + DocumentTypes.contInAuthorization.class, + DocumentTypes.contInAuthorization.type, + this.getTempId, + this.getContinuationInConsumerDocumentId + ) + + if (!res || ![StatusCodes.OK, StatusCodes.CREATED].includes(res.status)) throw new Error() + + // add file to array + this.authorization.files.push({ + file: { + name: file.name, + lastModified: file.lastModified, + size: file.size + } as File, + fileKey: res.data.documentServiceId, + fileName: file.name + }) + + this.setContinuationConsumerDocumentId(res.data.consumerDocumentId) + + this.isFileAdded = true } catch { // set error message this.customErrorMessage = this.UPLOAD_FAILED_MESSAGE @@ -291,19 +314,6 @@ export default class AuthorizationProof extends Mixins(DocumentMixin) { } finally { this.isDocumentLoading = false } - - // add file to array - this.authorization.files.push({ - file: { - name: file.name, - lastModified: file.lastModified, - size: file.size - } as File, - fileKey: psu.key, - fileName: file.name - }) - - this.isFileAdded = true } } @@ -314,8 +324,9 @@ export default class AuthorizationProof extends Mixins(DocumentMixin) { onRemoveClicked (index = NaN): void { // safety check if (index >= 0) { - // delete file from Minio, not waiting for response and ignoring errors - this.deleteDocument(this.authorization.files[index].fileKey).catch(() => null) + // delete file from DRS, not waiting for response and ignoring errors + this.deleteDocumentFromDRS(this.authorization.files[index].fileKey).catch((res) => console.error(res.data)) + // remove file from array this.authorization.files.splice(index, 1) // clear any existing error message diff --git a/src/components/ContinuationIn/UnlimitedLiabilityCorporationInformation.vue b/src/components/ContinuationIn/UnlimitedLiabilityCorporationInformation.vue index e1305348..58efb1e0 100644 --- a/src/components/ContinuationIn/UnlimitedLiabilityCorporationInformation.vue +++ b/src/components/ContinuationIn/UnlimitedLiabilityCorporationInformation.vue @@ -97,8 +97,9 @@ import { Action, Getter } from 'pinia-class' import { StatusCodes } from 'http-status-codes' import { useStore } from '@/store/store' import { DateMixin, DocumentMixin } from '@/mixins' -import { ExistingBusinessInfoIF, PresignedUrlIF } from '@/interfaces' +import { ExistingBusinessInfoIF } from '@/interfaces' import FileUploadPreview from '../common/FileUploadPreview.vue' +import { DOCUMENT_TYPES as DocumentTypes } from '@/enums' @Component({ components: { @@ -115,9 +116,12 @@ export default class UnlimitedLiabilityCorporationInformation extends Mixins(Dat @Getter(useStore) getExistingBusinessInfo!: ExistingBusinessInfoIF @Getter(useStore) getKeycloakGuid!: string @Getter(useStore) getShowErrors!: boolean + @Getter(useStore) getContinuationInConsumerDocumentId!: string + @Getter(useStore) getTempId!: string @Action(useStore) setExistingBusinessInfo!: (x: ExistingBusinessInfoIF) => void @Action(useStore) setHaveChanges!: (x: boolean) => void + @Action(useStore) setContinuationConsumerDocumentId!: (x: string) => void // Local properties customErrorMessage = '' @@ -164,13 +168,28 @@ export default class UnlimitedLiabilityCorporationInformation extends Mixins(Dat return // don't add to array } - // try to upload to Minio - let psu: PresignedUrlIF + // try to upload to Document Record Service try { this.isDocumentLoading = true - psu = await this.getPresignedUrl(file.name) - const res = await this.uploadToUrl(psu.preSignedUrl, file, psu.key, this.getKeycloakGuid) - if (!res || res.status !== StatusCodes.OK) throw new Error() + const res = await this.uploadDocumentToDRS( + file, + DocumentTypes.affidavitDocument.class, + DocumentTypes.affidavitDocument.type, + this.getTempId, + this.getContinuationInConsumerDocumentId + ) + if (!res || ![StatusCodes.OK, StatusCodes.CREATED].includes(res.status)) throw new Error() + + // add properties reactively to business object + this.$set(this.getExistingBusinessInfo, 'affidavitFile', { + name: file.name, + lastModified: file.lastModified, + size: file.size + } as File) + this.$set(this.getExistingBusinessInfo, 'affidavitFileKey', res.data.documentServiceId) + this.$set(this.getExistingBusinessInfo, 'affidavitFileName', file.name) + + this.setContinuationConsumerDocumentId(res.data.consumerDocumentId) } catch { // set error message this.customErrorMessage = this.UPLOAD_FAILED_MESSAGE @@ -179,15 +198,6 @@ export default class UnlimitedLiabilityCorporationInformation extends Mixins(Dat this.isDocumentLoading = false } - // add properties reactively to business object - this.$set(this.getExistingBusinessInfo, 'affidavitFile', { - name: file.name, - lastModified: file.lastModified, - size: file.size - } as File) - this.$set(this.getExistingBusinessInfo, 'affidavitFileKey', psu.key) - this.$set(this.getExistingBusinessInfo, 'affidavitFileName', file.name) - // user has changed something this.setHaveChanges(true) } @@ -198,8 +208,8 @@ export default class UnlimitedLiabilityCorporationInformation extends Mixins(Dat * May also be called by parent component to remove the file info. */ onRemoveClicked (): void { - // delete file from Minio, not waiting for response and ignoring errors - this.deleteDocument(this.getExistingBusinessInfo.affidavitFileKey).catch(() => null) + // delete file from DRS, not waiting for response and ignoring errors + this.deleteDocumentFromDRS(this.getExistingBusinessInfo.affidavitFileKey).catch((res) => console.error(res.data)) // delete properties reactively this.$delete(this.getExistingBusinessInfo, 'affidavitFile') diff --git a/src/enums/documentTypes.ts b/src/enums/documentTypes.ts new file mode 100644 index 00000000..6a485ac4 --- /dev/null +++ b/src/enums/documentTypes.ts @@ -0,0 +1,19 @@ +export enum DocumentClassEnum { + CORP = 'CORP' +} + +export enum DocumetTypeEnum { + CNTO = 'CNTO', + DIRECTOR_AFFIDAVIT = 'DIRECTOR_AFFIDAVIT' +} + +export const DOCUMENT_TYPES = { + contInAuthorization: { + class: DocumentClassEnum.CORP, + type: DocumetTypeEnum.CNTO + }, + affidavitDocument: { + class: DocumentClassEnum.CORP, + type: DocumetTypeEnum.DIRECTOR_AFFIDAVIT + } +} diff --git a/src/enums/index.ts b/src/enums/index.ts index 850f1a9f..268e758d 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -14,6 +14,7 @@ export * from './routeNames' export * from './ruleIds' export * from './views' export * from './errorTypes' +export * from './documentTypes' // external enums export { diff --git a/src/interfaces/filing-interfaces/filing-interfaces.ts b/src/interfaces/filing-interfaces/filing-interfaces.ts index f7bf4721..624bc23f 100644 --- a/src/interfaces/filing-interfaces/filing-interfaces.ts +++ b/src/interfaces/filing-interfaces/filing-interfaces.ts @@ -109,7 +109,7 @@ export interface ContinuationInFilingIF { incorporationDate: string // YYYY-MM-DD taxId?: string // aka Business Number affidavitFile?: File - affidavitFileKey?: string + affidavitFileKey?: string // documentServiceId affidavitFileName?: string } authorization?: AuthorizationProofIF diff --git a/src/interfaces/store-interfaces/state-interfaces/authorization-proof-interface.ts b/src/interfaces/store-interfaces/state-interfaces/authorization-proof-interface.ts index 80ddb4c5..f82773f8 100644 --- a/src/interfaces/store-interfaces/state-interfaces/authorization-proof-interface.ts +++ b/src/interfaces/store-interfaces/state-interfaces/authorization-proof-interface.ts @@ -4,4 +4,5 @@ export interface AuthorizationProofIF { fileKey: string fileName: string }> + consumerDocumentId?: string } diff --git a/src/interfaces/store-interfaces/state-interfaces/continuation-in-state-interface.ts b/src/interfaces/store-interfaces/state-interfaces/continuation-in-state-interface.ts index 015d8dd6..01d7ee3c 100644 --- a/src/interfaces/store-interfaces/state-interfaces/continuation-in-state-interface.ts +++ b/src/interfaces/store-interfaces/state-interfaces/continuation-in-state-interface.ts @@ -4,4 +4,5 @@ export interface ContinuationInStateIF { continuationAuthorizationPageValid: boolean authorizationProof: AuthorizationProofIF existingBusinessInfo: ExistingBusinessInfoIF + consumerDocumentId: string } diff --git a/src/interfaces/store-interfaces/state-interfaces/existing-business-info-interface.ts b/src/interfaces/store-interfaces/state-interfaces/existing-business-info-interface.ts index 33daa7bd..e7861e83 100644 --- a/src/interfaces/store-interfaces/state-interfaces/existing-business-info-interface.ts +++ b/src/interfaces/store-interfaces/state-interfaces/existing-business-info-interface.ts @@ -2,7 +2,7 @@ import { EntityStates } from '@bcrs-shared-components/enums' export interface ExistingBusinessInfoIF { affidavitFile?: File // only used by UI - affidavitFileKey?: string + affidavitFileKey?: string // documentServiceId affidavitFileName?: string bcRegistrationDate?: string // expro only (ISO date-time: '2007-04-25T22:42:42-00:00') bcRegistrationNumber?: string // expro only (aka Identifier) diff --git a/src/mixins/document-mixin.ts b/src/mixins/document-mixin.ts index 67434310..c5a4b885 100644 --- a/src/mixins/document-mixin.ts +++ b/src/mixins/document-mixin.ts @@ -192,4 +192,92 @@ export default class DocumentMixin extends Vue { if (sizeKB > 1) return `${sizeKB.toFixed(0)} KB` return `${size} bytes` } + + /** + * Uploads the specified file to Document Record Service. + * @param file the file to upload + * @param documentClass the document class defined for the document service. e.g. 'CORP' + * @param documentType the type of document. e.g. 'CNTA' + * @param temPid the temp business identifier + * @param consumerDocumentId the identifier of one or more documents associated with the filing. + * @returns a promise to return the axios response or the error response + */ + async uploadDocumentToDRS ( + document: File, + documentClass: string, + documentType: string, + tempId: string, + consumerDocumentId: string = undefined + ): Promise { + const consumerFilingDate = new Date().toISOString() + + // Set request params. + let url = `${sessionStorage.getItem('DRS_API_URL')}/documents/${documentClass}/${documentType}` + url += `?consumerFilingDate=${consumerFilingDate}&consumerFilename=${document.name}` + url += `&consumerIdentifier=${tempId}` + if (consumerDocumentId) { + url += `&consumerDocumentId=${consumerDocumentId}` + } + + const headers = { + 'x-apikey': sessionStorage.getItem('DRS_API_KEY'), + 'Account-Id': sessionStorage.getItem('DRS_ACCOUNT_ID'), + 'Content-Type': 'application/pdf' + } + return axios.post(url, document, { headers: headers }) + .then(response => { + return response + }).catch(error => { + return error.response + }) + } + + /** + * Deletes a document from Document Record Service. + * @param documentServiceId the unique identifier of document on Document Record Service + * @returns a promise to return the axios response or the error response + */ + async deleteDocumentFromDRS (documentServiceId: string): Promise { + // safety checks + if (!documentServiceId) { + throw new Error('Invalid parameters') + } + + const url = `documents/drs/${documentServiceId}` + + return axios.delete(url) + } + + /** + * Download the specified file from Document Record Service. + * @param documentKey the unique id on Document Record Service + * @param documentClass the document class defined for the document service. e.g. 'CORP' + * @param documentName the document name to download + * @returns void + */ + async downloadDocumentFromDRS (documentKey: string, + documentName: string, + documentClass: string + ): Promise { + // safety checks + if (!documentKey || !documentName) { + throw new Error('Invalid parameters') + } + const url = `documents/drs/${documentClass}/${documentKey}` + + axios.get(url).then(response => { + if (!response) throw new Error('Null response') + const link = document.createElement('a') + link.href = response.data.documentURL + link.download = documentName + link.target = '_blank' // This opens the link in a new browser tab + + // Append to the document and trigger the download + document.body.appendChild(link) + link.click() + + // Remove the link after the download is triggered + document.body.removeChild(link) + }) + } } diff --git a/src/mixins/filing-template-mixin.ts b/src/mixins/filing-template-mixin.ts index c9ab0776..d3c9d95c 100644 --- a/src/mixins/filing-template-mixin.ts +++ b/src/mixins/filing-template-mixin.ts @@ -38,6 +38,7 @@ export default class FilingTemplateMixin extends Mixins(AmalgamationMixin, DateM @Getter(useStore) getCertifyState!: CertifyIF @Getter(useStore) getCompletingParty!: CompletingPartyIF @Getter(useStore) getContinuationInAuthorizationProof!: AuthorizationProofIF + @Getter(useStore) getContinuationInConsumerDocumentId!: string @Getter(useStore) getCorrectNameOption!: CorrectNameOptions @Getter(useStore) getCourtOrderStep!: CourtOrderStepIF @Getter(useStore) getCreateMemorandumStep!: CreateMemorandumIF @@ -413,7 +414,8 @@ export default class FilingTemplateMixin extends Mixins(AmalgamationMixin, DateM // Add continuation in authorization proof. if (this.getContinuationInAuthorizationProof) { filing.continuationIn.authorization = { - files: this.getContinuationInAuthorizationProof.files + files: this.getContinuationInAuthorizationProof.files, + consumerDocumentId: this.getContinuationInConsumerDocumentId } } diff --git a/src/store/state/state-model.ts b/src/store/state/state-model.ts index 3f016136..e1ecc926 100644 --- a/src/store/state/state-model.ts +++ b/src/store/state/state-model.ts @@ -219,7 +219,8 @@ export const stateModel: StateModelIF = { continuationIn: { continuationAuthorizationPageValid: false, authorizationProof: null, - existingBusinessInfo: {} as ExistingBusinessInfoIF + existingBusinessInfo: {} as ExistingBusinessInfoIF, + consumerDocumentId: null }, restoration: { applicationDate: null, diff --git a/src/store/store.ts b/src/store/store.ts index 56133f10..856eedd7 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -236,6 +236,11 @@ export const useStore = defineStore('store', { return this.getContinuationIn.existingBusinessInfo }, + /** The document ID used for Document Record Service. */ + getContinuationInConsumerDocumentId (): string { + return this.getContinuationIn.consumerDocumentId + }, + /** The account folio number. */ getFolioNumber (): string { return this.stateModel.tombstone.folioNumber @@ -1205,6 +1210,10 @@ export const useStore = defineStore('store', { this.stateModel.continuationIn.existingBusinessInfo = val if (!this.stateModel.ignoreChanges) this.stateModel.haveChanges = true }, + setContinuationConsumerDocumentId (val: string) { + this.stateModel.continuationIn.consumerDocumentId = val + if (!this.stateModel.ignoreChanges) this.stateModel.haveChanges = true + }, setIsFutureEffective (isFutureEffective: boolean) { this.stateModel.effectiveDateTime.isFutureEffective = isFutureEffective if (!this.stateModel.ignoreChanges) this.stateModel.haveChanges = true diff --git a/src/utils/AxiosInstance.ts b/src/utils/AxiosInstance.ts index bc83beec..7dac84c3 100644 --- a/src/utils/AxiosInstance.ts +++ b/src/utils/AxiosInstance.ts @@ -11,7 +11,7 @@ const instance = axios.create() instance.interceptors.request.use( request => { // don't add bearer token for Minio requests - if (request.url?.startsWith('https://minio')) { + if (request.url?.startsWith('https://minio') || request.url.startsWith(sessionStorage.getItem('DRS_API_URL'))) { return request } diff --git a/src/utils/FetchConfig.ts b/src/utils/FetchConfig.ts index 8d7d4d72..2ea311fc 100644 --- a/src/utils/FetchConfig.ts +++ b/src/utils/FetchConfig.ts @@ -76,6 +76,16 @@ export async function FetchConfig (): Promise { const keycloakClientId: string = import.meta.env.VUE_APP_KEYCLOAK_CLIENTID; (window).keycloakClientId = keycloakClientId + // Document Record Service Configration + const drsAccountId: string = import.meta.env.VUE_APP_DRS_ACCOUNT_ID + sessionStorage.setItem('DRS_ACCOUNT_ID', drsAccountId) + + const drsApiKey: string = import.meta.env.VUE_APP_DRS_API_KEY + sessionStorage.setItem('DRS_API_KEY', drsApiKey) + + const drsApiUrl: string = (import.meta.env.VUE_APP_DRS_API_URL + '/' + import.meta.env.VUE_APP_DRS_API_VERSION) + sessionStorage.setItem('DRS_API_URL', drsApiUrl) + const iaSurveyId: string = import.meta.env.VUE_APP_IA_SURVEY_ID // NB: set empty string if iaSurveyId is falsy (undefined, null or 0) sessionStorage.setItem('IA_SURVEY_ID', iaSurveyId || '')