From c2c27606b6c8bb8d3bc8cdaf76cbff459afe6ed1 Mon Sep 17 00:00:00 2001 From: Katherine Fleming <2205659+kflemin@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:45:33 -0600 Subject: [PATCH] complete Salesforce settings migration and fix bugs --- .../api/salesforce/salesforce.service.ts | 16 +- src/@seed/api/salesforce/salesforce.types.ts | 2 +- .../salesforce/salesforce.component.html | 753 ++++++++++-------- .../salesforce/salesforce.component.ts | 35 +- 4 files changed, 448 insertions(+), 358 deletions(-) diff --git a/src/@seed/api/salesforce/salesforce.service.ts b/src/@seed/api/salesforce/salesforce.service.ts index d749ad47..54a671eb 100644 --- a/src/@seed/api/salesforce/salesforce.service.ts +++ b/src/@seed/api/salesforce/salesforce.service.ts @@ -50,8 +50,7 @@ export class SalesforceService { return response }), catchError((error: HttpErrorResponse) => { - // TODO need to figure out error handling - return this._errorService.handleError(error, 'Error fetching organization') + return this._errorService.handleError(error, 'Error fetching Salesforce config') }), ) } @@ -64,14 +63,12 @@ export class SalesforceService { return response }), catchError((error: HttpErrorResponse) => { - // TODO need to figure out error handling - return this._errorService.handleError(error, 'Error fetching organization') + return this._errorService.handleError(error, 'Error fetching Salesforce mappings') }), ) } create(organizationId: number, config: SalesforceConfig): Observable { - console.log('Creating: ', config) const url = `/api/v3/salesforce_configs/?organization_id=${organizationId}` return this._httpClient.post(url, { ...config }).pipe( map((response) => { @@ -79,7 +76,7 @@ export class SalesforceService { return response.salesforce_config }), catchError((error: HttpErrorResponse) => { - return this._errorService.handleError(error, 'Error fetching organization') + return this._errorService.handleError(error, 'Error creating Salesforce config') }), ) } @@ -93,7 +90,7 @@ export class SalesforceService { return response.salesforce_config }), catchError((error: HttpErrorResponse) => { - return this._errorService.handleError(error, 'Error fetching organization') + return this._errorService.handleError(error, 'Error updating Salesforce config') }), ) } @@ -119,10 +116,11 @@ export class SalesforceService { return response }), catchError((error: HttpErrorResponse) => { - return this._errorService.handleError(error, `Salesforce Mapping could not be created: ${error.message}`) + return this._errorService.handleError(error, 'Error creating Salesforce mapping') }), ) } + updateMapping(organizationId: number, mapping: SalesforceMapping): Observable { const url = `/api/v3/salesforce_mappings/${mapping.id}/?organization_id=${organizationId}` return this._httpClient.put(url, { ...mapping }).pipe( @@ -131,7 +129,7 @@ export class SalesforceService { return response }), catchError((error: HttpErrorResponse) => { - return this._errorService.handleError(error, `Salesforce Mapping could not be created: ${error.message}`) + return this._errorService.handleError(error, 'Error updating Salesforce mapping') }), ) } diff --git a/src/@seed/api/salesforce/salesforce.types.ts b/src/@seed/api/salesforce/salesforce.types.ts index e59aea35..d1e92ed9 100644 --- a/src/@seed/api/salesforce/salesforce.types.ts +++ b/src/@seed/api/salesforce/salesforce.types.ts @@ -45,7 +45,7 @@ export type SalesforceConfigsResponse = { export type SalesforceMapping = { id: number; organization_id: number; - column: 10; + column: number; salesforce_fieldname: string; } diff --git a/src/app/modules/organizations/settings/salesforce/salesforce.component.html b/src/app/modules/organizations/settings/salesforce/salesforce.component.html index dcc11295..7acb3d54 100644 --- a/src/app/modules/organizations/settings/salesforce/salesforce.component.html +++ b/src/app/modules/organizations/settings/salesforce/salesforce.component.html @@ -16,351 +16,426 @@
-
- -
{{ t('Salesforce Connection') }}
-
-
{{ t('Enter your Salesforce instance details and ensure your connection is successful') }}
-
- - {{ t('Salesforce URL') }} - - -
-
- - {{ t('Username') }} - - -
-
- - {{ t('Password') }} - - - -
-
- - {{ t('Security Token') }} - - - {{ t('Security token set in Salesforce') }} - -
-
- - {{ t('Domain') }} - - {{ - t("If your Salesforce instance is a sandbox, set this field to the value 'test'; otherwise leave blank.") - }} - -
-
- -
- - -
- -
{{ t('Scheduled Daily Updates') }}
-
-
- {{ t('If you would like to automatically update Salesforce on a daily basis, configure the fields below') }} -
-
- - {{ t('Hour') }} - - Enter the hour when the update should be run daily (0-23) Timezone: America/New_York - - - {{ t('Minute') }} - - Enter the minute after the hour when the update should be run daily (0-59) - -
-
- - {{ t('Logging Email') }} - - Enter the e-mail address to use when reporting errors during the Salesforce updating process - @if (salesforceForm.get('salesforceConfig.logging_email').hasError('email')) { - Please enter a valid email address - } - -
- @if (salesforceConfig) { -
-
- {{ t('Last Salesforce Update') }}: {{ salesforceConfig.last_update_date || 'N/A' }} -
- @if (salesforceConfig.last_update_date) { - - } + @if (salesforceForm.controls.salesforce_enabled.value) { +
+ +
{{ t('Salesforce Connection') }}
- } - +
{{ t('Enter your Salesforce instance details and ensure your connection is successful') }}
+
+ + {{ t('Salesforce URL') }} + + +
+
+ + {{ t('Username') }} + + +
+
+ + {{ t('Password') }} + + + +
+
+ + {{ t('Security Token') }} + + + {{ t('Security token set in Salesforce') }} + +
+
+ + {{ t('Domain') }} + + {{ + t("If your Salesforce instance is a sandbox, set this field to the value 'test'; otherwise leave blank.") + }} + +
+
+ +
+ @if (testConnectionStatus === 'success') { +
+ + {{ t('Salesforce connection successful') }} +
+ } + @if (testConnectionStatus === 'error') { +
+ + {{ t('Connection error') }}{{ testConnectionMessage ? ': ' + testConnectionMessage : '' }} +
+ } + -
- -
{{ t('Configuration') }}
-
-
{{ t('Configure a few parameters needed for data transfer to Salesforce') }}
-
- - {{ t('Indication Label') }} - - @for (l of labels; track l.id) { - {{ l.name }} - } - - Label used to designate that a SEED property should be updated in Salesforce. Example: 'Add to Salesforce' - -
-
- - - {{ t('Delete Indication Label After Successful Salesforce Update') }} - - - - Check this checkbox to automatically remove the Indication Label from properties that were successfully updated in - Salesforce in order to prevent future automatic updates. - -
-
- - {{ t('Violation Label') }} - - @for (l of labels; track l.id) { - {{ l.name }} +
+ +
{{ t('Scheduled Daily Updates') }}
+
+
+ {{ t('If you would like to automatically update Salesforce on a daily basis, configure the fields below') }} +
+
+ + {{ t('Hour') }} + + Enter the hour when the update should be run daily (0-23) Timezone: America/New_York + +
+
+ + {{ t('Minute') }} + + Enter the minute after the hour when the update should be run daily (0-59) + +
+
+ + {{ t('Logging Email') }} + + Enter the e-mail address to use when reporting errors during the Salesforce updating process + @if (salesforceForm.get('salesforceConfig.logging_email').hasError('email')) { + Please enter a valid email address } - - Label used to designate that a SEED property has a violation. Example: 'Violation - Insufficient Data' - -
-
- - {{ t('Compliance Label') }} - - @for (l of labels; track l.id) { - {{ l.name }} + +
+ @if (salesforceConfig) { +
+
+ {{ t('Last Salesforce Update') }}: {{ salesforceConfig.last_update_date || 'N/A' }} +
+ @if (salesforceConfig.last_update_date) { + } - - Label used to designate that a SEED property is in compliance. Example: 'Complied' - -
- - -
- -
{{ t('Contacts and Accounts') }}
-
-
- {{ - t( - 'Configure the contact information used for benchmark status notifications in Salesforce. When configured, this functionality will create or update contact records in Salesforce and link them to the Benchmark object' - ) - }} -
-
- - {{ t('Salesforce Account Object Record Type') }} - - If your Salesforce instance has multiple account types, provide the Record Type ID of the type of account to use when - accounts are automatically created from SEED - -
-
- - {{ t('Salesforce Contact Object Record Type') }} - - If your Salesforce instance has multiple contact types, provide the Record Type ID of the type to use when contacts - are automatically created from SEED - -
+
+ } + -
{{ t('Main Contact Settings') }}
-
- {{ - t( - 'The following fields will be used to retrieve or create a main contact to associate with the Benchmark object in Salesforce. Leave blank if your instance does not use this functionality.' - ) - }} -
-
- - {{ t('Contact Account Name Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the account name for the contact record to be created in Salesforce. Ex: - 'Organization' - -
-
- - {{ t('Default Contact Account Name') }} - - Provide a default account name for Salesforce to use when there is no valid data in the Contact Account Name Column - specified above. Leave this field blank to report an error and abort sync instead when the Contact Account Name Column - is blank. - -
-
- - {{ t('Contact Name Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the contact name for the benchmark record. Ex: 'On Behalf Of' - -
-
- - {{ t('Contact Email Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the contact email for the benchmark record. Ex: 'Email' - -
-
- - {{ t('Contact Benchmark Field') }} - - If your Salesforce Benchmark Record stores a Salesforce Contact relation, provide the Salesforce field name here, ex: - Contact_Name__c - -
-
{{ t('Data Administrator Contact Settings') }}
-
- {{ - t( - 'The following fields will be used to retrieve or create a property data administrator contact to associate with the Benchmark object in Salesforce. Leave blank if your instance does not use this functionality.' - ) - }} -
-
- - {{ t('Data Administrator Account Name Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the account name for the data administrator contact record to be created in - Salesforce. Ex: 'Organization' - -
-
- - {{ t('Default Data Administrator Account Name') }} - - Provide a default account name for Salesforce to use when there is no valid data in the Data Administrator Account - Name Column specified above. Leave this field blank to report an error and abort sync instead when the Data - Administrator Account Name Column is blank - -
-
- - {{ t('Data Administrator Name Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the property data administrator's name for the benchmark record. Ex: 'Property Data - Administrator' - -
-
- - {{ t('Data Administrator Email Column') }} - - @for (c of columns; track c.id) { - {{ c.display_name }} - } - - Select the SEED field that holds the property data administrator's email for the benchmark record. Ex: 'Property Data - Administrator - Email' - -
-
- - {{ t('Data Administrator Contact Field') }} - - - If your Salesforce Benchmark Record stores a Salesforce Contact relation for a Property Data Administrator, provide - the Salesforce field name here, ex: Property_Data_Administrator__c +
+ +
{{ t('Configuration') }}
+
+
{{ t('Configure a few parameters needed for data transfer to Salesforce') }}
+
+ + {{ t('Indication Label') }} + + @for (l of labels; track l.id) { + {{ l.name }} + } + + Label used to designate that a SEED property should be updated in Salesforce. Example: 'Add to + Salesforce' + +
+
+ + + {{ t('Delete Indication Label After Successful Salesforce Update') }} + + + + Check this checkbox to automatically remove the Indication Label from properties that were successfully updated in + Salesforce in order to prevent future automatic updates. - -
- +
+
+ + {{ t('Violation Label') }} + + @for (l of labels; track l.id) { + {{ l.name }} + } + + Label used to designate that a SEED property has a violation. Example: 'Violation - Insufficient Data' + +
+
+ + {{ t('Compliance Label') }} + + @for (l of labels; track l.id) { + {{ l.name }} + } + + Label used to designate that a SEED property is in compliance. Example: 'Complied' + +
+ + +
+ +
{{ t('Benchmark Configuration') }}
+
+
+ {{ t('Configure the fields used to identify and update benchmark records in Salesforce') }} +
+
+ + {{ t('Salesforce Unique Benchmark ID Fieldname') }} + + The API field name in the Salesforce Benchmark object that stores the unique identifier. Ex: + Unique_Benchmark_ID__c + +
+
+ + {{ t('SEED Unique Benchmark ID Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED property column that contains the unique identifier matching the Salesforce Benchmark ID + field + +
+
+ + {{ t('Cycle Name Benchmark Field') }} + + The API field name in the Salesforce Benchmark object that stores the cycle name. Ex: Cycle__c + +
+
+ + {{ t('Status Label Benchmark Field') }} + + The API field name in the Salesforce Benchmark object that stores the status label. Ex: Status__c + +
+
+ + {{ t('All Labels Benchmark Field') }} + + The API field name in the Salesforce Benchmark object that stores all labels. Ex: Labels__c + +
+ + +
+ +
{{ t('Contacts and Accounts') }}
+
+
+ {{ + t( + 'Configure the contact information used for benchmark status notifications in Salesforce. When configured, this functionality will create or update contact records in Salesforce and link them to the Benchmark object' + ) + }} +
+
+ + {{ t('Salesforce Account Object Record Type') }} + + If your Salesforce instance has multiple account types, provide the Record Type ID of the type of account to use + when accounts are automatically created from SEED + +
+
+ + {{ t('Salesforce Contact Object Record Type') }} + + If your Salesforce instance has multiple contact types, provide the Record Type ID of the type to use when contacts + are automatically created from SEED + +
+ +
{{ t('Main Contact Settings') }}
+
+ {{ + t( + 'The following fields will be used to retrieve or create a main contact to associate with the Benchmark object in Salesforce. Leave blank if your instance does not use this functionality.' + ) + }} +
+
+ + {{ t('Contact Account Name Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the account name for the contact record to be created in Salesforce. Ex: + 'Organization' + +
+
+ + {{ t('Default Contact Account Name') }} + + Provide a default account name for Salesforce to use when there is no valid data in the Contact Account Name Column + specified above. Leave this field blank to report an error and abort sync instead when the Contact Account Name + Column is blank. + +
+
+ + {{ t('Contact Name Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the contact name for the benchmark record. Ex: 'On Behalf Of' + +
+
+ + {{ t('Contact Email Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the contact email for the benchmark record. Ex: 'Email' + +
+
+ + {{ t('Contact Benchmark Field') }} + + If your Salesforce Benchmark Record stores a Salesforce Contact relation, provide the Salesforce field name here, + ex: Contact_Name__c + +
+
{{ t('Data Administrator Contact Settings') }}
+
+ {{ + t( + 'The following fields will be used to retrieve or create a property data administrator contact to associate with the Benchmark object in Salesforce. Leave blank if your instance does not use this functionality.' + ) + }} +
+
+ + {{ t('Data Administrator Account Name Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the account name for the data administrator contact record to be created in + Salesforce. Ex: 'Organization' + +
+
+ + {{ t('Default Data Administrator Account Name') }} + + Provide a default account name for Salesforce to use when there is no valid data in the Data Administrator Account + Name Column specified above. Leave this field blank to report an error and abort sync instead when the Data + Administrator Account Name Column is blank + +
+
+ + {{ t('Data Administrator Name Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the property data administrator's name for the benchmark record. Ex: 'Property + Data Administrator' + +
+
+ + {{ t('Data Administrator Email Column') }} + + @for (c of columns; track c.id) { + {{ c.display_name }} + } + + Select the SEED field that holds the property data administrator's email for the benchmark record. Ex: 'Property + Data Administrator - Email' + +
+
+ + {{ t('Data Administrator Contact Field') }} + + + If your Salesforce Benchmark Record stores a Salesforce Contact relation for a Property Data Administrator, provide + the Salesforce field name here, ex: Property_Data_Administrator__c + + +
+ + }
diff --git a/src/app/modules/organizations/settings/salesforce/salesforce.component.ts b/src/app/modules/organizations/settings/salesforce/salesforce.component.ts index 0c9971f5..465dad4b 100644 --- a/src/app/modules/organizations/settings/salesforce/salesforce.component.ts +++ b/src/app/modules/organizations/settings/salesforce/salesforce.component.ts @@ -26,6 +26,8 @@ export class SalesforceComponent implements OnDestroy, OnInit { private _dialog = inject(MatDialog) passwordHidden = true tokenHidden = true + testConnectionStatus: 'idle' | 'success' | 'error' = 'idle' + testConnectionMessage = '' labels: Label[] columns: Column[] organization: Organization @@ -74,6 +76,7 @@ export class SalesforceComponent implements OnDestroy, OnInit { this._organizationService.currentOrganization$.pipe(takeUntil(this._unsubscribeAll$)).subscribe((organization) => { this.organization = organization this.salesforceForm.get('salesforce_enabled').setValue(this.organization.salesforce_enabled) + this._setFormEnabledState(this.organization.salesforce_enabled) }) this._salesforceService.config$.pipe(takeUntil(this._unsubscribeAll$)).subscribe((config) => { this.salesforceConfig = config @@ -186,22 +189,25 @@ export class SalesforceComponent implements OnDestroy, OnInit { } testConnection(): void { + this.testConnectionStatus = 'idle' this.updateConfig() - this._salesforceService.test_connection(this.organization.id, this.salesforceConfig).subscribe() + this._salesforceService.test_connection(this.organization.id, this.salesforceConfig).subscribe({ + next: () => { + this.testConnectionStatus = 'success' + this.testConnectionMessage = '' + }, + error: (error: { message?: string }) => { + this.testConnectionStatus = 'error' + this.testConnectionMessage = error?.message || 'Connection failed' + }, + }) } toggleForm(): void { const enabled = this.salesforceForm.get('salesforce_enabled').value this.organization.salesforce_enabled = enabled this._organizationService.updateSettings(this.organization).subscribe() - const fg = this.salesforceForm.get('salesforceConfig') as FormGroup - for (const field of Object.keys(fg.controls)) { - if (enabled) { - this.salesforceForm.get(`salesforceConfig.${field}`).enable() - } else { - this.salesforceForm.get(`salesforceConfig.${field}`).disable() - } - } + this._setFormEnabledState(enabled) } updateConfig(): void { @@ -226,4 +232,15 @@ export class SalesforceComponent implements OnDestroy, OnInit { } } } + + private _setFormEnabledState(enabled: boolean): void { + const fg = this.salesforceForm.get('salesforceConfig') as FormGroup + for (const field of Object.keys(fg.controls)) { + if (enabled) { + this.salesforceForm.get(`salesforceConfig.${field}`).enable() + } else { + this.salesforceForm.get(`salesforceConfig.${field}`).disable() + } + } + } }