import { Component, Input, OnInit, Type } from '@angular/core'; import { SpinnerService } from '@core/services/spinner.service'; import { DRAFT, StatusService } from '@core/services/status.service'; import { APConfigAPI } from '@core/typings/api/ap-config.typing'; import { ProgramApplicantType } from '@core/typings/program.typing'; import { ApplicationStatuses } from '@core/typings/status.typing'; import { ProgramService } from '@features/programs/program.service'; import { AllProgramsResolver } from '@features/programs/resolvers/all-programs.resolver'; import { ALL_SKIP_FILTER, createValidator, FileService, IsNumber, OrganizedError, Required, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup, Unique } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { YCModalComponent } from '@yourcause/common/modals'; import * as parse from 'papaparse'; import { APConfigResources } from '../ap-config.resources'; import { APConfigService } from '../ap-config.service'; const ExistsInGC = createValidator((isIndividual: boolean) => async ( prop, { attr, group, injector, context } ) => { const service = injector.get(APConfigResources); if (!context.call) { context.call = service.validateVendorIdImport({ ids: group.map(v => v[attr]), importType: isIndividual ? APConfigAPI.VendorIdImportType.APPLICANT : APConfigAPI.VendorIdImportType.ORG }); } const existingIds = await context.call; return existingIds.includes(prop) ? { i18nKey: isIndividual ? 'GLOBAL:textIndividualIdMustExistInGC' : 'GLOBAL:textOrgIdMustExistInGC', defaultValue: isIndividual ? 'Individual ID must exist in __systemName__' : 'Organization ID must exist in __systemName__', context: { systemName: 'GrantsConnect' } } : []; }); class ApplicantValidationClass { @ExistsInGC(true) @IsNumber() @Required() @Unique() 'Individual ID': number; @Required() 'Vendor ID': string; } class OrgValidationClass { @ExistsInGC(false) @IsNumber() @Required() @Unique() 'Organization ID': number; @Required() 'Vendor ID': string; } interface ImportVendorFormGroup { program: number[]; applicationStatus: (ApplicationStatuses|string)[]; includeExisting: boolean; } @Component({ selector: 'gc-import-vendor-modal', templateUrl: './import-vendor-modal.component.html', styleUrls: ['./import-vendor-modal.component.scss'] }) export class ImportVendorModalComponent extends YCModalComponent< APConfigAPI.ImportVendorIdModalResponse[] > implements OnInit { @Input() isIndividual = false; contents: APConfigAPI.ImportVendorIdContents[]; formGroup: TypeSafeFormGroup; individualImportDescription = this.i18n.translate( 'CONFIG:textIndividualImportDesc', { name: 'GrantsConnect' }, `Click the button below to select your completed CSV file and import into the staging table. Your file will be validated to confirm that it contains the headers "Individual ID" and "Vendor ID" and the individual ID exists in the __name__ system for your client. Click "Download errors" to receive a CSV file of all errors.` ); orgImportDescription = this.i18n.translate( 'CONFIG:OrganizationImportDesc', { name: 'GrantsConnect' }, `Click the button below to select your completed CSV file and import into the staging table. Your file will be validated to confirm that it contains the headers "Organization ID" and "Vendor ID" and the organization ID exists in the __name__ system for your client. Click "Download errors" to receive a CSV file of all errors.` ); programOptions: TypeaheadSelectOption[] = []; statusOptions = this.statusService.getApplicationStatusTypeaheadOptions(); importValid: boolean; errors: OrganizedError[]; ApplicantValidationClass: Type = ApplicantValidationClass; OrgValidationClass: Type = OrgValidationClass; file: File; constructor ( private formBuilder: TypeSafeFormBuilder, private statusService: StatusService, private programService: ProgramService, private i18n: I18nService, private apConfigService: APConfigService, private fileService: FileService, private spinnerService: SpinnerService, private allProgramsResolver: AllProgramsResolver, private analyticsService: AnalyticsService ) { super(); } async ngOnInit () { await this.allProgramsResolver.resolve(); this.formGroup = this.formBuilder.group({ program: [[]], applicationStatus: [[]], includeExisting: false }); this.programOptions = this.programService.allPublishedPrograms.filter((prog) => { return this.isIndividual ? prog.programApplicantType === ProgramApplicantType.INDIVIDUAL : prog.programApplicantType === ProgramApplicantType.ORGS; }).map((prog) => { return { label: prog.grantProgramName, value: prog.grantProgramId }; }); } async downloadTemplate () { this.spinnerService.startSpinner(); let applicationStatuses = this.formGroup.value.applicationStatus || []; let grantProgramIds = this.formGroup.value.program || []; if (grantProgramIds.length === 0) { grantProgramIds = this.programOptions.map((prog) => prog.value); } if ( applicationStatuses.length === 0 || (applicationStatuses.length === 1 && applicationStatuses[0] === ALL_SKIP_FILTER) ) { applicationStatuses = this.statusOptions.map((opt) => opt.value) .filter((status) => { return status !== ALL_SKIP_FILTER; }); } applicationStatuses = applicationStatuses.filter((status: string|number) => { return status !== ALL_SKIP_FILTER; }).map((status: string|number) => { if (status === DRAFT) { status = 0; } return status; }); const data = await this.apConfigService.getDataForVendorIdExport( this.isIndividual, { grantProgramIds, applicationStatuses: applicationStatuses as ApplicationStatuses[], includeExisting: this.formGroup.value.includeExisting } ); if (data && data.length > 0) { const csv = parse.unparse(data as unknown[]); this.fileService.downloadCSV(csv); } else { const input = this.isIndividual ? 'firstName,lastName,email,address1,address2,city,state,zip,country,Individual ID,Vendor ID' : 'name,address1,address2,city,state,zip,country,Registration ID,Organization ID,Vendor ID'; this.fileService.downloadString( input, 'text/csv', 'template.csv' ); } this.spinnerService.stopSpinner(); } downloadErrors () { const csv = this.fileService.convertObjectArrayToCSVString(this.errors); this.fileService.downloadString(csv, 'text/csv', 'ImportVendorIds_errors.csv'); this.analyticsService.emitEvent({ eventName: 'Download errors for vendor import', eventType: EventType.Click, extras: null }); } onContentsChange (contents: APConfigAPI.ImportVendorIdContents[]) { this.contents = contents; } import () { const contents = this.contents.map((item) => { return { id: this.isIndividual ? item['Individual ID'] : item['Organization ID'], vendorId: item['Vendor ID'] }; }); this.closeModal.emit(contents); this.analyticsService.emitEvent({ eventName: 'Import vendor submit', eventType: EventType.Click, extras: null }); } }