import { Injectable } from '@angular/core'; import { CurrencyService } from '@core/services/currency.service'; import { FormMaskingService } from '@core/services/form-masking.service'; import { SpecialHandlingService } from '@core/services/special-handling.service'; import { TimeZoneService } from '@core/services/time-zone.service'; import { ReferenceFieldAPI } from '@core/typings/api/reference-fields.typing'; import { ApplicationInfoForPDF } from '@core/typings/pdf.typing'; import { ReferenceFieldsUI } from '@core/typings/ui/reference-fields.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { FormDecisionTypes, FormDefinitionComponent, FormioAnswerValues, ReportFieldDataOptions } from '@features/configure-forms/form.typing'; import { CustomDataTablesService } from '@features/custom-data-tables/custom-data-table.service'; import { EmployeeSSOFieldsData } from '@features/employee-sso-fields/employee-sso-fields.typing'; import { ComponentHelperService, EMPLOYEE_SSO_TYPE_PREFIX } from '@features/formio/services/component-helper/component-helper.service'; import { ReportFieldService } from '@features/formio/services/report-field/report-field.service'; import { InKindService } from '@features/in-kind/in-kind.service'; import { ReferenceFieldsService } from '@features/reference-fields/services/reference-fields.service'; import { YcFile } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { CurrencyValue } from '@yourcause/common/masking'; @Injectable({ providedIn: 'root' }) export class ApplicationDownloadAnswerService { constructor ( private referenceFieldService: ReferenceFieldsService, private formMaskingService: FormMaskingService, private specialHandlingService: SpecialHandlingService, private i18n: I18nService, private currencyService: CurrencyService, private timezoneService: TimeZoneService, private customDataTableService: CustomDataTablesService, private clientSettingsService: ClientSettingsService, private inKindService: InKindService, private reportFieldService: ReportFieldService, private componentHelper: ComponentHelperService ) { } /** * Gets the answer of a particular component * * @param component: the component * @param application: the application * @param referenceFields: the reference fields * @param decision: the decision * @param responsesMasked: are responses masked? * @param reviewerRecommendedFundingAmount: reviewer recommended funding amount * @returns the answer */ getAnswerFromForm ( component: FormDefinitionComponent, application: ApplicationInfoForPDF, referenceFields: ReferenceFieldsUI.RefResponseMap = {}, decision: FormDecisionTypes, responsesMasked: boolean, reviewerRecommendedFundingAmount = 0 ) { let answer: FormioAnswerValues; const field = this.referenceFieldService.getReferenceFieldFromCompType(component.type); const type = this.adaptTypeForGetAnswer(component.type, field); const key = field ? field.key : component.key; if (field?.isMasked && responsesMasked) { return this.formMaskingService.formFieldMask; } switch (type) { case 'refFieldDefault': { answer = this.getTextFieldAnswer(referenceFields, key); break; } case 'refFieldTextMulti': { answer = this.getMultiTextFieldAnswer(referenceFields, field.key); break; } case 'refFieldCheckbox': { answer = this.getCheckboxAnswer(referenceFields, field.key); break; } case 'refFieldCurrency': { answer = this.getCurrencyFieldAnswer(referenceFields, field.key); break; } case 'refFieldDate': { answer = this.getDateFieldAnswer(referenceFields, field.key); break; } case 'refFieldNumber': { answer = this.getNumberFieldAnswer( referenceFields, field.key, component.requireDecimal, component.decimalLimit ); break; } case 'refCustomDataTable': { answer = this.getCustomDataTableAnswer( referenceFields, field.key, field.customDataTableGuid, field.supportsMultiple ); break; } case 'refFieldFileUpload': answer = this.getFileUploadAnswer(referenceFields, field.key); break; case 'subset': answer = this.getSubsetFieldAnswer( referenceFields, field ); break; case 'refFieldTable': answer = this.getTableFieldAnswer( referenceFields, field ); break; case 'decision': { answer = this.getDecisionAnswer(decision, component); break; } case 'careOf': { answer = this.getCareOfAnswer(application); break; } case 'specialHandling': { answer = this.specialHandlingService.getSpecialHandlingAnswer(application.specialHandling); break; } case 'amountRequested': { answer = this.getAmountRequestedAnswer(application); break; } case 'designation': { answer = this.getDesignationAnswer(application); break; } case 'reviewerRecommendedFundingAmount': { answer = this.getRecommendedFundingAnswer(reviewerRecommendedFundingAmount); break; } case 'inKindItems': { answer = this.getInKindAnswer(application); break; } case 'reportField': { answer = this.getReportFieldAnswer(application, component); } break; case 'ssoField': { answer = this.getSsoFieldAnswer(application, component); break; } } if (!!answer || answer === 0) { if (component.prefix) { answer = component.prefix + answer; } if (component.suffix) { answer = answer + component.suffix; } } if (!answer && (answer !== 0)) { answer = this.i18n.translate( 'common:textNoAnswer', {}, 'No answer' ); } return answer; } /** * Gets the answer for a text field * * @param referenceFields: reference field responses * @param key: ref key * @returns the adapted answer */ getTextFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { let answer = referenceFields[key]; if (typeof answer === 'string') { answer = answer.replace(/\r?\n/g, '
'); } return answer; } /** * Gets the answer for a checkbox field * * @param referenceFields: reference field responses * @param key: ref key * @returns the adapted answer */ getCheckboxAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { const answer = referenceFields[key]; if (answer === true) { return this.i18n.translate('common:textTrue', {}, 'True'); } return ''; } /** * Gets the answer for a multi text field * * @param referenceFields: reference field responses * @param key: ref key * @returns the adapted answer */ getMultiTextFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { let refAnswer = referenceFields[key] || []; if (refAnswer instanceof Array) { refAnswer = refAnswer.join('
'); } else { refAnswer = refAnswer; } return refAnswer; } /** * Gets the date field answer * * @param referenceFields: reference field responses * @param key: ref key * @returns the adapted answer */ getDateFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { let answer = referenceFields[key]; if (answer) { answer = this.timezoneService.returnMidnightUTCDateShort(answer as string); } return answer; } /** * Gets the currency field answer * * @param referenceFields: reference field responses * @param key: ref key * @returns the adapted answer */ getCurrencyFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { let answer; const currencyValue = (referenceFields[key] as CurrencyValue); if (currencyValue) { answer = this.currencyService.formatMoney( currencyValue.amountForControl, currencyValue.currency, true ); } return answer; } /** * Gets the number field answer * * @param referenceFields: reference field responses * @param key: ref key * @param requireDecimal: require decimal setting * @param decimalLimit: decimal limit setting * @returns the adapted answer */ getNumberFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string, requireDecimal: boolean, decimalLimit: number ) { let answer = referenceFields[key]; if (answer || answer === 0) { answer = this.i18n.formatNumber( answer as number, { allowDecimal: true, requireDecimal, decimalLimit, thousands: this.i18n.numberFormattingData.group, decimal: this.i18n.numberFormattingData.decimal } ); } return answer; } /** * Gets the custom data table answer * * @param referenceFields: reference field responses * @param key: ref key * @param customDataTableGuid: custom data table guid * @param supportsMultiple: does it support multiple? * @returns the adapted answer */ getCustomDataTableAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string, customDataTableGuid: string, supportsMultiple: boolean ) { let answer; const options = customDataTableGuid ? this.customDataTableService.customDataTableOptionsMap[ customDataTableGuid ] : []; if (supportsMultiple) { const refAnswer = (referenceFields[key] || []) as string[]; answer = (options || []).filter((opt) => { return (refAnswer || []).includes(opt.key); }).map((opt) => { return opt.value; }).join('
'); } else { const refAnswer = referenceFields[key]; const found = (options || []).find((option) => { return option.key === refAnswer; }); answer = found ? found.value : refAnswer; } return answer; } /** * Gets the file upload answer * * @param referenceFields: reference fields responses * @param key: ref key * @returns the adapted answer */ getFileUploadAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, key: string ) { let answer; const files = (referenceFields[key] || []) as YcFile[]; const fileNames = files.map((file) => { return file.fileName; }).join(', '); if (files.length === 0) { answer = this.i18n.translate( 'common:textNoFileUploaded', {}, 'No file uploaded' ); } else { answer = this.i18n.translate( 'common:textSeeFileNamesDynamic', { fileNames }, 'See __fileNames__' ); } return answer; } /** * Gets teh subset field answer * * @param referenceFields: reference field responses * @param field: the reference field * @returns the adapted answer */ getSubsetFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, field: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { let answer; const subsetRows = (referenceFields[field.key] || []) as ReferenceFieldsUI.TableResponseRowForUiMapped[]; const subsetResponse = this.componentHelper.getShowTableOnPdfAndRecordCount( referenceFields, field, this.referenceFieldService.getNumberOfColumns(field) ); if (subsetResponse.showTableOnPdf) { answer = this.referenceFieldService.mapRowsForTable( subsetRows, field.referenceFieldId, true ); } else { answer = this.i18n.translate( 'common:textSeeFileNameDynamic', { fileName: `${field.name}.csv` }, 'See __fileName__' ); } return answer; } /** * Gets the table field answer * * @param referenceFields: reference field responses * @param field: the reference field * @returns the adapted answer */ getTableFieldAnswer ( referenceFields: ReferenceFieldsUI.RefResponseMap, field: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { let answer; const rows = (referenceFields[field.key] || []) as ReferenceFieldsUI.TableResponseRowForUiMapped[]; const response = this.componentHelper.getShowTableOnPdfAndRecordCount( referenceFields, field, this.referenceFieldService.getNumberOfColumns(field) ); if (response.showTableOnPdf) { answer = this.referenceFieldService.mapRowsForTable( rows, field.referenceFieldId, true, true ); } else { answer = this.i18n.translate( 'common:textSeeFileNameDynamic', { fileName: `${field.name}.csv` }, 'See __fileName__' ); } return answer; } /** * Gets the designation answer * * @param application: the application * @returns the designation answer */ getDesignationAnswer (application: ApplicationInfoForPDF) { return application.designation; } /** * Gets the amount requested answer * * @param application: the application * @returns the amount requested answer */ getAmountRequestedAnswer (application: ApplicationInfoForPDF) { return this.currencyService.formatMoney( application.currencyRequestedAmountEquivalent, application.currencyRequested, true ); } /** * Gets the recommended funding answer * * @param application: the application * @returns the amount requested answer */ getRecommendedFundingAnswer (reviewerRecommendedFundingAmount: number) { return this.currencyService.formatMoney( reviewerRecommendedFundingAmount, this.clientSettingsService.defaultCurrency, true ); } /** * Gets the care of answer * * @param application: the application * @returns the care of answer */ getCareOfAnswer (application: ApplicationInfoForPDF) { return application.careOf; } /** * Gets the decision answer * * @param decision: decision response * @param component: the form component * @returns the decision answer */ getDecisionAnswer ( decision: FormDecisionTypes, component: FormDefinitionComponent ) { let answer; if (decision === FormDecisionTypes.Approve) { answer = component.truthyValue; } else if (decision === FormDecisionTypes.Decline) { answer = component.falsyValue; } else if (decision === FormDecisionTypes.Recused) { answer = component.recuseValue; } return answer; } /** * Gets the in-kind answer * * @param application: the application * @returns the in kind answer */ getInKindAnswer (application: ApplicationInfoForPDF) { const allItemsMap = this.inKindService.allItemsMap; return application.inKindItems.filter((item) => { return item.count > 0; }).map((item) => { let name = allItemsMap[item.itemIdentification] ? allItemsMap[item.itemIdentification].name : ''; if (!name) { const found = application.availableItemsForApplicant.find((i) => { return i.identification === item.itemIdentification; }); name = found ? found.name : item.itemIdentification; } return `${item.count} ${name}`; }).join(', '); } /** * Gets the report field answer * * @param application: the application * @param component: the form component * @param answer: the answer to adapt * @returns the adapted answer */ getReportFieldAnswer ( application: ApplicationInfoForPDF, component: FormDefinitionComponent ) { let answer; const reportFieldOptions = component.reportFieldDataOptions; let reportFieldOptionsParsed: ReportFieldDataOptions; if (reportFieldOptions) { try { if (typeof reportFieldOptions === 'string') { reportFieldOptionsParsed = JSON.parse(reportFieldOptions as any); } else { reportFieldOptionsParsed = component.reportFieldDataOptions; } } catch (e) { } } if (reportFieldOptionsParsed && application.reportFieldResponse) { answer = this.reportFieldService.getReportFieldValue( reportFieldOptionsParsed, application.reportFieldResponse ); } return answer; } /** * Gets the SSO answer * * @param application: the application * @param component: the form component * @returns the adapted answer */ getSsoFieldAnswer ( application: ApplicationInfoForPDF, component: FormDefinitionComponent ) { let answer; const ssoKey = component.type.split(`${EMPLOYEE_SSO_TYPE_PREFIX}-`)[1] as keyof EmployeeSSOFieldsData; if (ssoKey) { answer = application.employeeInfo[ssoKey]; } return answer; } /** * Adapts the type for get answer * * @param compType: component type * @param field: reference field is applicable * @returns the adapted type */ adaptTypeForGetAnswer ( compType: string, field: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { let adaptedType = compType; if (!!field) { switch (field.type) { case ReferenceFieldsUI.ReferenceFieldTypes.TextArea: case ReferenceFieldsUI.ReferenceFieldTypes.TextField: if (field.supportsMultiple) { adaptedType = 'refFieldTextMulti'; } else { adaptedType = 'refFieldDefault'; } break; case ReferenceFieldsUI.ReferenceFieldTypes.Checkbox: adaptedType = 'refFieldCheckbox'; break; case ReferenceFieldsUI.ReferenceFieldTypes.CustomDataTable: case ReferenceFieldsUI.ReferenceFieldTypes.SelectBoxes: case ReferenceFieldsUI.ReferenceFieldTypes.Radio: adaptedType = 'refCustomDataTable'; break; case ReferenceFieldsUI.ReferenceFieldTypes.Currency: adaptedType = 'refFieldCurrency'; break; case ReferenceFieldsUI.ReferenceFieldTypes.Date: adaptedType = 'refFieldDate'; break; case ReferenceFieldsUI.ReferenceFieldTypes.FileUpload: adaptedType = 'refFieldFileUpload'; break; case ReferenceFieldsUI.ReferenceFieldTypes.Table: adaptedType = 'refFieldTable'; break; case ReferenceFieldsUI.ReferenceFieldTypes.Number: case ReferenceFieldsUI.ReferenceFieldTypes.Aggregate: adaptedType = 'refFieldNumber'; break; case ReferenceFieldsUI.ReferenceFieldTypes.Subset: adaptedType = 'subset'; break; } } else if (compType.startsWith(`${EMPLOYEE_SSO_TYPE_PREFIX}-`)) { adaptedType = 'ssoField'; } return adaptedType; } }